Imported Upstream version 0.21.1
authorAnas Nashif <anas.nashif@intel.com>
Tue, 12 Feb 2013 05:10:32 +0000 (21:10 -0800)
committerAnas Nashif <anas.nashif@intel.com>
Tue, 12 Feb 2013 05:10:32 +0000 (21:10 -0800)
355 files changed:
CHANGES [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENCE [new file with mode: 0644]
M2Crypto.egg-info/PKG-INFO [new file with mode: 0644]
M2Crypto.egg-info/SOURCES.txt [new file with mode: 0644]
M2Crypto.egg-info/dependency_links.txt [new file with mode: 0644]
M2Crypto.egg-info/top_level.txt [new file with mode: 0644]
M2Crypto/ASN1.py [new file with mode: 0644]
M2Crypto/AuthCookie.py [new file with mode: 0644]
M2Crypto/BIO.py [new file with mode: 0644]
M2Crypto/BN.py [new file with mode: 0755]
M2Crypto/DH.py [new file with mode: 0644]
M2Crypto/DSA.py [new file with mode: 0644]
M2Crypto/EC.py [new file with mode: 0644]
M2Crypto/EVP.py [new file with mode: 0644]
M2Crypto/Engine.py [new file with mode: 0644]
M2Crypto/Err.py [new file with mode: 0644]
M2Crypto/PGP/PublicKey.py [new file with mode: 0644]
M2Crypto/PGP/PublicKeyRing.py [new file with mode: 0644]
M2Crypto/PGP/RSA.py [new file with mode: 0644]
M2Crypto/PGP/__init__.py [new file with mode: 0644]
M2Crypto/PGP/constants.py [new file with mode: 0644]
M2Crypto/PGP/packet.py [new file with mode: 0644]
M2Crypto/RC4.py [new file with mode: 0644]
M2Crypto/RSA.py [new file with mode: 0644]
M2Crypto/Rand.py [new file with mode: 0644]
M2Crypto/SMIME.py [new file with mode: 0644]
M2Crypto/SSL/Checker.py [new file with mode: 0644]
M2Crypto/SSL/Cipher.py [new file with mode: 0644]
M2Crypto/SSL/Connection.py [new file with mode: 0644]
M2Crypto/SSL/Context.py [new file with mode: 0644]
M2Crypto/SSL/SSLServer.py [new file with mode: 0644]
M2Crypto/SSL/Session.py [new file with mode: 0644]
M2Crypto/SSL/TwistedProtocolWrapper.py [new file with mode: 0644]
M2Crypto/SSL/__init__.py [new file with mode: 0644]
M2Crypto/SSL/cb.py [new file with mode: 0644]
M2Crypto/SSL/ssl_dispatcher.py [new file with mode: 0644]
M2Crypto/SSL/timeout.py [new file with mode: 0644]
M2Crypto/X509.py [new file with mode: 0644]
M2Crypto/__init__.py [new file with mode: 0644]
M2Crypto/callback.py [new file with mode: 0644]
M2Crypto/ftpslib.py [new file with mode: 0644]
M2Crypto/httpslib.py [new file with mode: 0644]
M2Crypto/m2.py [new file with mode: 0644]
M2Crypto/m2urllib.py [new file with mode: 0644]
M2Crypto/m2urllib2.py [new file with mode: 0644]
M2Crypto/m2xmlrpclib.py [new file with mode: 0644]
M2Crypto/threading.py [new file with mode: 0644]
M2Crypto/util.py [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README [new file with mode: 0644]
SWIG/Makefile [new file with mode: 0644]
SWIG/Makefile.mw [new file with mode: 0644]
SWIG/Makefile.osx [new file with mode: 0644]
SWIG/_aes.i [new file with mode: 0644]
SWIG/_asn1.i [new file with mode: 0644]
SWIG/_bio.i [new file with mode: 0644]
SWIG/_bn.i [new file with mode: 0755]
SWIG/_dh.i [new file with mode: 0644]
SWIG/_dsa.i [new file with mode: 0644]
SWIG/_ec.i [new file with mode: 0644]
SWIG/_engine.i [new file with mode: 0644]
SWIG/_evp.i [new file with mode: 0644]
SWIG/_lib.h [new file with mode: 0644]
SWIG/_lib.i [new file with mode: 0644]
SWIG/_m2crypto.def [new file with mode: 0644]
SWIG/_m2crypto.i [new file with mode: 0644]
SWIG/_objects.i [new file with mode: 0644]
SWIG/_pkcs7.i [new file with mode: 0644]
SWIG/_rand.i [new file with mode: 0644]
SWIG/_rc4.i [new file with mode: 0644]
SWIG/_rsa.i [new file with mode: 0644]
SWIG/_ssl.i [new file with mode: 0644]
SWIG/_threads.i [new file with mode: 0644]
SWIG/_util.i [new file with mode: 0644]
SWIG/_x509.i [new file with mode: 0644]
contrib/README [new file with mode: 0644]
contrib/SimpleX509create.README [new file with mode: 0644]
contrib/SimpleX509create.py [new file with mode: 0644]
contrib/dave.README [new file with mode: 0644]
contrib/dave.patch [new file with mode: 0644]
contrib/dispatcher.README [new file with mode: 0644]
contrib/dispatcher.py [new file with mode: 0644]
contrib/isaac.README [new file with mode: 0644]
contrib/isaac.httpslib.py [new file with mode: 0644]
contrib/m2crypto.spec [new file with mode: 0644]
contrib/smimeplus.README [new file with mode: 0644]
contrib/smimeplus.py [new file with mode: 0644]
demo/CipherSaber/CipherSaber.py [new file with mode: 0644]
demo/CipherSaber/cstest1.cs1 [new file with mode: 0644]
demo/Zope/ZServer/HTTPS_Server.py [new file with mode: 0644]
demo/Zope/ZServer/__init__.py [new file with mode: 0644]
demo/Zope/ZServer/medusa/ftps_server.py [new file with mode: 0644]
demo/Zope/ZServer/medusa/https_server.py [new file with mode: 0644]
demo/Zope/ca.pem [new file with mode: 0644]
demo/Zope/dh1024.pem [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/README.txt [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/TODO.txt [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/__init__.py [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/add.dtml [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/refresh.txt [new file with mode: 0644]
demo/Zope/lib/python/Products/GuardedFile/version.txt [new file with mode: 0644]
demo/Zope/lib/python/Products/ZSmime/README.txt [new file with mode: 0644]
demo/Zope/lib/python/Products/ZSmime/SmimeTag.py [new file with mode: 0644]
demo/Zope/lib/python/Products/ZSmime/__init__.py [new file with mode: 0644]
demo/Zope/lib/python/Products/ZSmime/version.txt [new file with mode: 0644]
demo/Zope/server.pem [new file with mode: 0644]
demo/Zope/starts [new file with mode: 0644]
demo/Zope/starts.bat [new file with mode: 0755]
demo/Zope/utilities/x509_user.py [new file with mode: 0644]
demo/Zope/z2s.py [new file with mode: 0644]
demo/Zope/z2s.py.diff [new file with mode: 0644]
demo/Zope27/INSTALL.txt [new file with mode: 0644]
demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py [new file with mode: 0644]
demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch [new file with mode: 0644]
demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch [new file with mode: 0644]
demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch [new file with mode: 0644]
demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py [new file with mode: 0644]
demo/Zope27/instance_home/README.txt.patch [new file with mode: 0644]
demo/Zope27/instance_home/etc/zope.conf.patch [new file with mode: 0644]
demo/Zope27/instance_home/ssl/ca.pem [new file with mode: 0644]
demo/Zope27/instance_home/ssl/dh1024.pem [new file with mode: 0644]
demo/Zope27/instance_home/ssl/server.pem [new file with mode: 0644]
demo/ZopeX3/INSTALL.txt [new file with mode: 0644]
demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch [new file with mode: 0644]
demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py [new file with mode: 0644]
demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py [new file with mode: 0644]
demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py [new file with mode: 0644]
demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py [new file with mode: 0644]
demo/ZopeX3/instance_home/etc/zope.conf.patch [new file with mode: 0644]
demo/ZopeX3/instance_home/ssl/ca.pem [new file with mode: 0644]
demo/ZopeX3/instance_home/ssl/dh1024.pem [new file with mode: 0644]
demo/ZopeX3/instance_home/ssl/server.pem [new file with mode: 0644]
demo/bio_mem_rw.py [new file with mode: 0644]
demo/dhtest.py [new file with mode: 0644]
demo/dsa1024pvtkey.pem [new file with mode: 0644]
demo/dsa_bench.py [new file with mode: 0644]
demo/dsatest.pem [new file with mode: 0644]
demo/dsatest.py [new file with mode: 0644]
demo/ec/ecdhtest.py [new file with mode: 0644]
demo/ec/ecdsa_bench.py [new file with mode: 0644]
demo/ec/ecdsatest.pem [new file with mode: 0644]
demo/ec/ecdsatest.py [new file with mode: 0644]
demo/ec/secp160r1pvtkey.pem [new file with mode: 0644]
demo/https.howto/ca.pem [new file with mode: 0644]
demo/https.howto/dh1024.pem [new file with mode: 0644]
demo/https.howto/get_https.py [new file with mode: 0755]
demo/https.howto/https_cli.py [new file with mode: 0644]
demo/https.howto/orig_https_srv.py [new file with mode: 0644]
demo/https.howto/server.pem [new file with mode: 0644]
demo/medusa/00_README [new file with mode: 0644]
demo/medusa/START.py [new file with mode: 0644]
demo/medusa/START_xmlrpc.py [new file with mode: 0644]
demo/medusa/asynchat.py [new file with mode: 0644]
demo/medusa/asyncore.py [new file with mode: 0644]
demo/medusa/auth_handler.py [new file with mode: 0644]
demo/medusa/ca.pem [new file with mode: 0644]
demo/medusa/counter.py [new file with mode: 0644]
demo/medusa/default_handler.py [new file with mode: 0644]
demo/medusa/dh1024.pem [new file with mode: 0644]
demo/medusa/filesys.py [new file with mode: 0644]
demo/medusa/ftp_server.py [new file with mode: 0644]
demo/medusa/ftps_server.py [new file with mode: 0644]
demo/medusa/http_date.py [new file with mode: 0644]
demo/medusa/http_server.py [new file with mode: 0644]
demo/medusa/https_server.py [new file with mode: 0644]
demo/medusa/index.html [new file with mode: 0644]
demo/medusa/logger.py [new file with mode: 0644]
demo/medusa/m_syslog.py [new file with mode: 0644]
demo/medusa/medusa_gif.py [new file with mode: 0644]
demo/medusa/mime_type_table.py [new file with mode: 0644]
demo/medusa/poison_handler.py [new file with mode: 0644]
demo/medusa/producers.py [new file with mode: 0644]
demo/medusa/put_handler.py [new file with mode: 0644]
demo/medusa/redirecting_handler.py [new file with mode: 0644]
demo/medusa/server.pem [new file with mode: 0644]
demo/medusa/status_handler.py [new file with mode: 0644]
demo/medusa/virtual_handler.py [new file with mode: 0644]
demo/medusa/xmlrpc_handler.py [new file with mode: 0644]
demo/medusa054/00_README [new file with mode: 0644]
demo/medusa054/START.py [new file with mode: 0644]
demo/medusa054/START_xmlrpc.py [new file with mode: 0644]
demo/medusa054/ca.pem [new file with mode: 0644]
demo/medusa054/counter.py [new file with mode: 0644]
demo/medusa054/default_handler.py [new file with mode: 0644]
demo/medusa054/dh1024.pem [new file with mode: 0644]
demo/medusa054/filesys.py [new file with mode: 0644]
demo/medusa054/ftp_server.py [new file with mode: 0644]
demo/medusa054/ftps_server.py [new file with mode: 0644]
demo/medusa054/http_date.py [new file with mode: 0644]
demo/medusa054/http_server.py [new file with mode: 0644]
demo/medusa054/https_server.py [new file with mode: 0644]
demo/medusa054/index.html [new file with mode: 0644]
demo/medusa054/logger.py [new file with mode: 0644]
demo/medusa054/m_syslog.py [new file with mode: 0644]
demo/medusa054/medusa_gif.py [new file with mode: 0644]
demo/medusa054/poison_handler.py [new file with mode: 0644]
demo/medusa054/producers.py [new file with mode: 0644]
demo/medusa054/server.pem [new file with mode: 0644]
demo/medusa054/status_handler.py [new file with mode: 0644]
demo/medusa054/xmlrpc_handler.py [new file with mode: 0644]
demo/perf/memio.py [new file with mode: 0644]
demo/perf/sha1.py [new file with mode: 0644]
demo/pgp/pgpstep.py [new file with mode: 0644]
demo/pgp/pubring.pgp [new file with mode: 0644]
demo/pgp/secring.pgp [new file with mode: 0644]
demo/pkcs7/pkcs7-thawte.pem [new file with mode: 0644]
demo/pkcs7/test.py [new file with mode: 0644]
demo/rsa.priv.pem [new file with mode: 0644]
demo/rsa.priv0.pem [new file with mode: 0644]
demo/rsa.pub.pem [new file with mode: 0644]
demo/rsa1024pvtkey.pem [new file with mode: 0644]
demo/rsa_bench.py [new file with mode: 0644]
demo/rsatest.py [new file with mode: 0644]
demo/smime.howto/README [new file with mode: 0644]
demo/smime.howto/decrypt.py [new file with mode: 0644]
demo/smime.howto/dv.py [new file with mode: 0644]
demo/smime.howto/encrypt.p7 [new file with mode: 0644]
demo/smime.howto/encrypt.py [new file with mode: 0644]
demo/smime.howto/recipient.pem [new file with mode: 0644]
demo/smime.howto/recipient_key.pem [new file with mode: 0644]
demo/smime.howto/se.p7 [new file with mode: 0644]
demo/smime.howto/se.py [new file with mode: 0644]
demo/smime.howto/sendsmime.py [new file with mode: 0644]
demo/smime.howto/sign.p7 [new file with mode: 0644]
demo/smime.howto/sign.py [new file with mode: 0644]
demo/smime.howto/signer.pem [new file with mode: 0644]
demo/smime.howto/signer_key.pem [new file with mode: 0644]
demo/smime.howto/verify.py [new file with mode: 0644]
demo/smime/README [new file with mode: 0644]
demo/smime/ca.pem [new file with mode: 0644]
demo/smime/clear.p7 [new file with mode: 0644]
demo/smime/client.p12 [new file with mode: 0644]
demo/smime/client.pem [new file with mode: 0644]
demo/smime/client2.pem [new file with mode: 0644]
demo/smime/m2.se.p7 [new file with mode: 0644]
demo/smime/ns.p7 [new file with mode: 0644]
demo/smime/ns.se.p7 [new file with mode: 0644]
demo/smime/opaque.p7 [new file with mode: 0644]
demo/smime/sendsmime.py [new file with mode: 0644]
demo/smime/test.py [new file with mode: 0644]
demo/smime/unsmime.py [new file with mode: 0644]
demo/ssl/README [new file with mode: 0644]
demo/ssl/c.py [new file with mode: 0644]
demo/ssl/c_bio.py [new file with mode: 0644]
demo/ssl/ca.der [new file with mode: 0644]
demo/ssl/ca.pem [new file with mode: 0644]
demo/ssl/client.p12 [new file with mode: 0644]
demo/ssl/client.pem [new file with mode: 0644]
demo/ssl/dh1024.pem [new file with mode: 0644]
demo/ssl/echo-eg.py [new file with mode: 0644]
demo/ssl/echo.py [new file with mode: 0644]
demo/ssl/echod-async.py [new file with mode: 0644]
demo/ssl/echod-eg1.py [new file with mode: 0644]
demo/ssl/echod-forking.py [new file with mode: 0644]
demo/ssl/echod-iterative.py [new file with mode: 0644]
demo/ssl/echod-thread.py [new file with mode: 0644]
demo/ssl/echod-threading.py [new file with mode: 0644]
demo/ssl/echod_lib.py [new file with mode: 0644]
demo/ssl/ftp_tls.py [new file with mode: 0644]
demo/ssl/http_cli_20.py [new file with mode: 0644]
demo/ssl/https_cli.py [new file with mode: 0644]
demo/ssl/https_cli_async.py [new file with mode: 0644]
demo/ssl/https_srv.py [new file with mode: 0644]
demo/ssl/myapp.py [new file with mode: 0644]
demo/ssl/s_client.py [new file with mode: 0644]
demo/ssl/s_server.py [new file with mode: 0644]
demo/ssl/server.pem [new file with mode: 0644]
demo/ssl/server3.py [new file with mode: 0644]
demo/ssl/sess.py [new file with mode: 0644]
demo/ssl/sess2.py [new file with mode: 0644]
demo/ssl/sess2.ssldump.out [new file with mode: 0644]
demo/ssl/socklib.py [new file with mode: 0644]
demo/ssl/somelib.py [new file with mode: 0644]
demo/ssl/ss.py [new file with mode: 0644]
demo/ssl/twistedsslclient.py [new file with mode: 0755]
demo/ssl/twistedsslserver.py [new file with mode: 0755]
demo/ssl/xmlrpc_cli.py [new file with mode: 0644]
demo/ssl/xmlrpc_srv.py [new file with mode: 0644]
demo/tinderbox/build_lib.py [new file with mode: 0644]
demo/tinderbox/killableprocess.py [new file with mode: 0644]
demo/tinderbox/slave.py [new file with mode: 0755]
demo/tinderbox/winprocess.py [new file with mode: 0644]
demo/x509/ca.py [new file with mode: 0644]
demo/x509/certdata2pem.py [new file with mode: 0755]
demo/x509/client2.pem [new file with mode: 0644]
demo/x509/demo1.py [new file with mode: 0644]
demo/x509/proxy_destroy.py [new file with mode: 0644]
demo/x509/proxy_info.py [new file with mode: 0644]
demo/x509/proxy_init.py [new file with mode: 0644]
demo/x509/proxylib.py [new file with mode: 0644]
demo/x509/server-expired.pem [new file with mode: 0644]
demo/x509/server.pem [new file with mode: 0644]
demo/x509/x509auth.py [new file with mode: 0644]
doc/ZServerSSL-HOWTO.html [new file with mode: 0644]
doc/howto.ca.html [new file with mode: 0644]
doc/howto.smime.html [new file with mode: 0644]
doc/howto.ssl.html [new file with mode: 0644]
epydoc.conf [new file with mode: 0644]
fedora_setup.sh [new file with mode: 0755]
pack.py [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
tests/README [new file with mode: 0644]
tests/__init__.py [new file with mode: 0644]
tests/alltests.py [new file with mode: 0644]
tests/ca.pem [new file with mode: 0644]
tests/der_encoded_seq.b64 [new file with mode: 0644]
tests/dhparams.pem [new file with mode: 0644]
tests/dsa.param.pem [new file with mode: 0644]
tests/dsa.priv.pem [new file with mode: 0644]
tests/dsa.pub.pem [new file with mode: 0644]
tests/ec.priv.pem [new file with mode: 0644]
tests/ec.pub.pem [new file with mode: 0644]
tests/fips.py [new file with mode: 0644]
tests/long_serial_cert.pem [new file with mode: 0644]
tests/pubring.pgp [new file with mode: 0644]
tests/recipient.pem [new file with mode: 0644]
tests/recipient_key.pem [new file with mode: 0644]
tests/rsa.priv.pem [new file with mode: 0644]
tests/rsa.priv2.pem [new file with mode: 0644]
tests/rsa.pub.pem [new file with mode: 0644]
tests/server.pem [new file with mode: 0644]
tests/signer.pem [new file with mode: 0644]
tests/signer_key.pem [new file with mode: 0644]
tests/test_asn1.py [new file with mode: 0644]
tests/test_authcookie.py [new file with mode: 0644]
tests/test_bio.py [new file with mode: 0644]
tests/test_bio_file.py [new file with mode: 0644]
tests/test_bio_iobuf.py [new file with mode: 0644]
tests/test_bio_membuf.py [new file with mode: 0644]
tests/test_bio_ssl.py [new file with mode: 0644]
tests/test_bn.py [new file with mode: 0755]
tests/test_dh.py [new file with mode: 0644]
tests/test_dsa.py [new file with mode: 0644]
tests/test_ec_curves.py [new file with mode: 0644]
tests/test_ecdh.py [new file with mode: 0644]
tests/test_ecdsa.py [new file with mode: 0644]
tests/test_engine.py [new file with mode: 0644]
tests/test_evp.py [new file with mode: 0644]
tests/test_obj.py [new file with mode: 0644]
tests/test_pgp.py [new file with mode: 0644]
tests/test_rand.py [new file with mode: 0644]
tests/test_rc4.py [new file with mode: 0644]
tests/test_rsa.py [new file with mode: 0644]
tests/test_smime.py [new file with mode: 0644]
tests/test_ssl.py [new file with mode: 0644]
tests/test_ssl_offline.py [new file with mode: 0644]
tests/test_ssl_win.py [new file with mode: 0644]
tests/test_threading.py [new file with mode: 0644]
tests/test_x509.py [new file with mode: 0644]
tests/thawte.pem [new file with mode: 0644]
tests/x509.der [new file with mode: 0644]
tests/x509.pem [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
new file mode 100644 (file)
index 0000000..8355a94
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,488 @@
+0.21.1 - 2011-01-15
+-------------------
+- Distribution fix
+
+0.21 - 2011-01-12
+-----------------
+- Support OpenSSL 1.0. Thanks to Miloslav Trmac for figuring out how to fix
+  test_smime.py
+- Rename m2.engine_init to engine_init_error so that
+  ENGINE_init and ENGINE_finish can be exposed, thanks to Erlo
+- 0.20 started releasing Python locks even around some operations that
+  interacted with the Python runtime, potentially causing crashes and other
+  weirdness, fix by Miloslav Trmac
+- Make httpslib.ProxyHTTPSConnection work with Python 2.3
+
+0.20.2 - 2009-10-06
+-------------------
+- (Re)Enable configuration and use with OpenSSL 0.9.7g and older by disabling
+  RSA PSS methods when using such old OpenSSL, thanks to Stef Walter
+
+0.20.1 - 2009-08-27
+-------------------
+- Fix regression in httpslib.ProxyHTTPSConnection, by Miloslav Trmac
+
+0.20 - 2009-08-10
+-----------------
+- Deprecated M2Crypto.PGP subpackage since nobody seems to be using it nor
+  is it being maintained (if you do use it, please let me know)
+- Added fedora_setup.sh to help work around differences on Fedora Core -based
+  distributions (RedHat, CentOS, ...); thanks to Miloslav Trmac
+- Added X509.load_request_bio and load_request_string, by Hartmut Goebel and
+  Pavel Shramov
+- Added alias X509.Request.set_subject for set_subject_name to match X509.X509,
+  by Pavel Shramov
+- OBJ_* wrappers did not work properly with OpenSSL 0.9.8a and earlier, fix by
+  Pavel Shramov
+- Added ASN1_UTCTIME.get_datetime and set_datetime, by Pavel Shramov
+- Fixed obj_obj2txt, which returned nonsense, fix by Barney Stratford
+- m2urllib did not close sockets properly, fix by Miloslav Trmac
+- Allow SSL peer certificate to have subjectAltName without dNSName and use
+  commonName for hostname check, fix by Miloslav Trmac
+- threading_locking_callback did not block on a lock when the lock
+  was held by another thread, by Miloslav Trmac
+- Allow more blocking OpenSSL functions to run without GIL, by Miloslav Trmac
+- Fixed httpslib to send only the path+query+fragment part of the URL when
+  using CONNECT proxy, by James Bowes  
+- SSLServer.__init__ now takes optional bind_and_activate parameter and
+  initializes by calling SocketServer.BaseServer.__init__, which
+  are Python 2.6 compatibility fixes, by Christian
+- ftpslib now works with Python 2.6, by Theodore A. Roth
+- httpslib.ProxyHTTPSConnection needs to cast port into integer,
+  by John M. Schanck
+- Added support for RSASSA-PSS signing and verifying, by Chris Collis
+- Added support for disabling padding when using RSA encryption,
+  by Chris Collis
+- ASN1_INTEGERs can now be larger than fits in an int, for example to support
+  X509 certificates with large serial numbers, 
+  patch by Mikhail Vorozhtsov and testcase by Barry G.
+- Reverted a change done in 0.17 to m2urllib2 which changed urls to include
+  host when it should stay as it was
+- httpslib no longer uses urllib; instead it uses urlparse for url parsing
+- SMIME.text_crlf and text_crlf_bio were always raising TypeError; fixed
+- EVP.load_key and load_key_bio fixed to raise EVP.EVPError and BIO.BIOError
+  instead of str (str exceptions not allowed in Python 2.6 and later)
+- SSL.Session.load_session fixed to raise SSL.SSLError instead of str
+- SMIME.load_pkcs7, load_pkcs7_bio, smime_load_pkcs7, smime_load_pkcs7_bio,
+  text_crlf, text_crlf_bio fixed to raise BIO.BIOError, SMIME.PKCS7_Error and
+  SMIME.SMIME_Error as appropriate instead of str
+- Added FIPS mode to unit tests, and used FIPS-compliant key sizes in other
+  tests, by Miloslav Trmac. Note that tests run much slower because of this!
+- Unit tests cover 80% of the code
+
+0.19.1 - 2008-10-12
+-------------------
+- Re-enable building when OpenSSL built without EC support, by Miloslav Trmac
+- Remove shebang from Engine.py since it is not executable, by Miloslav Trmac
+
+0.19 - 2008-10-05
+-----------------
+- OpenSSL OBJ_* functions wrapped by Pavel Shramov
+- OpenSSL ENGINE interface wrapped, providing support for smart cards, by
+  Martin Paljak and Pavel Shramov
+- EVP.PKey.get_rsa() now returns RSA_pub, which fixes segmentation fault
+  when trying to encrypt using public key from X509 certificate, by Ben Timby
+- httpslib.ProxyHTTPSConnection now sends the required Host header,
+  by Karl Grindley
+- Use the proxied User-Agent value in CONNECT requests, by James Antill and
+  Miloslav Trmac
+- Fixed m2urllib.build_opener when optional handlers were in use,
+  affected Python 2.5 and later, by Miloslav Trmac
+- Reverted the incorrect GIL change done in 0.18 to m2.passphrase_callback,
+  which caused a deadlock when called from mod_python for example. Thanks to
+  Michal Kochel and Keith Jackson.
+- SSL.Connection.accept() passed wrong certificate to postConnectionCheck
+  callback
+- httpslib.HTTPSConnection now raises ValueError for illegal keyword argument
+- m2.pkey_write_pem[_no_cipher] changed to use the recommended (more secure)
+  PEM_write_bio_PKCS8PrivateKey (used by PEM_write_bio_PrivateKey).
+- X509.load_cert, load_cert_bio, load_cert_der_string, new_stack_from_der,
+  load_request and load_crl now raise X509Error for invalid data. Previously
+  some of these raised a string as an error, some did not raise but caused
+  strange errors later, for example x509.verify() would return -1.
+- Fixed SSL.Connection.get_socket_read_timeout and set_socket_read_timeout on
+  64bit platforms by adding SSL.timeout.struct_size() and using it instead of
+  hardcoded size for socket.getsockopt
+- X509_Store.load_info now returns the value from the underlying
+  m2.x509_store_load_locations call, and in case of error raises X509Error
+- Fixed SMIME.verify to raise the correct PKCS7_Error (used to raise
+  SMIME_Error) when verification fails with Python 2.6
+
+0.18.2 - 2007-10-12
+-------------------
+- typedef Py_ssize_t was insufficiently guarded, now follows PEP 353. This
+  prevented building on at least Red Hat Linux and Debian Linux (unstable).
+
+0.18.1 - 2007-10-08
+-------------------
+- Redo build fix when OpenSSL configured without Elliptic Curves (EC), see
+  also INSTALL file
+
+0.18 - 2007-07-26
+-----------------
+- Added EVP.pbkdf2 to derive key from password
+- X509_Store_Context.get1_chain added
+- Added X509_Name.__iter__, __getitem__, get_entries_by_nid which allow
+  iterating over all X509_Name_Entries or getting just all commonName entries,
+  for example
+- Added X509_Name_Entry.get_object, get_data, set_data
+- Added back PKCS7.get0_signers (was removed in 0.16)
+- X509_Extension.get_value accepts flag and indent parameters.
+- support multiple dNSName fields in subjectAltName
+- support multiple commonName fields for SSL peer hostname checking
+- Checking for erroneous returns from more OpenSSL EVP_* functions, which
+  means that certain things that used to fail silently will now raise an
+  EVP.EVPError; affected m2 functions are: digest_final, cipher_init,
+  cipher_update, cipher_final and sign_update. sign_final will now raise
+  EVP.EVPError instead of SystemError as well.
+- Fixed Pkey.verify_final to take a sign parameter
+- If a subjectAltName extension of type dNSName is present in peer certificate,
+  use only the dNSNames when checking peer certificate hostname, as specified
+  by RFC 2818. If no dNSNames are present, use subject commonName.
+- Fixed memory leaks in m2 functions ec_key_new_by_curve_name,
+  pkey_get_modulus, ecdsa_verify, threading_init and
+  X509.X509.verify, X509.X509_Stack (which manifested for example when
+  calling X509.new_stack_from_der), SSL.Connection (which manifested with some
+  connection errors or when connect was never called), twisted wrapper,
+  SSL.Connection.makefile (in BIO.IOBuffer really)
+- Fixed threading regressions introduced in 0.16,
+  by Aaron Reizes and Keith Jackson
+- Added SSL session caching support to HTTPSConnection, by Keith Jackson
+- Added the ability to save and load DER formatted X509 certificates and
+  certificate requests, by Keith Jackson
+- m2xmlrpclib.py fixed to work with Python 2.5, by Miloslav Trmac
+- 64-bit correctness fixes, by Miloslav Trmac
+- Added X509_Name.as_hash, by Thomas Uram
+- Moved --openssl option from general setup.py option to build_ext option,
+  meaning you need to do: python setup.py build build_ext --openssl=/path,
+  by Philip Kershaw
+- Fixed build problem affecting certain systems where OpenSSL was built without
+  EC support
+- M2CRYPTO_TEST_SSL_SLEEP environment variable controls how long to sleep
+  after starting the test SSL server. Default is 0.5, but 0.1 or even 0.05
+  might work with modern computers. Makes tests finish significantly faster.
+
+0.17 - 2006-12-20
+-----------------
+- setup.py has new test command to run unit tests (requires setuptools)
+- Added m2urllib2, by James Bowes (python 2.4 and later, at least for now)
+- Added CONNECT proxy for httpslib and m2urllib2, by James Bowes
+- Added PKey.get_modulus, X509.get_fingerprint, X509_Name.as_der and
+  m2.bn_to_hex, by Thomas Uram
+- Prevent Connection.makefile from freeing bio redundantly, by Thomas Uram
+- Added Err.peek_error_code, by Thomas Uram
+- Fixed m2urllib.open_https to return the response headers, otherwise code
+  that relied on that would break (for example msnlib-3.5), by Arno bakker
+- Fixed twisted wrapper to work with >16kb BIO buffers, by Martin Paljak
+- Added support for remaining ECs, by Larry Bugbee
+- Fixed DSA.save_key and DSA_.save_pub_key, by Larry Bugbee
+- SSL.Context.load_verify_locations raises ValueError if cafile and capath
+  are both None
+- Fixed X509.check_purpose() (was always raising exceptions)
+- smime_read_pkcs7 was changed to automatically call BIO_set_mem_eof_return
+  on memory BIOs because otherwise the read would fail with 
+  "SMIME_Error: not enough data"
+- X509.new_extension('subjectKeyIdentifier', 'hash') raises ValueError instead
+  of crashing Python
+
+0.16 - 2006-07-05
+-----------------
+- Minimum requirements updated: Python 2.3+, OpenSSL 0.9.7+, SWIG 1.3.24+
+- Optional features from OpenSSL 0.9.8 and newer
+- Enhancements to EVP and X509 to allow proxy certificate handling,
+  by Matt Rodriguez
+- SSLBio and related additions to help do SSL with BIOs directly,
+  by Matt Rodriguez
+- Added --openssl option to build command which can be used to specify
+  where OpenSSL is installed, by Matt Rodriguez
+- Added sign and verify to RSA class, and get_rsa to PKey class,
+  by Matt Rodriguez
+- ECDSA signatures and ECDH key agreement, requires OpenSSL 0.9.8+,
+  by Arno Bakker
+- Fix non-hashable type problems in SSL._ctxmap and users,
+  by Michael Weiser
+- Fixed SSLServer.handle_error to take the correct number of
+  arguments, by Dan Williams
+- Various DSA enhancements by Larry Bugbee
+- Added sha224, sha256, sha384 and sha512, by Larry Bugbee
+- Added serialNumber, SN, surname, GN and givenName fields to X509_Name,
+  by Martin Paljak
+- m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT is the fourth certificate
+  verification error that will be allowed when unknown CAs are allowed
+- post connection checks in Connection.accept() and connect() fixed (these
+  were broken in 0.15)
+- Fixed EVP.Cipher to work with aes_* ciphers (used to crash Python).
+  The actual problem was in m2.bytes_to_key.
+- SMIME methods and functions raise correct exceptions
+- Raise ValueError instead of AttributeError when a non-existing hash
+  algorithm or SSL version is asked for
+- ssl_ctx_set_tmp_(dh|rsa) now return value, and the rsa version calls
+  the rsa function instead of the dh function
+- digest_update and verify_update return type changed to int, which allows
+  better error reporting; EVP.MessageDigest.update and
+  EVP.PKey.verify_update likewise changed
+- X509_Name and ASN1_String as_text (new for ASN1_String) take optional
+  parameters to control formatting.
+- Cipher_Stack, X509_Stack and X509_Extension_Stack are iterable
+- EVP.MessageDigest now properly cleans up the underlying data when the object
+  gets deleted
+- It is now possible to set and get non-nid values to X509_Name (previously
+  only set worked)
+- SSL.Connection.set_client_CA_list_from_file now uses the actual implementd
+  function instead of raising exception
+- Multithreaded SSL no longer uses the SSL_set/get_app_data to set and
+  restore thread state, but uses the standard PyGILState_STATE instead.
+- m2urllib no longer outputs the HTTP headers (there was an erronous call
+  to set_debuglevel(1))
+- Removed RCS_id, RCS_ID and _RCS_id from Python files
+- All known memory leaks fixed
+- SWIG and compiler warning fixes
+- More and better Epydoc formatted docstrings
+- More than doubled the number of unit tests, also made many demos into tests
+
+0.15 - 2005-08-17
+-----------------
+- Support OpenSSL 0.9.8, Python 2.4.1, SWIG 1.3.24
+- Fixed multiple memory leaks
+- Twisted integration
+- Safer defaults for SSL context and post connection check for clients
+- Eliminated C pointers from interfaces (some may still remain in callbacks)
+- Many cases where Python interpreter crashed have been fixed
+- Improved thread safety of many callbacks
+- And of course more of the OpenSSL API is covered, new docstrings and
+  tests have been written
+
+ Changes since 0.13
+--------------------
+- Fixed memory leak due to circular reference in SSL.Connection.
+  Thanks to Michael Dunstan. Oops, patch is ZServerSSL-specific.
+  Andre Reitz provided a generalised fix. Thanks Andre.
+- Fixed __getattr__ error in DSA. Thanks to Igor Belyi.
+- Added rand_poll, rand_screen and rand_win32_event functions to
+  M2Crypto.Rand.
+- Updated ZServerSSL files to match Zope 2.7.0 versions.
+- Integrated (overlapping) patches by Peter Teniz and Heikki Toivonen
+  covering operations on X.509-related structures that gives M2Crypto 
+  PKI functionality. Thanks Peter and Heikki.
+- Peter Teniz contributed demo2004/pki/x509auth.py.
+- Created demo2004/ directory that will contain new or updated demos.
+- Added verify_[init|update|final] in _evp.i. Patch by Zachery Corbiere.
+  Thanks Zac.
+
+
+ Changes since 0.12/0.11
+-------------------------
+- Patches from Artur Frysiak <wiget@pld-linux.org>. Thanks Artur.
+  = Allow using a passphrase callback in class SMIME.
+  = Added method get0_signers to class PKCS7, which retrieves signers' 
+    certificates from a PKCS7 blob.
+  = Added methods as_pem and save_pem to class X509.
+  = Added file version.py.
+  = Allow SSL.Context.load_verify_locations to accept both 'cafile' and 
+    'capath'.
+- Fixed BIO.read() not reading until EOF. Thanks to Egil Muller 
+  <redhog@redhog.org> for suggestion.
+- Honour 'mode' parameter in SSL.Connection.makefile. Thanks again to Egil 
+  Muller.
+- Roger Binns contributed epydoc-generated docs for M2Crypto. Thanks Roger.
+- Peter Teniz contributed patches to create X.509 requests and certificates.
+  Thanks Peter.
+- Updated Medusa to 0.54.
+- Make various OpenSSL bignum functions (written long ago) available to Python.
+
+
+ Changes since 0.11
+--------------------
+- ZServerSSL with client certificate-based authentication rides again.
+- Created Makefile for Python 2.3.
+- Modified LICENCE: changed my name to the generic "the author" in the 
+  all-caps disclaimer paragraph.
+- Allow to save RSA key pair in the clear.
+- ZServerSSL for Zope 2.7.
+- Excluded RC5. IDEA was taken out several releases ago. This should 
+  allow M2Crypto to build with stock OpenSSL on various Linuxen.
+- Added ssl_set_tmp_dh_callback.
+- Added ssl_set_tmp_rsa and ssl_set_tmp_rsa_callback to support weak-cipher
+  browsers.
+- ZServerSSL exports SSL_CIPHER request header (a la mod_ssl) to Zope 
+  applications.
+- Perform distutils's SWIG .i search path tweaking within setup.py. setup.py
+  should now work "out of the box".
+- Added contrib/smimeplus.py, a high-level S/MIME interface, contributed by 
+  Bernard Yue <bernie@3captus.com>. Thanks Bernard.
+- Added in long forms of nid's in X509_Name. Thanks to William K Volkman 
+  <development@netshark.com> for patch.
+- Updated Mac OS X build instructions. Thanks to Larry Bugbee 
+
+
+ Changes since 0.10
+--------------------
+- Dave Berkeley <dave@rotwang.freeserve.co.uk> contributed fixes to
+  SSL.Context-related memory leaks and code to set the size of the SSL
+  session cache.
+- Brent Chun <bnc@intel-research.net> contributed the following:
+  + Fixes to memory leaks.
+  + Code to expose X.509 certificate chain operations.
+  + Code to expose set/get operations on the SSL session cache.
+- Changed swig/ to SWIG/, for the convenience of people who don't read
+  INSTALL. Some Makefiles may break because of this. setup.py continues
+  to work, of course.
+- ZServerSSL tested with Zope 2.6.1. There is now a HOWTO.
+- Updated README and INSTALL.
+- Filled doc/ with stuff that went missing in several past releases.
+
+
+ Changes since 0.09
+--------------------
+- Updated to OpenSSL 0.9.7. Thanks to Toby Allsopp <toby@MI6.GEN.NZ> for
+  patches. 
+- Added functionality to create a basic certificate request. Also 
+  contributed by Toby Allsopp. 
+- Finally, AES!
+
+
+ Changes since 0.08
+--------------------
+- Replaced demo/Zope/ZServer/__init__.py with the correct version 
+  for Zope 2.6.0.
+- Added a sample starts.bat for ZServerSSL.
+- Incoporated a patch by prashanth@jibe.biz that handled the
+  new-in-Python-2.2.2 "strict" parameter for the various HTTP[S] connection 
+  classes in httplib.py. Thanks prashanth. This fixes M2Crypto's XMLRPC
+  support for Python 2.2.2. (Apparently it was working for Python 2.2.1.)
+- Incorporated some cosmetic patches from Adam Karpierz <karpierz@zope.pl>.
+  Thanks Adam.
+
+
+ Changes since 0.07 snapshot #3
+--------------------------------
+- Updated to SWIG 1.3.17.
+- Excluded IDEA.
+- Tested with OpenSSL 0.9.6h.
+- ZServerSSL rides again for Zope 2.6.0.
+- setup.py does!
+- Removed Makefiles for Windows and Unix. (Makefile.osx remains.)
+- Included in contrib/ Isaac Salzberg's application of Mihai Ibanescu's
+  patch that allows IIS interoperability thru an authenticating proxy.
+  Thanks Isaac.
+- Included in contrib/ patch by Dave Brueck <dave@pythonaprocrypha.com> 
+  that has smarter non-blocking behaviour. Thanks Dave.
+
+
+ Changes since 0.06
+-----------------------
+- test_ssl_win.py. (Requires Mark Hammond's Win32 extensions.)
+- Renamed demo/https to demo/medusa; updated Medusa to 2001 Jun release.
+- Improved _ssl.i's and M2Crypto.SSL.Connection's accept/connect methods.
+- M2Crypto.ftpslib for client-side FTP/TLS.
+- demo/medusa/ftps_server.py for server-side FTP/TLS.
+- Improved thread-safety.
+- Cleaned up echo client and servers.
+- Fixed missing import in m2urllib.
+- Fixed m2urllib to handle HTTP redirects.
+- Python 2.2 compatibility.
+- AuthCookie - secure authenticator cookies.
+
+
+ Changes since 0.05
+-----------------------
+- Handled the cases where Python callbacks raised exceptions.
+- Fixed a NULL-deref bug in _ssl.i which crashes Medusa https when IE 
+  or Opera comes a-calling.
+- ZServerSSL rides again - a more robust ZServerSSL for Zope 2.3.0.
+- Added the MIME type 'application/x-x509-ca-cert' to
+  demo/ssl/https_srv.py. This facilitates installing self-generated
+  certificates into your browser.
+- ZSmime and GuardedFile bundled.
+- Documentation! A HOWTO on operating your own CA.
+- Documentation! A HOWTO on S/MIME. Examples are in demo/smime.howto.
+- Python 2.1 compatibility.
+- Fixed demo/https/https_server.py's CPU-spinning. (As per ZServerSSL.)
+- Fixed m2urllib's unexpected eof - demo/ssl/urllib_cli.py now works.
+- Renamed xmlrpclib2.py to m2xmlrpclib.py.
+- Kludged SSL.ssl_dispatcher to do blocking connect()'s: see
+  demo/ssl/https_cli_async.py.
+- SWIG 1.3.6 does! Thanks to Keith Jackson <krjackson@lbl.gov>.
+
+
+ Changes since 0.04
+-----------------------
+- Fixed a silly reversed-logic bug in M2Crypto.SSL.Connection.setblocking().
+- Fixed yet more memory leaks. Thanks to Ray Suorsa <res@loudcloud.com>.
+- Build instructions for Borland BC++ 5.5 free compiler suite.
+- Bundles the June 2000 unencumbered release of Medusa.
+- SSL callback thread-safety. Thanks again to Ray Suorsa for insights and 
+  patches.
+- Renamed M2Crypto.M2Crypto to M2Crypto.m2 to prevent package/module loading 
+  confusion.
+- SSL.Session and a demo in demo/ssl/sess.py.
+- https_srv.py, an enhanced, https version of SimpleHTTPServer.py.
+- Interface change: SMIME.load_pkcs7_bio() is renamed 
+  SMIME.smime_load_pkcs7_bio(), similarly SMIME.load_pkcs7() to 
+  SMIME.smime_load_pkcs7(); these load PKCS7 objects generated by S/MIME.
+- Interface change: SMIME.load_pkcs7_bio() now loads a PKCS7 PEM file, i.e., a 
+  file of the format "-----BEGIN PKCS7-----". 
+- Works with both Python 2.0 and Python 1.5.2.
+- OpenSSL 0.9.6. (Possibly incompatible with earlier OpenSSL releases.)
+- Unit tests with PyUnit.
+- Improved C code:
+    =   Custom Python exceptions.
+    =   Diligent error checking.
+    =   Fixed memory leaks.
+- Renamed M2Crypto.urllib2 to M2Crypto.m2urllib.
+- HTTPS clients of Python 1.5.2's and Python 2.0's httplib and urllib. 
+
+
+ Changes since 0.03
+-----------------------
+- SSL certificate-based authentication with Python callback.
+- More robust SSL.Connection - raises exceptions, not dumps core.
+- Fixed (some) memory leaks and multiple-free()s.
+- Cleaned up EVP.HMAC and EVP.PKey.
+- More X.509 certificate manipulation.
+- An interface to create SSL sessions.
+- Unified SSL read() and write() for synchronous and asynchronous operation.
+- S/MIME and PKCS #7.
+- Integrated with OpenSSL 0.9.5.
+- Enhanced the PRNG interface. 
+
+
+ Changes since 0.02
+-----------------------
+1. Ephemeral DH for SSL.
+2. ThreadingSSLServer now does.
+3. XMLrpc over https.
+4. ZServerSSL for Zope 2.1.3.
+5. Encrypting monitor for Zope 2.1.3.
+6. Beginnings of PGP2 support.
+7. Replaced eval() calls with other (hopefully) safe ones.
+8. Miscellaneous enhancements and bug fixes.
+   
+
+ Changes since 0.01
+-----------------------
+1. Beginnings of SSL support.
+
+       For building servers, blocking i/o:
+               - An SSLServer modeled after SocketServer.
+               - A ForkingSSLServer that seems to work well.
+               - A ThreadingSSLServer that runs one thread at a time. (!) ;-)
+               
+       For building servers, nonblocking i/o:
+               - An ssl_dispatcher modeled after asyncore.dispatcher.
+
+       A HTTPS server based on Medusa.
+       
+       For client-side web programming:
+               - httpslib
+               - urllib2
+
+
+2. Support for some BIO objects.
+3. Reduced per-module name space pollution.
+4. Have Swig check for NULL pointers: reduced .i cut-&-paste.
+5. Standardise on MPINT for passing big integers between Python and OpenSSL.
+6. Removed MD5, SHA1, RIPEMD160. Just use EVP.MessageDigest.  
+7. Removed HMAC. Just use EVP.HMAC.  
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..f67fed4
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,226 @@
+====================
+ Installing M2Crypto
+====================
+
+:Maintainer: Heikki Toivonen
+:Web-Site: http://chandlerproject.org/Projects/MeTooCrypto
+
+.. contents::
+
+
+Pre-requisites
+----------------
+
+The following software packages are pre-requisites:
+
+- **Python 2.3 or newer**
+- **OpenSSL 0.9.7 or newer**
+- **SWIG 1.3.28 or newer**
+
+Note about OpenSSL versions early in the 0.9.7 series
+-----------------------------------------------------
+
+Early OpenSSL 0.9.7 versions require the __i386__ symbol to be defined.
+Uncomment this line in setup.py:
+
+  #'-D__i386__', # Uncomment for early OpenSSL 0.9.7 versions
+
+if you get this compile-time error:
+  
+  This openssl-devel package does not work your architecture?
+
+Note about Fedora Core -based Distributions
+-----------------------------------------------------
+
+Fedora Core (and RedHat, CentOS etc.) have made changes to OpenSSL
+configuration compared to many other Linux distributions. If you can not
+build M2Crypto normally, try the fedora_setup.sh script included with
+M2Crypto sources.
+
+Installing on Unix-like systems, including Cygwin
+-------------------------------------------------
+
+::
+
+    $ tar zxf m2crypto-<version>.tar.gz
+    $ cd m2crypto-<version>
+    $ python setup.py build
+    $ python setup.py install
+
+If you have installed setuptools you can also optionally run tests like this:
+
+    $ python setup.py test
+
+This assumes OpenSSL is installed in /usr. You can provide an alternate
+OpenSSL prefix location with --openssl option to build_ext command. Other
+commands accept standard options if you need them.
+
+Some distributions, like Fedora Core, package OpenSSL headers in a different
+location from OpenSSL itself. In that case you need to tell build_ext the
+additional include location with -I option.
+
+Differences when installing on Windows
+--------------------------------------
+
+Before building from source, you need to install OpenSSL's include files,
+import libraries and DLLs. By default setup.py assumes that OpenSSL include
+files are in ``c:\pkg\openssl\include``, and the import libraries 
+in ``c:\pkg\openssl\lib``. As with other platforms, you can specify a different
+OpenSSL location with --openssl option to build_ext command.
+
+Using OpenSSL 0.9.8 on Windows requires Python be built with applink.c 
+(add an include statement in python.c).  This is not a requirement for 
+Linux or MacOSX.  (applink.c is provided by OpenSSL.)
+
+
+MSVC++
+~~~~~~~~
+
+setup.py is already configured to work with MSVC++ by default.
+
+With MSVC++, the OpenSSL DLLs, as built, are named ``libeay32.dll``
+and ``ssleay32.dll``. Install these somewhere on your PATH; for example 
+in ``c:\bin``, together with ``openssl.exe``. 
+
+For MSVC++, the import libraries, as built by OpenSSL, are named
+``libeay32.lib`` and ``ssleay32.lib``.
+
+
+MINGW
+~~~~~~~
+
+.. NOTE:: 
+   The following instructions for building M2Crypto with MINGW are from 
+   M2Crypto 0.12. These instructions should continue to work for this release,
+   although I have not tested them.
+
+Read Sebastien Sauvage's webpage:
+
+     http://sebsauvage.net/python/mingw.html
+
+For mingw32, the OpenSSL import libraries are named ``libeay32.a`` and
+``libssl32.a``. You may need to edit setup.py file for these.
+
+You'll also need to create ``libpython2[123].a``, depending on your version
+of Python.
+
+OpenSSL DLLs for mingw32 are named ``libeay32.dll`` and ``libssl32.dll``.
+Install these somewhere on your PATH; for example in
+``c:\bin``, together with ``openssl.exe``.
+
+Build M2Crypto:
+
+    python setup.py build -cmingw32
+    python setup.py install
+
+
+BC++
+~~~~~~
+
+.. NOTE:: 
+   The following instructions for building M2Crypto with MSVC++ 6.0 and
+   BC++ 5.5 free compiler suite are from M2Crypto 0.10. These instructions
+   should continue to work for this release, although I have not tested
+   them.
+
+For BC++ these files are created from the MSVC++-built ones using the
+tool ``coff2omf.exe``. I call them ``libeay32_bc.lib`` and
+``ssleay32_bc.lib``, respectively. You will need to edit setup.py file 
+for these.
+
+You'll also need Python's import library, e.g., ``python22.lib``, to
+be the BC++-compatible version; i.e., create ``python22_bc.lib`` from
+``python22.lib``, save a copy of ``python22.lib`` (as ``python22_vc.lib``,
+say), then rename ``python22_bc.lib`` to ``python22.lib``.
+
+
+Now you are ready to build M2Crypto. Do one of the following::
+
+    python setup.py build
+    python setup.py build -cbcpp
+
+Then,
+
+::
+
+    python setup.py install
+
+
+MacOSX
+------
+
+Follow the standard instructions to build and install M2Crypto.  
+However, should you encounter difficulties, you may want to consider 
+the following possibilities.
+
+  - Distutils for Python 2.5 now provides support for universal 
+    builds (ppc and i386) and Distutils requires a recent version 
+    of Xcode.  See http://developer.apple.com/tools/download/
+
+  - OpenSSL 0.9.7l gets installed in /usr with Apple's Security 
+    Update 2006-007.  If you need features in OpenSSL 0.9.8, you 
+    should consider installing 0.9.8 in /usr/local.  The commands 
+    are:
+    
+      OpenSSL:
+        ./config shared --prefix=/usr/local
+        make
+        make test
+        sudo make install     [or... install_sw]
+        
+      M2Crypto:
+        python setup.py build build_ext --openssl=/usr/local
+        sudo python setup.py install build_ext --openssl=/usr/local
+
+To make Universal builds, you will need to uncomment a line in setup.py:
+
+  extra_link_args = ['-Wl,-search_paths_first'],
+  
+If that does not work, here is what Marc Hedlund was able to get working:
+
+  First, download OpenSSL 0.9.8d and unpack it.  Edit the OpenSSL Makefiles 
+  per PROBLEMS.  Then:
+
+    ./config no-shared no-asm --prefix=/usr/local
+    make
+    make test
+    sudo make install
+    make clean
+    ./Configure no-shared no-asm --prefix=/usr/local darwin-ppc-cc
+    make build_libs "CC=cc -arch ppc"
+    lipo -info lib*
+    mkdir -p build/ppc
+    mv lib* build/ppc
+    make clean
+    ./Configure no-shared no-asm --prefix=/usr/local darwin-i386-cc
+    make build_libs "CC=cc -arch i386"
+    lipo -info lib*
+    mkdir -p build/i386
+    mv lib* build/i386/
+    /bin/ls -1 build/i386/ > libnames.tmp
+    mkdir universal
+
+  Create a script in the OpenSSL directory called 'make_universal', with these
+  contents:
+
+    #!/bin/sh
+    for lib in `cat libnames.tmp`; do
+     lipo -create build/*/$lib -output universal/$lib
+    done
+    exit 0
+
+  Then:
+
+    sh make_universal
+    lipo -info universal/lib*
+    sudo cp universal/lib* /usr/local/lib
+    lipo -info /usr/local/lib/lib{crypto,ssl}*
+    cd ../m2crypto-0.17
+
+  Then edit the m2crypto setup.py and uncomment the extra_link_args line at 
+  the end.
+
+    python setup.py build build_ext --openssl=/usr/local
+    sudo python setup.py install build_ext --openssl=/usr/local
+
+
diff --git a/LICENCE b/LICENCE
new file mode 100644 (file)
index 0000000..d2f636f
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,26 @@
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2004-2006 Open Source Applications Foundation. 
+All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. 
+All rights reserved.
+
+Copyright (c) 2008-2010 Heikki Toivonen. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation.
+
+THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/M2Crypto.egg-info/PKG-INFO b/M2Crypto.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..3972df5
--- /dev/null
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: M2Crypto
+Version: 0.21.1
+Summary: M2Crypto: A Python crypto and SSL toolkit
+Home-page: http://chandlerproject.org/Projects/MeTooCrypto
+Author: Heikki Toivonen
+Author-email: heikki@osafoundation.org
+License: BSD-style license
+Description: M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+        DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+        functionality to implement clients and servers; HTTPS extensions to Python's
+        httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+        session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+        server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+        used to provide SSL for Twisted.
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Security :: Cryptography
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/M2Crypto.egg-info/SOURCES.txt b/M2Crypto.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..dffdb58
--- /dev/null
@@ -0,0 +1,354 @@
+CHANGES
+INSTALL
+LICENCE
+README
+epydoc.conf
+fedora_setup.sh
+pack.py
+setup.cfg
+setup.py
+M2Crypto/ASN1.py
+M2Crypto/AuthCookie.py
+M2Crypto/BIO.py
+M2Crypto/BN.py
+M2Crypto/DH.py
+M2Crypto/DSA.py
+M2Crypto/EC.py
+M2Crypto/EVP.py
+M2Crypto/Engine.py
+M2Crypto/Err.py
+M2Crypto/RC4.py
+M2Crypto/RSA.py
+M2Crypto/Rand.py
+M2Crypto/SMIME.py
+M2Crypto/X509.py
+M2Crypto/__init__.py
+M2Crypto/callback.py
+M2Crypto/ftpslib.py
+M2Crypto/httpslib.py
+M2Crypto/m2.py
+M2Crypto/m2urllib.py
+M2Crypto/m2urllib2.py
+M2Crypto/m2xmlrpclib.py
+M2Crypto/threading.py
+M2Crypto/util.py
+M2Crypto.egg-info/PKG-INFO
+M2Crypto.egg-info/SOURCES.txt
+M2Crypto.egg-info/dependency_links.txt
+M2Crypto.egg-info/top_level.txt
+M2Crypto/PGP/PublicKey.py
+M2Crypto/PGP/PublicKeyRing.py
+M2Crypto/PGP/RSA.py
+M2Crypto/PGP/__init__.py
+M2Crypto/PGP/constants.py
+M2Crypto/PGP/packet.py
+M2Crypto/SSL/Checker.py
+M2Crypto/SSL/Cipher.py
+M2Crypto/SSL/Connection.py
+M2Crypto/SSL/Context.py
+M2Crypto/SSL/SSLServer.py
+M2Crypto/SSL/Session.py
+M2Crypto/SSL/TwistedProtocolWrapper.py
+M2Crypto/SSL/__init__.py
+M2Crypto/SSL/cb.py
+M2Crypto/SSL/ssl_dispatcher.py
+M2Crypto/SSL/timeout.py
+SWIG/Makefile
+SWIG/Makefile.mw
+SWIG/Makefile.osx
+SWIG/_aes.i
+SWIG/_asn1.i
+SWIG/_bio.i
+SWIG/_bn.i
+SWIG/_dh.i
+SWIG/_dsa.i
+SWIG/_ec.i
+SWIG/_engine.i
+SWIG/_evp.i
+SWIG/_lib.h
+SWIG/_lib.i
+SWIG/_m2crypto.def
+SWIG/_m2crypto.i
+SWIG/_objects.i
+SWIG/_pkcs7.i
+SWIG/_rand.i
+SWIG/_rc4.i
+SWIG/_rsa.i
+SWIG/_ssl.i
+SWIG/_threads.i
+SWIG/_util.i
+SWIG/_x509.i
+contrib/README
+contrib/SimpleX509create.README
+contrib/SimpleX509create.py
+contrib/dave.README
+contrib/dave.patch
+contrib/dispatcher.README
+contrib/dispatcher.py
+contrib/isaac.README
+contrib/isaac.httpslib.py
+contrib/m2crypto.spec
+contrib/smimeplus.README
+contrib/smimeplus.py
+demo/bio_mem_rw.py
+demo/dhtest.py
+demo/dsa1024pvtkey.pem
+demo/dsa_bench.py
+demo/dsatest.pem
+demo/dsatest.py
+demo/rsa.priv.pem
+demo/rsa.priv0.pem
+demo/rsa.pub.pem
+demo/rsa1024pvtkey.pem
+demo/rsa_bench.py
+demo/rsatest.py
+demo/CipherSaber/CipherSaber.py
+demo/CipherSaber/cstest1.cs1
+demo/Zope/ca.pem
+demo/Zope/dh1024.pem
+demo/Zope/server.pem
+demo/Zope/starts
+demo/Zope/starts.bat
+demo/Zope/z2s.py
+demo/Zope/z2s.py.diff
+demo/Zope/ZServer/HTTPS_Server.py
+demo/Zope/ZServer/__init__.py
+demo/Zope/ZServer/medusa/ftps_server.py
+demo/Zope/ZServer/medusa/https_server.py
+demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py
+demo/Zope/lib/python/Products/GuardedFile/README.txt
+demo/Zope/lib/python/Products/GuardedFile/TODO.txt
+demo/Zope/lib/python/Products/GuardedFile/__init__.py
+demo/Zope/lib/python/Products/GuardedFile/add.dtml
+demo/Zope/lib/python/Products/GuardedFile/refresh.txt
+demo/Zope/lib/python/Products/GuardedFile/version.txt
+demo/Zope/lib/python/Products/ZSmime/README.txt
+demo/Zope/lib/python/Products/ZSmime/SmimeTag.py
+demo/Zope/lib/python/Products/ZSmime/__init__.py
+demo/Zope/lib/python/Products/ZSmime/version.txt
+demo/Zope/utilities/x509_user.py
+demo/Zope27/INSTALL.txt
+demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py
+demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch
+demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch
+demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch
+demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py
+demo/Zope27/instance_home/README.txt.patch
+demo/Zope27/instance_home/etc/zope.conf.patch
+demo/Zope27/instance_home/ssl/ca.pem
+demo/Zope27/instance_home/ssl/dh1024.pem
+demo/Zope27/instance_home/ssl/server.pem
+demo/ZopeX3/INSTALL.txt
+demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch
+demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py
+demo/ZopeX3/instance_home/etc/zope.conf.patch
+demo/ZopeX3/instance_home/ssl/ca.pem
+demo/ZopeX3/instance_home/ssl/dh1024.pem
+demo/ZopeX3/instance_home/ssl/server.pem
+demo/ec/ecdhtest.py
+demo/ec/ecdsa_bench.py
+demo/ec/ecdsatest.pem
+demo/ec/ecdsatest.py
+demo/ec/secp160r1pvtkey.pem
+demo/https.howto/ca.pem
+demo/https.howto/dh1024.pem
+demo/https.howto/get_https.py
+demo/https.howto/https_cli.py
+demo/https.howto/orig_https_srv.py
+demo/https.howto/server.pem
+demo/medusa/00_README
+demo/medusa/START.py
+demo/medusa/START_xmlrpc.py
+demo/medusa/asynchat.py
+demo/medusa/asyncore.py
+demo/medusa/auth_handler.py
+demo/medusa/ca.pem
+demo/medusa/counter.py
+demo/medusa/default_handler.py
+demo/medusa/dh1024.pem
+demo/medusa/filesys.py
+demo/medusa/ftp_server.py
+demo/medusa/ftps_server.py
+demo/medusa/http_date.py
+demo/medusa/http_server.py
+demo/medusa/https_server.py
+demo/medusa/index.html
+demo/medusa/logger.py
+demo/medusa/m_syslog.py
+demo/medusa/medusa_gif.py
+demo/medusa/mime_type_table.py
+demo/medusa/poison_handler.py
+demo/medusa/producers.py
+demo/medusa/put_handler.py
+demo/medusa/redirecting_handler.py
+demo/medusa/server.pem
+demo/medusa/status_handler.py
+demo/medusa/virtual_handler.py
+demo/medusa/xmlrpc_handler.py
+demo/medusa054/00_README
+demo/medusa054/START.py
+demo/medusa054/START_xmlrpc.py
+demo/medusa054/ca.pem
+demo/medusa054/counter.py
+demo/medusa054/default_handler.py
+demo/medusa054/dh1024.pem
+demo/medusa054/filesys.py
+demo/medusa054/ftp_server.py
+demo/medusa054/ftps_server.py
+demo/medusa054/http_date.py
+demo/medusa054/http_server.py
+demo/medusa054/https_server.py
+demo/medusa054/index.html
+demo/medusa054/logger.py
+demo/medusa054/m_syslog.py
+demo/medusa054/medusa_gif.py
+demo/medusa054/poison_handler.py
+demo/medusa054/producers.py
+demo/medusa054/server.pem
+demo/medusa054/status_handler.py
+demo/medusa054/xmlrpc_handler.py
+demo/perf/memio.py
+demo/perf/sha1.py
+demo/pgp/pgpstep.py
+demo/pgp/pubring.pgp
+demo/pgp/secring.pgp
+demo/pkcs7/pkcs7-thawte.pem
+demo/pkcs7/test.py
+demo/smime/README
+demo/smime/ca.pem
+demo/smime/clear.p7
+demo/smime/client.p12
+demo/smime/client.pem
+demo/smime/client2.pem
+demo/smime/m2.se.p7
+demo/smime/ns.p7
+demo/smime/ns.se.p7
+demo/smime/opaque.p7
+demo/smime/sendsmime.py
+demo/smime/test.py
+demo/smime/unsmime.py
+demo/smime.howto/README
+demo/smime.howto/decrypt.py
+demo/smime.howto/dv.py
+demo/smime.howto/encrypt.p7
+demo/smime.howto/encrypt.py
+demo/smime.howto/recipient.pem
+demo/smime.howto/recipient_key.pem
+demo/smime.howto/se.p7
+demo/smime.howto/se.py
+demo/smime.howto/sendsmime.py
+demo/smime.howto/sign.p7
+demo/smime.howto/sign.py
+demo/smime.howto/signer.pem
+demo/smime.howto/signer_key.pem
+demo/smime.howto/verify.py
+demo/ssl/README
+demo/ssl/c.py
+demo/ssl/c_bio.py
+demo/ssl/ca.der
+demo/ssl/ca.pem
+demo/ssl/client.p12
+demo/ssl/client.pem
+demo/ssl/dh1024.pem
+demo/ssl/echo-eg.py
+demo/ssl/echo.py
+demo/ssl/echod-async.py
+demo/ssl/echod-eg1.py
+demo/ssl/echod-forking.py
+demo/ssl/echod-iterative.py
+demo/ssl/echod-thread.py
+demo/ssl/echod-threading.py
+demo/ssl/echod_lib.py
+demo/ssl/ftp_tls.py
+demo/ssl/http_cli_20.py
+demo/ssl/https_cli.py
+demo/ssl/https_cli_async.py
+demo/ssl/https_srv.py
+demo/ssl/myapp.py
+demo/ssl/s_client.py
+demo/ssl/s_server.py
+demo/ssl/server.pem
+demo/ssl/server3.py
+demo/ssl/sess.py
+demo/ssl/sess2.py
+demo/ssl/sess2.ssldump.out
+demo/ssl/socklib.py
+demo/ssl/somelib.py
+demo/ssl/ss.py
+demo/ssl/twistedsslclient.py
+demo/ssl/twistedsslserver.py
+demo/ssl/xmlrpc_cli.py
+demo/ssl/xmlrpc_srv.py
+demo/tinderbox/build_lib.py
+demo/tinderbox/killableprocess.py
+demo/tinderbox/slave.py
+demo/tinderbox/winprocess.py
+demo/x509/ca.py
+demo/x509/certdata2pem.py
+demo/x509/client2.pem
+demo/x509/demo1.py
+demo/x509/proxy_destroy.py
+demo/x509/proxy_info.py
+demo/x509/proxy_init.py
+demo/x509/proxylib.py
+demo/x509/server-expired.pem
+demo/x509/server.pem
+demo/x509/x509auth.py
+doc/ZServerSSL-HOWTO.html
+doc/howto.ca.html
+doc/howto.smime.html
+doc/howto.ssl.html
+tests/README
+tests/__init__.py
+tests/alltests.py
+tests/ca.pem
+tests/der_encoded_seq.b64
+tests/dhparams.pem
+tests/dsa.param.pem
+tests/dsa.priv.pem
+tests/dsa.pub.pem
+tests/ec.priv.pem
+tests/ec.pub.pem
+tests/fips.py
+tests/long_serial_cert.pem
+tests/pubring.pgp
+tests/recipient.pem
+tests/recipient_key.pem
+tests/rsa.priv.pem
+tests/rsa.priv2.pem
+tests/rsa.pub.pem
+tests/server.pem
+tests/signer.pem
+tests/signer_key.pem
+tests/test_asn1.py
+tests/test_authcookie.py
+tests/test_bio.py
+tests/test_bio_file.py
+tests/test_bio_iobuf.py
+tests/test_bio_membuf.py
+tests/test_bio_ssl.py
+tests/test_bn.py
+tests/test_dh.py
+tests/test_dsa.py
+tests/test_ec_curves.py
+tests/test_ecdh.py
+tests/test_ecdsa.py
+tests/test_engine.py
+tests/test_evp.py
+tests/test_obj.py
+tests/test_pgp.py
+tests/test_rand.py
+tests/test_rc4.py
+tests/test_rsa.py
+tests/test_smime.py
+tests/test_ssl.py
+tests/test_ssl_offline.py
+tests/test_ssl_win.py
+tests/test_threading.py
+tests/test_x509.py
+tests/thawte.pem
+tests/x509.der
+tests/x509.pem
\ No newline at end of file
diff --git a/M2Crypto.egg-info/dependency_links.txt b/M2Crypto.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/M2Crypto.egg-info/top_level.txt b/M2Crypto.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..fa4a704
--- /dev/null
@@ -0,0 +1 @@
+M2Crypto
diff --git a/M2Crypto/ASN1.py b/M2Crypto/ASN1.py
new file mode 100644 (file)
index 0000000..09d9e9f
--- /dev/null
@@ -0,0 +1,192 @@
+"""
+M2Crypto wrapper for OpenSSL ASN1 API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2005 OSAF. All Rights Reserved.
+"""
+
+import time, datetime
+
+import BIO
+import m2
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC  = MBSTRING_FLAG | 1
+MBSTRING_BMP  = MBSTRING_FLAG | 2
+
+
+class ASN1_Integer:
+
+    m2_asn1_integer_free = m2.asn1_integer_free
+
+    def __init__(self, asn1int, _pyfree=0):
+        self.asn1int = asn1int
+        self._pyfree = _pyfree
+        
+    def __cmp__(self, other):
+        return m2.asn1_integer_cmp(self.asn1int, other.asn1int)
+
+    def __del__(self):
+        if self._pyfree:
+            self.m2_asn1_integer_free(self.asn1int)
+
+
+class ASN1_String:
+        
+    m2_asn1_string_free = m2.asn1_string_free
+    
+    def __init__(self, asn1str, _pyfree=0):
+        self.asn1str = asn1str
+        self._pyfree = _pyfree
+
+    def __str__(self):
+        buf = BIO.MemoryBuffer()
+        m2.asn1_string_print( buf.bio_ptr(), self.asn1str )
+        return buf.read_all()
+
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_asn1_string_free(self.asn1str)
+                                                                                                        
+    def _ptr(self):
+        return self.asn1str
+    
+    def as_text(self, flags=0):
+        buf = BIO.MemoryBuffer()
+        m2.asn1_string_print_ex( buf.bio_ptr(), self.asn1str, flags)
+        return buf.read_all()
+
+
+class ASN1_Object:
+    
+    m2_asn1_object_free = m2.asn1_object_free
+
+    def __init__(self, asn1obj, _pyfree=0):
+        self.asn1obj = asn1obj
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if self._pyfree:
+            self.m2_asn1_object_free(self.asn1obj)
+
+    def _ptr(self):
+        return self.asn1obj
+
+class _UTC(datetime.tzinfo):
+    def tzname(self, dt):
+        return "UTC"
+    
+    def dst(self, dt):
+        return datetime.timedelta(0)
+    
+    def utcoffset(self, dt):
+        return datetime.timedelta(0)
+
+    def __repr__(self):
+        return "<Timezone: %s>" % self.tzname(None)
+UTC = _UTC()
+
+
+class LocalTimezone(datetime.tzinfo):
+    """ Localtimezone from datetime manual """
+    def __init__(self):
+        self._stdoffset = datetime.timedelta(seconds = -time.timezone)
+        if time.daylight:
+            self._dstoffset = datetime.timedelta(seconds = -time.altzone)
+        else:
+            self._dstoffset = self._stdoffset
+        self._dstdiff = self._dstoffset - self._stdoffset
+
+
+    def utcoffset(self, dt):
+        if self._isdst(dt):
+            return self._dstoffset
+        else:
+            return self._stdoffset
+
+    def dst(self, dt):
+        if self._isdst(dt):
+            return self._dstdiff
+        else:
+            return datetime.timedelta(0)
+
+    def tzname(self, dt):
+        return time.tzname[self._isdst(dt)]
+
+    def _isdst(self, dt):
+        tt = (dt.year, dt.month, dt.day,
+              dt.hour, dt.minute, dt.second,
+              dt.weekday(), 0, -1)
+        stamp = time.mktime(tt)
+        tt = time.localtime(stamp)
+        return tt.tm_isdst > 0
+
+
+class ASN1_UTCTIME:
+    _ssl_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
+                   "Sep", "Oct", "Nov", "Dec"]
+    m2_asn1_utctime_free = m2.asn1_utctime_free
+
+    def __init__(self, asn1_utctime=None, _pyfree=0):
+        if asn1_utctime is not None:
+            assert m2.asn1_utctime_type_check(asn1_utctime), "'asn1_utctime' type error'"
+            self.asn1_utctime = asn1_utctime
+            self._pyfree = _pyfree
+        else:
+            self.asn1_utctime = m2.asn1_utctime_new ()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_asn1_utctime_free(self.asn1_utctime)
+            
+    def __str__(self):
+        assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+        buf = BIO.MemoryBuffer()
+        m2.asn1_utctime_print( buf.bio_ptr(), self.asn1_utctime )
+        return buf.read_all()
+
+    def _ptr(self):
+        assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+        return self.asn1_utctime
+
+    def set_string (self, string):
+        """
+        Set time from UTC string.
+        """
+        assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+        return m2.asn1_utctime_set_string( self.asn1_utctime, string )
+
+    def set_time (self, time):
+        """
+        Set time from seconds since epoch (long).
+        """
+        assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+        return m2.asn1_utctime_set( self.asn1_utctime, time )
+
+    def get_datetime(self):
+        date = str(self)
+
+        timezone = None
+        if ' ' not in date:
+            raise ValueError("Invalid date: %s" % date)
+        month, rest = date.split(' ', 1)
+        if month not in self._ssl_months:
+            raise ValueError("Invalid date %s: Invalid month: %s" % (date, m))
+        if rest.endswith(' GMT'):
+            timezone = UTC
+            rest = rest[:-4]
+        tm = list(time.strptime(rest, "%d %H:%M:%S %Y"))[:6]
+        tm[1] = self._ssl_months.index(month) + 1
+        tm.append(0)
+        tm.append(timezone)
+        return datetime.datetime(*tm)
+
+    def set_datetime(self, date):
+        local = LocalTimezone()
+        if date.tzinfo is None:
+            date = date.replace(tzinfo=local)
+        date = date.astimezone(local)
+        return self.set_time(int(time.mktime(date.timetuple())))
diff --git a/M2Crypto/AuthCookie.py b/M2Crypto/AuthCookie.py
new file mode 100644 (file)
index 0000000..d401708
--- /dev/null
@@ -0,0 +1,114 @@
+"""Secure Authenticator Cookies
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+import Rand, m2
+
+# Python. Cookie is bundled with Python 2.x. 
+import Cookie, binascii, re, time
+
+
+_MIX_FORMAT = 'exp=%s&data=%s&digest='
+_MIX_RE     = re.compile('exp=(\d+\.\d+)&data=(.+)&digest=(\S*)')
+
+def mix(expiry, data, format=_MIX_FORMAT):
+    return format % (repr(expiry), data)
+
+def unmix(dough, regex=_MIX_RE):
+    mo = regex.match(dough)
+    if mo:
+        return float(mo.group(1)), mo.group(2)
+    else:
+        return None
+
+def unmix3(dough, regex=_MIX_RE):
+    mo = regex.match(dough)
+    if mo:
+        return float(mo.group(1)), mo.group(2), mo.group(3)
+    else:
+        return None
+
+
+_TOKEN = '_M2AUTH_'
+
+class AuthCookieJar:
+
+    _keylen = 20
+
+    def __init__(self):
+        self._key = Rand.rand_bytes(self._keylen)
+    
+    def _hmac(self, key, data):
+        return binascii.b2a_base64(m2.hmac(key, data, m2.sha1()))[:-1]
+        
+    def makeCookie(self, expiry, data):
+        dough = mix(expiry, data)
+        return AuthCookie(expiry, data, dough, self._hmac(self._key, dough))
+
+    def isGoodCookie(self, cookie):
+        assert isinstance(cookie, AuthCookie)
+        if cookie.isExpired():
+            return 0
+        c = self.makeCookie(cookie._expiry, cookie._data)
+        return (c._expiry == cookie._expiry) \
+            and (c._data == cookie._data) \
+            and (c._mac == cookie._mac) \
+            and (c.output() == cookie.output())
+
+    def isGoodCookieString(self, cookie_str):
+        c = Cookie.SmartCookie()        
+        c.load(cookie_str)
+        if not c.has_key(_TOKEN):
+            return 0
+        undough = unmix3(c[_TOKEN].value)
+        if undough is None:
+            return 0
+        exp, data, mac = undough
+        c2 = self.makeCookie(exp, data)
+        return (not c2.isExpired()) and (c2._mac == mac)
+
+
+class AuthCookie:
+    
+    def __init__(self, expiry, data, dough, mac):
+        self._expiry = expiry
+        self._data = data
+        self._mac = mac
+        self._cookie = Cookie.SmartCookie()
+        self._cookie[_TOKEN] = '%s%s' % (dough, mac)
+        self._name = '%s%s' % (dough, mac)  # XXX WebKit only.
+
+    def expiry(self):
+        """Return the cookie's expiry time."""
+        return self._expiry
+
+    def data(self):
+        """Return the data portion of the cookie."""
+        return self._data
+
+    def mac(self):
+        """Return the cookie's MAC."""
+        return self._mac
+
+    def output(self):
+        """Return the cookie's output in "Set-Cookie" format."""
+        return self._cookie.output()
+
+    def value(self):
+        """Return the cookie's output minus the "Set-Cookie: " portion.
+        """
+        return self._cookie[_TOKEN].value
+
+    def isExpired(self):
+        """Return 1 if the cookie has expired, 0 otherwise."""
+        return (time.time() > self._expiry)
+
+    # XXX Following methods are for WebKit only. These should be pushed 
+    # to WKAuthCookie.
+    def name(self):
+        return self._name
+
+    def headerValue(self):
+        return self.value()
+
diff --git a/M2Crypto/BIO.py b/M2Crypto/BIO.py
new file mode 100644 (file)
index 0000000..11dbce4
--- /dev/null
@@ -0,0 +1,286 @@
+"""M2Crypto wrapper for OpenSSL BIO API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import m2 
+
+# Deprecated
+from m2 import bio_do_handshake as bio_do_ssl_handshake
+
+from cStringIO import StringIO
+
+class BIOError(Exception): pass
+
+m2.bio_init(BIOError)
+
+class BIO:
+
+    """Abstract object interface to the BIO API."""
+
+    m2_bio_free = m2.bio_free
+
+    def __init__(self, bio=None, _pyfree=0, _close_cb=None):
+        self.bio = bio
+        self._pyfree = _pyfree
+        self._close_cb = _close_cb
+        self.closed = 0
+        self.write_closed = 0
+        
+    def __del__(self):
+        if self._pyfree:
+            self.m2_bio_free(self.bio)
+
+    def _ptr(self):
+        return self.bio
+
+    # Deprecated.
+    bio_ptr = _ptr
+
+    def fileno(self):
+        return m2.bio_get_fd(self.bio)
+
+    def readable(self):
+        return not self.closed
+
+    def read(self, size=None):
+        if not self.readable():
+            raise IOError, 'cannot read'
+        if size is None:
+            buf = StringIO()
+            while 1:
+                data = m2.bio_read(self.bio, 4096)
+                if not data: break
+                buf.write(data)
+            return buf.getvalue()
+        elif size == 0:
+            return ''
+        elif size < 0:
+            raise ValueError, 'read count is negative'
+        else:
+            return m2.bio_read(self.bio, size)
+
+    def readline(self, size=4096):
+        if not self.readable():
+            raise IOError, 'cannot read'
+        buf = m2.bio_gets(self.bio, size)
+        return buf
+
+    def readlines(self, sizehint='ignored'):
+        if not self.readable():
+            raise IOError, 'cannot read'
+        lines=[]
+        while 1:
+            buf=m2.bio_gets(self.bio, 4096)
+            if buf is None:
+                break
+            lines.append(buf)
+        return lines
+
+    def writeable(self):
+        return (not self.closed) and (not self.write_closed)
+       
+    def write(self, data):
+        if not self.writeable():
+            raise IOError, 'cannot write'
+        return m2.bio_write(self.bio, data)
+
+    def write_close(self):
+        self.write_closed = 1
+
+    def flush(self):
+        m2.bio_flush(self.bio)
+
+    def reset(self):
+        """
+        Sets the bio to its initial state
+        """
+        return m2.bio_reset(self.bio)
+
+    def close(self):
+        self.closed = 1
+        if self._close_cb:
+            self._close_cb()
+
+    def should_retry(self):
+        """
+        Can the call be attempted again, or was there an error
+        ie do_handshake 
+       
+        """
+        return m2.bio_should_retry(self.bio) 
+
+    def should_read(self):
+        """
+        Returns whether the cause of the condition is the bio
+        should read more data
+        """
+        return m2.bio_should_read(self.bio)
+    
+    def should_write(self):
+        """
+        Returns whether the cause of the condition is the bio
+        should write more data
+        """
+        return m2.bio_should_write(self.bio)
+    
+class MemoryBuffer(BIO):
+
+    """
+    Object interface to BIO_s_mem. 
+    
+    Empirical testing suggests that this class performs less well than cStringIO, 
+    because cStringIO is implemented in C, whereas this class is implemented in 
+    Python. Thus, the recommended practice is to use cStringIO for regular work and 
+    convert said cStringIO object to a MemoryBuffer object only when necessary.
+    """
+
+    def __init__(self, data=None):
+        BIO.__init__(self)
+        self.bio = m2.bio_new(m2.bio_s_mem())
+        self._pyfree = 1
+        if data is not None:
+            m2.bio_write(self.bio, data)
+
+    def __len__(self):
+        return m2.bio_ctrl_pending(self.bio)
+
+    def read(self, size=0):
+        if not self.readable():
+            raise IOError, 'cannot read'
+        if size:
+            return m2.bio_read(self.bio, size)
+        else:
+            return m2.bio_read(self.bio, m2.bio_ctrl_pending(self.bio))
+            
+    # Backwards-compatibility.
+    getvalue = read_all = read
+
+    def write_close(self):
+        self.write_closed = 1
+        m2.bio_set_mem_eof_return(self.bio, 0)
+
+    close = write_close
+
+
+class File(BIO):
+
+    """
+    Object interface to BIO_s_fp. 
+    
+    This class interfaces Python to OpenSSL functions that expect BIO *. For
+    general file manipulation in Python, use Python's builtin file object.
+    """
+
+    def __init__(self, pyfile, close_pyfile=1):
+        BIO.__init__(self, _pyfree=1)
+        self.pyfile = pyfile
+        self.close_pyfile = close_pyfile
+        self.bio = m2.bio_new_fp(pyfile, 0)
+
+    def close(self):
+        self.closed = 1
+        if self.close_pyfile:
+            self.pyfile.close()
+
+def openfile(filename, mode='rb'):
+    return File(open(filename, mode))
+
+
+class IOBuffer(BIO):
+
+    """
+    Object interface to BIO_f_buffer. 
+    
+    Its principal function is to be BIO_push()'ed on top of a BIO_f_ssl, so
+    that makefile() of said underlying SSL socket works.
+    """
+
+    m2_bio_pop = m2.bio_pop
+    m2_bio_free = m2.bio_free
+
+    def __init__(self, under_bio, mode='rwb', _pyfree=1):
+        BIO.__init__(self, _pyfree=_pyfree)
+        self.io = m2.bio_new(m2.bio_f_buffer())
+        self.bio = m2.bio_push(self.io, under_bio._ptr())
+        # This reference keeps the underlying BIO alive while we're not closed.
+        self._under_bio = under_bio 
+        if 'w' in mode:
+            self.write_closed = 0
+        else:
+            self.write_closed = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_bio_pop(self.bio)
+        self.m2_bio_free(self.io)
+
+    def close(self):
+        BIO.close(self)
+
+
+class CipherStream(BIO):
+
+    """
+    Object interface to BIO_f_cipher.
+    """
+
+    SALT_LEN = m2.PKCS5_SALT_LEN
+
+    m2_bio_pop = m2.bio_pop
+    m2_bio_free = m2.bio_free
+
+    def __init__(self, obio):
+        BIO.__init__(self, _pyfree=1)
+        self.obio = obio
+        self.bio = m2.bio_new(m2.bio_f_cipher())
+        self.closed = 0
+        
+    def __del__(self):
+        if not getattr(self, 'closed', 1):
+            self.close()
+
+    def close(self):
+        self.m2_bio_pop(self.bio)
+        self.m2_bio_free(self.bio)
+        self.closed = 1
+        
+    def write_close(self):
+        self.obio.write_close()
+
+    def set_cipher(self, algo, key, iv, op):
+        cipher = getattr(m2, algo, None)
+        if cipher is None:
+            raise ValueError, ('unknown cipher', algo)
+        m2.bio_set_cipher(self.bio, cipher(), key, iv, op) 
+        m2.bio_push(self.bio, self.obio._ptr())
+
+
+class SSLBio(BIO):
+    """
+    Object interface to BIO_f_ssl 
+    """
+    def __init__(self, _pyfree=1):
+        BIO.__init__(self, _pyfree)
+        self.bio = m2.bio_new(m2.bio_f_ssl())
+        self.closed = 0
+
+   
+    def set_ssl(self, conn, close_flag=m2.bio_noclose):
+        """
+        Sets the bio to the SSL pointer which is
+        contained in the connection object.  
+        """
+        self._pyfree = 0 
+        m2.bio_set_ssl(self.bio, conn.ssl, close_flag)
+        if close_flag == m2.bio_noclose:
+            conn.set_ssl_close_flag(m2.bio_close)
+       
+    def do_handshake(self):
+        """
+        Do the handshake.
+        
+        Return 1 if the handshake completes
+        Return 0 or a negative number if there is a problem
+        """
+        return m2.bio_do_handshake(self.bio)
+
diff --git a/M2Crypto/BN.py b/M2Crypto/BN.py
new file mode 100755 (executable)
index 0000000..ec741c0
--- /dev/null
@@ -0,0 +1,47 @@
+"""
+M2Crypto wrapper for OpenSSL BN (BIGNUM) API.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import m2
+
+def rand(bits, top=-1, bottom=0):
+    """
+    Generate cryptographically strong random number.
+    
+    @param bits:   Length of random number in bits.
+    @param top:    If -1, the most significant bit can be 0. If 0, the most
+                   significant bit is 1, and if 1, the two most significant
+                   bits will be 1.
+    @param bottom: If bottom is true, the number will be odd.
+    """
+    return m2.bn_rand(bits, top, bottom)
+
+
+def rand_range(range):
+    """
+    Generate a random number in a range.
+    
+    @param range: Upper limit for range.
+    @return:      A random number in the range [0, range)
+    """
+    return m2.bn_rand_range(range)
+
+
+def randfname(length):
+    """
+    Return a random filename, which is simply a string where all
+    the characters are from the set [a-zA-Z0-9].
+
+    @param length: Length of filename to return.
+    @type length:  int
+    @return:       random filename string
+    """
+    letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890'
+    lettersLen = len(letters)
+    fname = []
+    for x in range(length):
+        fname += [letters[m2.bn_rand_range(lettersLen)]]
+        
+    return ''.join(fname)
diff --git a/M2Crypto/DH.py b/M2Crypto/DH.py
new file mode 100644 (file)
index 0000000..4d454ef
--- /dev/null
@@ -0,0 +1,96 @@
+"""M2Crypto wrapper for OpenSSL DH API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from util import genparam_callback
+import BIO, Err, m2
+
+class DHError(Exception): pass
+
+m2.dh_init(DHError)
+
+class DH:
+
+    """
+    Object interface to the Diffie-Hellman key exchange
+    protocol.
+    """
+
+    m2_dh_free = m2.dh_free
+
+    def __init__(self, dh, _pyfree=0):
+        assert m2.dh_type_check(dh)
+        self.dh = dh
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_dh_free(self.dh)
+
+    def __len__(self):
+        assert m2.dh_type_check(self.dh), "'dh' type error"
+        return m2.dh_size(self.dh)
+
+    def __getattr__(self, name):
+        if name in ('p', 'g', 'pub', 'priv'):
+            method = getattr(m2, 'dh_get_%s' % (name,))
+            assert m2.dh_type_check(self.dh), "'dh' type error"
+            return method(self.dh)
+        else:
+            raise AttributeError
+
+    def __setattr__(self, name, value):
+        if name in ('p', 'g'):
+            raise DHError, 'set (p, g) via set_params()'
+        elif name in ('pub','priv'):
+            raise DHError, 'generate (pub, priv) via gen_key()'
+        else:
+            self.__dict__[name] = value
+
+    def _ptr(self):
+        return self.dh
+
+    def check_params(self):
+        assert m2.dh_type_check(self.dh), "'dh' type error"
+        return m2.dh_check(self.dh)
+        
+    def gen_key(self):
+        assert m2.dh_type_check(self.dh), "'dh' type error"
+        m2.dh_generate_key(self.dh)   
+
+    def compute_key(self, pubkey):
+        assert m2.dh_type_check(self.dh), "'dh' type error"
+        return m2.dh_compute_key(self.dh, pubkey)
+
+    def print_params(self, bio):
+        assert m2.dh_type_check(self.dh), "'dh' type error"
+        return m2.dhparams_print(bio._ptr(), self.dh)
+
+
+def gen_params(plen, g, callback=genparam_callback):
+    return DH(m2.dh_generate_parameters(plen, g, callback), 1)
+
+
+def load_params(file):
+    bio = BIO.openfile(file)
+    return load_params_bio(bio)
+
+
+def load_params_bio(bio):
+    return DH(m2.dh_read_parameters(bio._ptr()), 1)
+
+
+def set_params(p, g):
+    dh = m2.dh_new()
+    m2.dh_set_p(dh, p)
+    m2.dh_set_g(dh, g)
+    return DH(dh, 1)
+
+
+#def free_params(cptr):
+#    m2.dh_free(cptr)
+
+
+DH_GENERATOR_2 = m2.DH_GENERATOR_2
+DH_GENERATOR_5 = m2.DH_GENERATOR_5
+
diff --git a/M2Crypto/DSA.py b/M2Crypto/DSA.py
new file mode 100644 (file)
index 0000000..db1f2ff
--- /dev/null
@@ -0,0 +1,439 @@
+"""
+    M2Crypto wrapper for OpenSSL DSA API.
+
+    Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+    Portions created by Open Source Applications Foundation (OSAF) are
+    Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+import sys
+import util, BIO, m2
+
+class DSAError(Exception): pass
+
+m2.dsa_init(DSAError)
+
+class DSA:
+
+    """
+    This class is a context supporting DSA key and parameter
+    values, signing and verifying.
+    
+    Simple example::
+    
+        from M2Crypto import EVP, DSA, util
+        
+        message = 'Kilroy was here!'
+        md = EVP.MessageDigest('sha1')
+        md.update(message)        
+        digest = md.final()
+        
+        dsa = DSA.gen_params(1024)
+        dsa.gen_key()
+        r, s = dsa.sign(digest)
+        good = dsa.verify(digest, r, s)
+        if good:
+            print '  ** success **'
+        else:
+            print '  ** verification failed **'
+    """
+
+    m2_dsa_free = m2.dsa_free
+
+    def __init__(self, dsa, _pyfree=0):
+        """
+        Use one of the factory functions to create an instance.
+        """
+        assert m2.dsa_type_check(dsa), "'dsa' type error"
+        self.dsa = dsa
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_dsa_free(self.dsa)
+
+    def __len__(self):
+        """
+        Return the key length.
+    
+        @rtype:   int
+        @return:  the DSA key length in bits
+        """
+        assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+        return m2.dsa_keylen(self.dsa)
+
+    def __getattr__(self, name):
+        """
+        Return specified DSA parameters and key values.
+    
+        @type  name: str
+        @param name: name of variable to be returned.  Must be 
+                     one of 'p', 'q', 'g', 'pub', 'priv'.
+        @rtype:      str
+        @return:     value of specified variable (a "byte string")
+        """
+        if name in ['p', 'q', 'g', 'pub', 'priv']:
+            method = getattr(m2, 'dsa_get_%s' % (name,))
+            assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+            return method(self.dsa)
+        else:
+            raise AttributeError
+
+    def __setattr__(self, name, value):
+        if name in ['p', 'q', 'g']:
+            raise DSAError('set (p, q, g) via set_params()')
+        elif name in ['pub','priv']:
+            raise DSAError('generate (pub, priv) via gen_key()')
+        else:
+            self.__dict__[name] = value
+
+    def set_params(self, p, q, g):
+        """
+        Set new parameters.
+        
+        @warning: This does not change the private key, so it may be
+                  unsafe to use this method. It is better to use
+                  gen_params function to create a new DSA object.
+        """
+        m2.dsa_set_p(self.dsa, p)
+        m2.dsa_set_q(self.dsa, q)
+        m2.dsa_set_g(self.dsa, g)
+
+    def gen_key(self):
+        """
+        Generate a key pair.
+        """
+        assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+        m2.dsa_gen_key(self.dsa)   
+
+    def save_params(self, filename):
+        """
+        Save the DSA parameters to a file.
+    
+        @type  filename: str
+        @param filename: Save the DSA parameters to this file.
+        @return:         1 (true) if successful
+        """
+        bio = BIO.openfile(filename, 'wb')
+        ret = m2.dsa_write_params_bio(self.dsa, bio._ptr())
+        bio.close()
+        return ret
+
+    def save_params_bio(self, bio):
+        """
+        Save DSA parameters to a BIO object.
+    
+        @type  bio: M2Crypto.BIO object
+        @param bio: Save DSA parameters to this object.
+        @return:    1 (true) if successful
+        """
+        return m2.dsa_write_params_bio(self.dsa, bio._ptr())
+
+    def save_key(self, filename, cipher='aes_128_cbc', 
+                 callback=util.passphrase_callback):
+        """
+        Save the DSA key pair to a file.
+    
+        @type  filename: str
+        @param filename: Save the DSA key pair to this file.
+        @type  cipher:   str
+        @param cipher:   name of symmetric key algorithm and mode
+                         to encrypt the private key.
+        @return:         1 (true) if successful
+        """
+        bio = BIO.openfile(filename, 'wb')
+        ret = self.save_key_bio(bio, cipher, callback)
+        bio.close()
+        return ret
+
+    def save_key_bio(self, bio, cipher='aes_128_cbc', 
+                     callback=util.passphrase_callback):
+        """
+        Save DSA key pair to a BIO object.
+    
+        @type  bio:    M2Crypto.BIO object
+        @param bio:    Save DSA parameters to this object.
+        @type  cipher: str
+        @param cipher: name of symmetric key algorithm and mode
+                       to encrypt the private key.
+        @return:       1 (true) if successful
+        """
+        if cipher is None:
+            return m2.dsa_write_key_bio_no_cipher(self.dsa, 
+                                                 bio._ptr(), callback)
+        else:
+            ciph = getattr(m2, cipher, None)
+            if ciph is None:
+                raise DSAError('no such cipher: %s' % cipher)
+            else:
+                ciph = ciph()
+            return m2.dsa_write_key_bio(self.dsa, bio._ptr(), ciph, callback)
+
+    def save_pub_key(self, filename):
+        """
+        Save the DSA public key (with parameters) to a file.
+    
+        @type  filename: str
+        @param filename: Save DSA public key (with parameters) 
+                         to this file.
+        @return:         1 (true) if successful
+        """
+        bio = BIO.openfile(filename, 'wb')
+        ret = self.save_pub_key_bio(bio)
+        bio.close()
+        return ret
+
+    def save_pub_key_bio(self, bio):
+        """
+        Save DSA public key (with parameters) to a BIO object.
+    
+        @type  bio: M2Crypto.BIO object
+        @param bio: Save DSA public key (with parameters) 
+                    to this object.
+        @return:  1 (true) if successful
+        """
+        return m2.dsa_write_pub_key_bio(self.dsa, bio._ptr())
+
+    def sign(self, digest):
+        """
+        Sign the digest.
+    
+        @type  digest: str
+        @param digest: SHA-1 hash of message (same as output 
+                       from MessageDigest, a "byte string")
+        @rtype:        tuple
+        @return:       DSA signature, a tuple of two values, r and s,
+                       both "byte strings".
+        """
+        assert self.check_key(), 'key is not initialised'
+        return m2.dsa_sign(self.dsa, digest)
+    
+    def verify(self, digest, r, s):
+        """
+        Verify a newly calculated digest against the signature 
+        values r and s.
+    
+        @type  digest: str
+        @param digest: SHA-1 hash of message (same as output 
+                       from MessageDigest, a "byte string")
+        @type  r:      str
+        @param r:      r value of the signature, a "byte string"
+        @type  s:      str
+        @param s:      s value of the signature, a "byte string"
+        @rtype:        int
+        @return:       1 (true) if verify succeeded, 0 if failed
+        """
+        assert self.check_key(), 'key is not initialised'
+        return m2.dsa_verify(self.dsa, digest, r, s)
+
+    def sign_asn1(self, digest):
+        assert self.check_key(), 'key is not initialised'
+        return m2.dsa_sign_asn1(self.dsa, digest)
+    
+    def verify_asn1(self, digest, blob):
+        assert self.check_key(), 'key is not initialised'
+        return m2.dsa_verify_asn1(self.dsa, digest, blob)
+
+    def check_key(self):
+        """
+        Check to be sure the DSA object has a valid private key.
+    
+        @rtype:   int
+        @return:  1 (true) if a valid private key
+        """
+        assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+        return m2.dsa_check_key(self.dsa)
+        
+
+
+class DSA_pub(DSA):
+
+    """
+    This class is a DSA context that only supports a public key 
+    and verification.  It does NOT support a private key or 
+    signing.
+    
+    """
+
+    def sign(self, *argv):
+        raise DSAError('DSA_pub object has no private key')
+
+    sign_asn1 = sign
+
+    def check_key(self):
+        return m2.dsa_check_pub_key(self.dsa)
+    
+    save_key = DSA.save_pub_key
+
+    save_key_bio = DSA.save_pub_key_bio
+
+#---------------------------------------------------------------
+# factories and other functions 
+
+def gen_params(bits, callback=util.genparam_callback):
+    """
+    Factory function that generates DSA parameters and 
+    instantiates a DSA object from the output.
+
+    @type  bits: int
+    @param bits: The length of the prime to be generated. If 
+                 'bits' < 512, it is set to 512.
+    @type  callback: function
+    @param callback: A Python callback object that will be 
+                 invoked during parameter generation; it usual 
+                 purpose is to provide visual feedback.
+    @rtype:   DSA
+    @return:  instance of DSA.
+    """
+    dsa = m2.dsa_generate_parameters(bits, callback)
+    if dsa is None:
+        raise DSAError('problem generating DSA parameters')
+    return DSA(dsa, 1)
+
+def set_params(p, q, g):
+    """
+    Factory function that instantiates a DSA object with DSA
+    parameters.
+
+    @type  p: str
+    @param p: value of p, a "byte string"
+    @type  q: str
+    @param q: value of q, a "byte string"
+    @type  g: str
+    @param g: value of g, a "byte string"
+    @rtype:   DSA
+    @return:  instance of DSA.
+    """
+    dsa = m2.dsa_new()
+    m2.dsa_set_p(dsa, p)
+    m2.dsa_set_q(dsa, q)
+    m2.dsa_set_g(dsa, g)
+    return DSA(dsa, 1)
+
+def load_params(file, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA object with DSA 
+    parameters from a file.
+
+    @type  file:     str
+    @param file:     Names the file (a path) that contains the PEM 
+                     representation of the DSA parameters. 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked if the DSA parameters file is 
+                     passphrase-protected.
+    @rtype:          DSA
+    @return:         instance of DSA.
+    """
+    bio = BIO.openfile(file)
+    ret = load_params_bio(bio, callback)
+    bio.close()
+    return ret
+
+
+def load_params_bio(bio, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA object with DSA
+    parameters from a M2Crypto.BIO object.
+
+    @type  bio:      M2Crypto.BIO object
+    @param bio:      Contains the PEM representation of the DSA 
+                     parameters. 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked if the DSA parameters file is 
+                     passphrase-protected.
+    @rtype:          DSA
+    @return:         instance of DSA.
+    """
+    dsa = m2.dsa_read_params(bio._ptr(), callback)
+    if dsa is None:
+        raise DSAError('problem loading DSA parameters')
+    return DSA(dsa, 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA object from a
+    PEM encoded DSA key pair.
+
+    @type  file:     str
+    @param file:     Names the file (a path) that contains the PEM 
+                     representation of the DSA key pair. 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked if the DSA key pair is 
+                     passphrase-protected.
+    @rtype:          DSA
+    @return:         instance of DSA.
+    """
+    bio = BIO.openfile(file)
+    ret = load_key_bio(bio, callback)
+    bio.close()
+    return ret
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA object from a
+    PEM encoded DSA key pair.
+
+    @type  bio:      M2Crypto.BIO object
+    @param bio:      Contains the PEM representation of the DSA 
+                     key pair. 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked if the DSA key pair is 
+                     passphrase-protected.
+    @rtype:          DSA
+    @return:         instance of DSA.
+    """
+    dsa = m2.dsa_read_key(bio._ptr(), callback)
+    if not dsa:
+        raise DSAError('problem loading DSA key pair')
+    return DSA(dsa, 1)
+
+
+def load_pub_key(file, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA_pub object using
+    a DSA public key contained in PEM file.  The PEM file 
+    must contain the parameters in addition to the public key.
+
+    @type  file:     str
+    @param file:     Names the file (a path) that contains the PEM 
+                     representation of the DSA public key. 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked should the DSA public key be 
+                     passphrase-protected.
+    @rtype:          DSA_pub
+    @return:         instance of DSA_pub.
+    """
+    bio = BIO.openfile(file)
+    ret = load_pub_key_bio(bio, callback)
+    bio.close()
+    return ret
+
+
+def load_pub_key_bio(bio, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a DSA_pub object using
+    a DSA public key contained in PEM format.  The PEM 
+    must contain the parameters in addition to the public key.
+
+    @type  bio:      M2Crypto.BIO object
+    @param bio:      Contains the PEM representation of the DSA 
+                     public key (with params). 
+    @type  callback: A Python callable
+    @param callback: A Python callback object that will be 
+                     invoked should the DSA public key be 
+                     passphrase-protected.
+    @rtype:          DSA_pub
+    @return:         instance of DSA_pub.
+    """
+    dsapub = m2.dsa_read_pub_key(bio._ptr(), callback)
+    if not dsapub:
+        raise DSAError('problem loading DSA public key')
+    return DSA_pub(dsapub, 1)
diff --git a/M2Crypto/EC.py b/M2Crypto/EC.py
new file mode 100644 (file)
index 0000000..b1ed43e
--- /dev/null
@@ -0,0 +1,335 @@
+"""
+M2Crypto wrapper for OpenSSL ECDH/ECDSA API.
+
+@requires: OpenSSL 0.9.8 or newer
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. 
+All rights reserved."""
+
+import util, BIO, m2
+
+class ECError(Exception): pass
+
+m2.ec_init(ECError)
+
+# Curve identifier constants
+NID_secp112r1 = m2.NID_secp112r1
+NID_secp112r2 = m2.NID_secp112r2
+NID_secp128r1 = m2.NID_secp128r1
+NID_secp128r2 = m2.NID_secp128r2
+NID_secp160k1 = m2.NID_secp160k1
+NID_secp160r1 = m2.NID_secp160r1
+NID_secp160r2 = m2.NID_secp160r2
+NID_secp192k1 = m2.NID_secp192k1
+NID_secp224k1 = m2.NID_secp224k1
+NID_secp224r1 = m2.NID_secp224r1
+NID_secp256k1 = m2.NID_secp256k1
+NID_secp384r1 = m2.NID_secp384r1
+NID_secp521r1 = m2.NID_secp521r1
+NID_sect113r1 = m2.NID_sect113r1
+NID_sect113r2 = m2.NID_sect113r2
+NID_sect131r1 = m2.NID_sect131r1
+NID_sect131r2 = m2.NID_sect131r2
+NID_sect163k1 = m2.NID_sect163k1
+NID_sect163r1 = m2.NID_sect163r1
+NID_sect163r2 = m2.NID_sect163r2
+NID_sect193r1 = m2.NID_sect193r1
+NID_sect193r2 = m2.NID_sect193r2
+NID_sect233k1 = m2.NID_sect233k1 # default for secg.org TLS test server
+NID_sect233r1 = m2.NID_sect233r1
+NID_sect239k1 = m2.NID_sect239k1
+NID_sect283k1 = m2.NID_sect283k1
+NID_sect283r1 = m2.NID_sect283r1
+NID_sect409k1 = m2.NID_sect409k1
+NID_sect409r1 = m2.NID_sect409r1
+NID_sect571k1 = m2.NID_sect571k1
+NID_sect571r1 = m2.NID_sect571r1
+
+NID_X9_62_prime192v1 = m2.NID_X9_62_prime192v1
+NID_X9_62_prime192v2 = m2.NID_X9_62_prime192v2
+NID_X9_62_prime192v3 = m2.NID_X9_62_prime192v3
+NID_X9_62_prime239v1 = m2.NID_X9_62_prime239v1
+NID_X9_62_prime239v2 = m2.NID_X9_62_prime239v2
+NID_X9_62_prime239v3 = m2.NID_X9_62_prime239v3
+NID_X9_62_prime256v1 = m2.NID_X9_62_prime256v1
+NID_X9_62_c2pnb163v1 = m2.NID_X9_62_c2pnb163v1
+NID_X9_62_c2pnb163v2 = m2.NID_X9_62_c2pnb163v2
+NID_X9_62_c2pnb163v3 = m2.NID_X9_62_c2pnb163v3
+NID_X9_62_c2pnb176v1 = m2.NID_X9_62_c2pnb176v1
+NID_X9_62_c2tnb191v1 = m2.NID_X9_62_c2tnb191v1
+NID_X9_62_c2tnb191v2 = m2.NID_X9_62_c2tnb191v2
+NID_X9_62_c2tnb191v3 = m2.NID_X9_62_c2tnb191v3
+NID_X9_62_c2pnb208w1 = m2.NID_X9_62_c2pnb208w1
+NID_X9_62_c2tnb239v1 = m2.NID_X9_62_c2tnb239v1
+NID_X9_62_c2tnb239v2 = m2.NID_X9_62_c2tnb239v2
+NID_X9_62_c2tnb239v3 = m2.NID_X9_62_c2tnb239v3
+NID_X9_62_c2pnb272w1 = m2.NID_X9_62_c2pnb272w1
+NID_X9_62_c2pnb304w1 = m2.NID_X9_62_c2pnb304w1
+NID_X9_62_c2tnb359v1 = m2.NID_X9_62_c2tnb359v1
+NID_X9_62_c2pnb368w1 = m2.NID_X9_62_c2pnb368w1
+NID_X9_62_c2tnb431r1 = m2.NID_X9_62_c2tnb431r1
+
+NID_wap_wsg_idm_ecid_wtls1  = m2.NID_wap_wsg_idm_ecid_wtls1
+NID_wap_wsg_idm_ecid_wtls3  = m2.NID_wap_wsg_idm_ecid_wtls3
+NID_wap_wsg_idm_ecid_wtls4  = m2.NID_wap_wsg_idm_ecid_wtls4
+NID_wap_wsg_idm_ecid_wtls5  = m2.NID_wap_wsg_idm_ecid_wtls5
+NID_wap_wsg_idm_ecid_wtls6  = m2.NID_wap_wsg_idm_ecid_wtls6
+NID_wap_wsg_idm_ecid_wtls7  = m2.NID_wap_wsg_idm_ecid_wtls7
+NID_wap_wsg_idm_ecid_wtls8  = m2.NID_wap_wsg_idm_ecid_wtls8
+NID_wap_wsg_idm_ecid_wtls9  = m2.NID_wap_wsg_idm_ecid_wtls9
+NID_wap_wsg_idm_ecid_wtls10 = m2.NID_wap_wsg_idm_ecid_wtls10
+NID_wap_wsg_idm_ecid_wtls11 = m2.NID_wap_wsg_idm_ecid_wtls11
+NID_wap_wsg_idm_ecid_wtls12 = m2.NID_wap_wsg_idm_ecid_wtls12
+
+# The following two curves, according to OpenSSL, have a 
+# "Questionable extension field!" and are not supported by 
+# the OpenSSL inverse function.  ECError: no inverse.
+# As such they cannot be used for signing.  They might, 
+# however, be usable for encryption but that has not 
+# been tested.  Until thir usefulness can be established,
+# they are not supported at this time.
+# NID_ipsec3 = m2.NID_ipsec3
+# NID_ipsec4 = m2.NID_ipsec4
+
+
+class EC:
+
+    """
+    Object interface to a EC key pair.
+    """
+
+    m2_ec_key_free = m2.ec_key_free
+    
+    def __init__(self, ec, _pyfree=0):
+        assert m2.ec_key_type_check(ec), "'ec' type error"
+        self.ec = ec
+        self._pyfree = _pyfree
+
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_ec_key_free(self.ec)
+
+    def __len__(self):
+        assert m2.ec_key_type_check(self.ec), "'ec' type error"
+        return m2.ec_key_keylen(self.ec)
+
+    def gen_key(self):
+        """
+        Generates the key pair from its parameters. Use::
+            keypair = EC.gen_params(curve)
+            keypair.gen_key()
+        to create an EC key pair.
+        """
+        assert m2.ec_key_type_check(self.ec), "'ec' type error"
+        m2.ec_key_gen_key(self.ec)   
+
+    def pub(self):
+        # Don't let python free
+        return EC_pub(self.ec, 0)
+
+    def sign_dsa(self, digest):
+        """
+        Sign the given digest using ECDSA. Returns a tuple (r,s), the two
+        ECDSA signature parameters.
+        """
+        assert self._check_key_type(), "'ec' type error"
+        return m2.ecdsa_sign(self.ec, digest)
+    
+    def verify_dsa(self, digest, r, s):
+        """
+        Verify the given digest using ECDSA. r and s are the ECDSA
+        signature parameters.
+        """
+        assert self._check_key_type(), "'ec' type error"
+        return m2.ecdsa_verify(self.ec, digest, r, s)
+
+    def sign_dsa_asn1(self, digest):
+        assert self._check_key_type(), "'ec' type error"
+        return m2.ecdsa_sign_asn1(self.ec, digest)
+    
+    def verify_dsa_asn1(self, digest, blob):
+        assert self._check_key_type(), "'ec' type error"
+        return m2.ecdsa_verify_asn1(self.ec, digest, blob)
+
+    def compute_dh_key(self,pub_key):
+        """
+        Compute the ECDH shared key of this key pair and the given public 
+        key object. They must both use the same curve. Returns the 
+        shared key in binary as a buffer object. No Key Derivation Function is 
+        applied.
+        """
+        assert self.check_key(), 'key is not initialised'
+        return m2.ecdh_compute_key(self.ec, pub_key.ec)
+
+    def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to an M2Crypto.BIO.BIO object in PEM format.
+
+        @type bio: M2Crypto.BIO.BIO
+        @param bio: M2Crypto.BIO.BIO object to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key.
+        The default is util.passphrase_callback.
+        """
+        if cipher is None:
+            return m2.ec_key_write_bio_no_cipher(self.ec, bio._ptr(), callback)
+        else:
+            ciph = getattr(m2, cipher, None)
+            if ciph is None:
+                raise ValueError('not such cipher %s' % cipher)
+            return m2.ec_key_write_bio(self.ec, bio._ptr(), ciph(), callback)
+
+    def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to a file in PEM format.
+
+        @type file: string
+        @param file: Name of file to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key.
+        The default is util.passphrase_callback.
+        """
+        bio = BIO.openfile(file, 'wb')
+        return self.save_key_bio(bio, cipher, callback)
+    
+    def save_pub_key_bio(self, bio):
+        """
+        Save the public key to an M2Crypto.BIO.BIO object in PEM format.
+
+        @type bio: M2Crypto.BIO.BIO
+        @param bio: M2Crypto.BIO.BIO object to save key to.
+        """ 
+        return m2.ec_key_write_pubkey(self.ec, bio._ptr())
+
+    def save_pub_key(self, file):
+        """
+        Save the public key to a file in PEM format.
+
+        @type file: string
+        @param file: Name of file to save key to.
+        """
+        bio = BIO.openfile(file, 'wb')
+        return m2.ec_key_write_pubkey(self.ec, bio._ptr())
+    
+    def _check_key_type(self):
+        return m2.ec_key_type_check(self.ec)
+
+    def check_key(self):
+        assert m2.ec_key_type_check(self.ec), "'ec' type error"
+        return m2.ec_key_check_key(self.ec)
+
+        
+class EC_pub(EC):
+
+    """
+    Object interface to an EC public key. 
+    ((don't like this implementation inheritance))
+    """
+    def __init__(self,ec,_pyfree=0):
+        EC.__init__(self,ec,_pyfree)
+        self.der = None
+
+    def get_der(self):
+        """
+        Returns the public key in DER format as a buffer object.
+        """
+        assert self.check_key(), 'key is not initialised'
+        if self.der is None:
+            self.der = m2.ec_key_get_public_der(self.ec)
+        return self.der
+
+    save_key = EC.save_pub_key
+
+    save_key_bio = EC.save_pub_key_bio
+
+
+def gen_params(curve):
+    """
+    Factory function that generates EC parameters and 
+    instantiates a EC object from the output.
+
+    @param curve: This is the OpenSSL nid of the curve to use. 
+    """
+    return EC(m2.ec_key_new_by_curve_name(curve), 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a EC object.
+
+    @param file: Names the file that contains the PEM representation 
+    of the EC key pair.
+
+    @param callback: Python callback object that will be invoked 
+    if the EC key pair is passphrase-protected.
+    """
+    bio = BIO.openfile(file)
+    return load_key_bio(bio, callback)
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+    """
+    Factory function that instantiates a EC object.
+
+    @param bio: M2Crypto.BIO object that contains the PEM
+    representation of the EC key pair. 
+
+    @param callback: Python callback object that will be invoked 
+    if the EC key pair is passphrase-protected.
+    """
+    return EC(m2.ec_key_read_bio(bio._ptr(), callback), 1)
+
+def load_pub_key(file):
+    """
+    Load an EC public key from file.
+
+    @type file: string
+    @param file: Name of file containing EC public key in PEM format.
+
+    @rtype: M2Crypto.EC.EC_pub
+    @return: M2Crypto.EC.EC_pub object.
+    """
+    bio = BIO.openfile(file) 
+    return load_pub_key_bio(bio)
+
+
+def load_pub_key_bio(bio):
+    """
+    Load an EC public key from an M2Crypto.BIO.BIO object.
+
+    @type bio: M2Crypto.BIO.BIO
+    @param bio: M2Crypto.BIO.BIO object containing EC public key in PEM
+    format.
+
+    @rtype: M2Crypto.EC.EC_pub
+    @return: M2Crypto.EC.EC_pub object.
+    """ 
+    ec = m2.ec_key_read_pubkey(bio._ptr())
+    if ec is None:
+        ec_error()
+    return EC_pub(ec, 1)
+
+def ec_error():
+    raise ECError, m2.err_reason_error_string(m2.err_get_error())
+
+def pub_key_from_der(der):
+    """
+    Create EC_pub from DER.
+    """
+    return EC_pub(m2.ec_key_from_pubkey_der(der), 1)
diff --git a/M2Crypto/EVP.py b/M2Crypto/EVP.py
new file mode 100644 (file)
index 0000000..cb92380
--- /dev/null
@@ -0,0 +1,408 @@
+"""M2Crypto wrapper for OpenSSL EVP API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions Copyright (c) 2004-2007 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+
+from M2Crypto import Err, util, BIO, RSA
+import m2
+
+class EVPError(Exception): pass
+
+m2.evp_init(EVPError)
+
+
+def pbkdf2(password, salt, iter, keylen):
+    """
+    Derive a key from password using PBKDF2 algorithm specified in RFC 2898.
+    
+    @param password: Derive the key from this password.
+    @type password:  str
+    @param salt:     Salt.
+    @type salt:      str
+    @param iter:     Number of iterations to perform.
+    @type iter:      int 
+    @param keylen:   Length of key to produce.
+    @type keylen:    int
+    @return:         Key.
+    @rtype:          str
+    """
+    return m2.pkcs5_pbkdf2_hmac_sha1(password, salt, iter, keylen)
+
+class MessageDigest:
+    """
+    Message Digest
+    """
+    m2_md_ctx_free = m2.md_ctx_free
+
+    def __init__(self, algo):
+        md = getattr(m2, algo, None)
+        if md is None:
+            raise ValueError, ('unknown algorithm', algo)
+        self.md=md()
+        self.ctx=m2.md_ctx_new()
+        m2.digest_init(self.ctx, self.md)
+        
+    def __del__(self):
+        if getattr(self, 'ctx', None):
+            self.m2_md_ctx_free(self.ctx)
+
+    def update(self, data):
+        """
+        Add data to be digested.
+        
+        @return: -1 for Python error, 1 for success, 0 for OpenSSL failure.
+        """
+        return m2.digest_update(self.ctx, data)
+
+    def final(self):
+        return m2.digest_final(self.ctx)
+
+    # Deprecated.
+    digest = final 
+
+
+class HMAC:
+    
+    m2_hmac_ctx_free = m2.hmac_ctx_free
+
+    def __init__(self, key, algo='sha1'):
+        md = getattr(m2, algo, None)
+        if md is None:
+            raise ValueError, ('unknown algorithm', algo)
+        self.md=md()
+        self.ctx=m2.hmac_ctx_new()
+        m2.hmac_init(self.ctx, key, self.md)
+        
+    def __del__(self):
+        if getattr(self, 'ctx', None):
+            self.m2_hmac_ctx_free(self.ctx)
+
+    def reset(self, key):
+        m2.hmac_init(self.ctx, key, self.md)
+
+    def update(self, data):
+        m2.hmac_update(self.ctx, data)
+
+    def final(self):
+        return m2.hmac_final(self.ctx)
+    
+    digest=final
+
+def hmac(key, data, algo='sha1'):
+    md = getattr(m2, algo, None)
+    if md is None:
+        raise ValueError, ('unknown algorithm', algo)
+    return m2.hmac(key, data, md())
+
+
+class Cipher:
+
+    m2_cipher_ctx_free = m2.cipher_ctx_free
+
+    def __init__(self, alg, key, iv, op, key_as_bytes=0, d='md5', salt='12345678', i=1, padding=1):
+        cipher = getattr(m2, alg, None)
+        if cipher is None:
+            raise ValueError, ('unknown cipher', alg)
+        self.cipher=cipher()
+        if key_as_bytes:
+            kmd = getattr(m2, d, None)
+            if kmd is None:
+                raise ValueError, ('unknown message digest', d)
+            key = m2.bytes_to_key(self.cipher, kmd(), key, salt, iv, i)
+        self.ctx=m2.cipher_ctx_new()
+        m2.cipher_init(self.ctx, self.cipher, key, iv, op)
+        self.set_padding(padding)
+        del key
+        
+    def __del__(self):
+        if getattr(self, 'ctx', None):        
+            self.m2_cipher_ctx_free(self.ctx)
+
+    def update(self, data):
+        return m2.cipher_update(self.ctx, data)
+
+    def final(self):
+        return m2.cipher_final(self.ctx)
+
+    def set_padding(self, padding=1):
+        return m2.cipher_set_padding(self.ctx, padding) 
+
+
+class PKey:
+    """
+    Public Key
+    """
+    
+    m2_pkey_free = m2.pkey_free
+    m2_md_ctx_free = m2.md_ctx_free
+
+    def __init__(self, pkey=None, _pyfree=0, md='sha1'):
+        if pkey is not None:
+            self.pkey = pkey
+            self._pyfree = _pyfree
+        else:
+            self.pkey = m2.pkey_new()
+            self._pyfree = 1
+        self._set_context(md)
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_pkey_free(self.pkey)
+        if getattr(self, 'ctx', None):
+            self.m2_md_ctx_free(self.ctx)
+
+    def _ptr(self):
+        return self.pkey
+
+    def _set_context(self, md):
+        mda = getattr(m2, md, None)
+        if mda is None:
+            raise ValueError, ('unknown message digest', md)
+        self.md = mda()
+        self.ctx = m2.md_ctx_new()
+
+    def reset_context(self, md='sha1'):
+        """
+        Reset internal message digest context.
+
+        @type md: string
+        @param md: The message digest algorithm.
+        """
+        self._set_context(md)
+
+    def sign_init(self):
+        """
+        Initialise signing operation with self.
+        """
+        m2.sign_init(self.ctx, self.md)
+
+    def sign_update(self, data):
+        """
+        Feed data to signing operation.
+
+        @type data: string
+        @param data: Data to be signed.
+        """
+        m2.sign_update(self.ctx, data)
+
+    def sign_final(self):
+        """
+        Return signature.
+
+        @rtype: string
+        @return: The signature.
+        """
+        return m2.sign_final(self.ctx, self.pkey)
+
+    # Deprecated
+    update = sign_update
+    final = sign_final
+
+    def verify_init(self):
+        """
+        Initialise signature verification operation with self.
+        """
+        m2.verify_init(self.ctx, self.md)
+
+    def verify_update(self, data):
+        """
+        Feed data to verification operation.
+
+        @type data: string
+        @param data: Data to be verified.
+        @return: -1 on Python error, 1 for success, 0 for OpenSSL error
+        """
+        return m2.verify_update(self.ctx, data)
+
+    def verify_final(self, sign):
+        """
+        Return result of verification.
+
+        @param sign: Signature to use for verification
+        @rtype: int
+        @return: Result of verification: 1 for success, 0 for failure, -1 on
+                 other error.
+        """
+        return m2.verify_final(self.ctx, sign, self.pkey)
+
+    def assign_rsa(self, rsa, capture=1):
+        """
+        Assign the RSA key pair to self.
+
+        @type rsa: M2Crypto.RSA.RSA
+        @param rsa: M2Crypto.RSA.RSA object to be assigned to self.
+
+        @type capture:  boolean
+        @param capture: If true (default), this PKey object will own the RSA
+                        object, meaning that once the PKey object gets
+                        deleted it is no longer safe to use the RSA object.
+        
+        @rtype: int
+        @return: Return 1 for success and 0 for failure.
+        """
+        if capture:
+            ret = m2.pkey_assign_rsa(self.pkey, rsa.rsa)
+            if ret:
+                rsa._pyfree = 0
+        else:
+            ret = m2.pkey_set1_rsa(self.pkey, rsa.rsa)
+        return ret
+
+    def get_rsa(self):
+        """
+        Return the underlying RSA key if that is what the EVP
+        instance is holding.
+        """
+        rsa_ptr = m2.pkey_get1_rsa(self.pkey)
+        if rsa_ptr is None:
+            raise ValueError("PKey instance is not holding a RSA key")
+        
+        rsa = RSA.RSA_pub(rsa_ptr, 1)
+        return rsa
+
+    def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to a file in PEM format.
+
+        @type file: string
+        @param file: Name of file to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key. 
+        The default is util.passphrase_callback.
+        """
+        bio = BIO.openfile(file, 'wb')
+        return self.save_key_bio(bio, cipher, callback)
+
+    def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to the M2Crypto.BIO object 'bio' in PEM format.
+
+        @type bio: M2Crypto.BIO
+        @param bio: M2Crypto.BIO object to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key. 
+        The default is util.passphrase_callback.
+        """
+        if cipher is None:
+            return m2.pkey_write_pem_no_cipher(self.pkey, bio._ptr(), callback)
+        else:
+            proto = getattr(m2, cipher, None)
+            if proto is None:
+                raise ValueError, 'no such cipher %s' % cipher
+            return m2.pkey_write_pem(self.pkey, bio._ptr(), proto(), callback)
+
+    def as_pem(self, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Return key in PEM format in a string.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key. 
+        The default is util.passphrase_callback.
+        """
+        bio = BIO.MemoryBuffer()
+        self.save_key_bio(bio, cipher, callback)
+        return bio.read_all()
+
+    def as_der(self):
+        """
+        Return key in DER format in a string
+        """
+        buf = m2.pkey_as_der(self.pkey)
+        bio = BIO.MemoryBuffer(buf)
+        return bio.read_all()
+   
+    def size(self):
+        """
+        Return the size of the key in bytes.
+        """
+        return m2.pkey_size(self.pkey)
+        
+    def get_modulus(self):
+        """
+        Return the modulus in hex format.
+        """
+        return m2.pkey_get_modulus(self.pkey)
+   
+
+def load_key(file, callback=util.passphrase_callback):
+    """
+    Load an M2Crypto.EVP.PKey from file.
+
+    @type file: string
+    @param file: Name of file containing the key in PEM format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to protect the key.
+
+    @rtype: M2Crypto.EVP.PKey
+    @return: M2Crypto.EVP.PKey object.
+    """
+    bio = m2.bio_new_file(file, 'r')
+    if bio is None:
+        raise BIO.BIOError(Err.get_error())
+    cptr = m2.pkey_read_pem(bio, callback)
+    m2.bio_free(bio)
+    if cptr is None:
+        raise EVPError(Err.get_error())
+    return PKey(cptr, 1)
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+    """
+    Load an M2Crypto.EVP.PKey from an M2Crypto.BIO object.
+
+    @type bio: M2Crypto.BIO
+    @param bio: M2Crypto.BIO object containing the key in PEM format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to protect the key.
+
+    @rtype: M2Crypto.EVP.PKey
+    @return: M2Crypto.EVP.PKey object.
+    """
+    cptr = m2.pkey_read_pem(bio._ptr(), callback)
+    if cptr is None:
+        raise EVPError(Err.get_error())
+    return PKey(cptr, 1)
+
+def load_key_string(string, callback=util.passphrase_callback):
+    """
+    Load an M2Crypto.EVP.PKey from a string.
+
+    @type string: string
+    @param string: String containing the key in PEM format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to protect the key.
+
+    @rtype: M2Crypto.EVP.PKey
+    @return: M2Crypto.EVP.PKey object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    return load_key_bio( bio, callback)
+
diff --git a/M2Crypto/Engine.py b/M2Crypto/Engine.py
new file mode 100644 (file)
index 0000000..d6b879b
--- /dev/null
@@ -0,0 +1,119 @@
+# vim: sts=4 sw=4 et
+"""
+M2Crypto wrapper for OpenSSL ENGINE API.
+
+Pavel Shramov
+IMEC MSU
+"""
+
+from M2Crypto import m2, EVP, X509, Err
+
+class EngineError(Exception): pass
+
+m2.engine_init_error(EngineError)
+
+class Engine:
+    """Wrapper for ENGINE object."""
+
+    m2_engine_free = m2.engine_free
+    
+    def __init__(self, id = None, _ptr = None, _pyfree = 1):
+        """Create new Engine from ENGINE pointer or obtain by id"""
+        if not _ptr and not id:
+            raise ValueError("No engine id specified")
+        self._ptr = _ptr
+        if not self._ptr:
+            self._ptr = m2.engine_by_id(id)
+            if not self._ptr:
+                raise ValueError("Unknown engine: %s" % id)
+        self._pyfree = _pyfree
+
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_engine_free(self._ptr)
+
+    def init(self):
+        """Obtain a functional reference to the engine.
+        
+        @return: 0 on error, non-zero on success."""
+        return m2.engine_init(self._ptr)
+        
+    def finish(self):
+        """Release a functional and structural reference to the engine."""
+        return m2.engine_finish(self._ptr)
+
+    def ctrl_cmd_string(self, cmd, arg, optional = 0):
+        """Call ENGINE_ctrl_cmd_string"""
+        if not m2.engine_ctrl_cmd_string(self._ptr, cmd, arg, optional):
+            raise EngineError(Err.get_error())
+
+    def get_name(self):
+        """Return engine name"""
+        return m2.engine_get_name(self._ptr)
+
+    def get_id(self):
+        """Return engine id"""
+        return m2.engine_get_id(self._ptr)
+
+    def set_default(self, methods = m2.ENGINE_METHOD_ALL):
+        """Use this engine as default for methods specified in argument
+        Possible values are bitwise OR of m2.ENGINE_METHOD_*"""
+        return m2.engine_set_default(self._ptr, methods)
+
+    def _engine_load_key(self, func, name, pin = None):
+        """Helper function for loading keys"""
+        ui = m2.ui_openssl()
+        cbd = m2.engine_pkcs11_data_new(pin)
+        try:
+            kptr = func(self._ptr, name, ui, cbd)
+            if not kptr:
+                raise EngineError(Err.get_error())
+            key = EVP.PKey(kptr, _pyfree = 1)
+        finally:
+            m2.engine_pkcs11_data_free(cbd)
+        return key
+
+    def load_private_key(self, name, pin = None):
+        """Load private key with engine methods (e.g from smartcard).
+            If pin is not set it will be asked
+        """
+        return self._engine_load_key(m2.engine_load_private_key, name, pin)
+
+    def load_public_key(self, name, pin = None):
+        """Load public key with engine methods (e.g from smartcard)."""
+        return self._engine_load_key(m2.engine_load_public_key, name, pin)
+
+    def load_certificate(self, name):
+        """Load certificate from engine (e.g from smartcard).
+        NOTE: This function may be not implemented by engine!"""
+        cptr = m2.engine_load_certificate(self._ptr, name)
+        if not cptr:
+            raise EngineError("Certificate or card not found")
+        return X509.X509(cptr, _pyfree = 1)
+
+
+def load_dynamic_engine(id, sopath):
+    """Load and return dymanic engine from sopath and assign id to it"""
+    m2.engine_load_dynamic()
+    e = Engine('dynamic')
+    e.ctrl_cmd_string("SO_PATH", sopath)
+    e.ctrl_cmd_string("ID", id)
+    e.ctrl_cmd_string("LIST_ADD", "1")
+    e.ctrl_cmd_string("LOAD", None)
+    return e
+
+
+def load_dynamic():
+    """Load dynamic engine"""
+    m2.engine_load_dynamic()
+
+
+def load_openssl():
+    """Load openssl engine"""
+    m2.engine_load_openssl()
+
+
+def cleanup():
+    """If you load any engines, you need to clean up after your application
+    is finished with the engines."""
+    m2.engine_cleanup()
diff --git a/M2Crypto/Err.py b/M2Crypto/Err.py
new file mode 100644 (file)
index 0000000..3588f76
--- /dev/null
@@ -0,0 +1,49 @@
+"""M2Crypto wrapper for OpenSSL Error API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import BIO
+import m2
+
+def get_error():
+    err=BIO.MemoryBuffer()
+    m2.err_print_errors(err.bio_ptr())
+    return err.getvalue()
+
+def get_error_code():
+    return m2.err_get_error()
+
+def peek_error_code():
+    return m2.err_peek_error()
+
+def get_error_lib(err):
+    return m2.err_lib_error_string(err)
+
+def get_error_func(err):
+    return m2.err_func_error_string(err)
+
+def get_error_reason(err):
+    return m2.err_reason_error_string(err)
+
+def get_x509_verify_error(err):
+    return m2.x509_get_verify_error(err)
+
+class SSLError(Exception):
+    def __init__(self, err, client_addr):
+        self.err = err
+        self.client_addr = client_addr
+
+    def __str__(self):
+        if (isinstance(self.client_addr, unicode)):
+            s = self.client_addr.encode('utf8')
+        else:
+            s = self.client_addr
+        return "%s: %s: %s" % \
+            (m2.err_func_error_string(self.err), \
+            s, \
+            m2.err_reason_error_string(self.err))
+
+class M2CryptoError(Exception):
+    pass
+
diff --git a/M2Crypto/PGP/PublicKey.py b/M2Crypto/PGP/PublicKey.py
new file mode 100644 (file)
index 0000000..f892ade
--- /dev/null
@@ -0,0 +1,58 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+from packet import *
+import RSA
+
+class PublicKey:
+    def __init__(self, pubkey_pkt):
+        import warnings
+        warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+        self._pubkey_pkt = pubkey_pkt
+        self._pubkey = RSA.new_pub_key((pubkey_pkt._e, pubkey_pkt._n))
+        self._userid = {}
+        self._signature = {}
+
+    def keyid(self):
+        return self._pubkey.n[-8:]
+
+    def add_userid(self, u_pkt):
+        assert isinstance(u_pkt, userid_packet)
+        self._userid[u_pkt.userid()] = u_pkt
+
+    def remove_userid(self, userid):
+        del self._userid[userid]
+
+    def add_signature(self, userid, s_pkt):
+        assert isinstance(s_pkt, signature_packet)
+        assert self._userid.has_key(userid)
+        if self._signature.has_key(userid):
+            self._signature.append(s_pkt)
+        else:
+            self._signature = [s_pkt]
+        
+    def __getitem__(self, id):
+        return self._userid[id]
+    
+    def __setitem__(self, *args):
+        raise NotImplementedError
+
+    def __delitem__(self, id):
+        del self._userid[id]
+        if self._signature[id]:
+            del self._signature[id]
+
+    def write(self, stream):
+        pass
+
+    def encrypt(self, ptxt):
+        # XXX Munge ptxt into pgp format.
+        return self._pubkey.public_encrypt(ptxt, RSA.pkcs1_padding)
+
+    def decrypt(self, ctxt):
+        # XXX Munge ctxt into pgp format.
+        return self._pubkey.public_encrypt(ctxt, RSA.pkcs1_padding)
+
diff --git a/M2Crypto/PGP/PublicKeyRing.py b/M2Crypto/PGP/PublicKeyRing.py
new file mode 100644 (file)
index 0000000..a8447b3
--- /dev/null
@@ -0,0 +1,81 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+from packet import *
+from PublicKey import *
+
+class PublicKeyRing:
+    def __init__(self, keyring):
+        import warnings
+        warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+        self._keyring = keyring
+        self._userid = {}
+        self._keyid = {}
+        self._spurious = []
+        self._pubkey = []
+
+    def load(self):
+        curr_pub = None
+        curr_index = -1
+
+        ps = packet_stream(self._keyring)
+        while 1:
+            pkt = ps.read() 
+
+            if pkt is None:
+                break
+
+            elif isinstance(pkt, public_key_packet):
+                curr_index = curr_index + 1
+                curr_pub = PublicKey(pkt)
+                self._pubkey.append(curr_pub)
+                #self._keyid[curr_pub.keyid()] = (curr_pub, curr_index)
+
+            elif isinstance(pkt, userid_packet):
+                if curr_pub is None:
+                    self._spurious.append(pkt)
+                else:
+                    curr_pub.add_userid(pkt)
+                    self._userid[pkt.userid()] = (curr_pub, curr_index)
+
+            elif isinstance(pkt, signature_packet):
+                if curr_pub is None:
+                    self._spurious.append(pkt)
+                else:
+                    curr_pub.add_signature(pkt)
+
+            else:
+                self._spurious.append(pkt)
+
+        ps.close()
+            
+    def __getitem__(self, id):
+        return self._userid[id][0]
+
+    def __setitem__(self, *args):
+        raise NotImplementedError
+
+    def __delitem__(self, id):
+        pkt, idx = self._userid[id]
+        del self._pubkey[idx]
+        del self._userid[idx]
+        pkt, idx = self._keyid[id]
+        del self._keyid[idx]
+
+    def spurious(self):
+        return tuple(self._spurious)
+
+    def save(self, keyring):
+        for p in self._pubkey:
+            pp = p.pack()
+            keyring.write(pp)
+
+
+def load_pubring(filename='pubring.pgp'):
+    pkr = PublicKeyRing(open(filename, 'rb'))
+    pkr.load()
+    return pkr
+
diff --git a/M2Crypto/PGP/RSA.py b/M2Crypto/PGP/RSA.py
new file mode 100644 (file)
index 0000000..153193d
--- /dev/null
@@ -0,0 +1,36 @@
+"""M2Crypto PGP2 RSA.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import sys
+from M2Crypto import m2, RSA
+_RSA = RSA
+del RSA
+
+
+class RSA(_RSA.RSA):
+    pass
+
+
+class RSA_pub(_RSA.RSA_pub):
+    pass
+
+
+def new_pub_key((e, n)):
+    """
+    Factory function that instantiates an RSA_pub object from a (e, n) tuple.
+
+    'e' is the RSA public exponent; it is a string in OpenSSL's binary format,
+    i.e., a number of bytes in big-endian.
+
+    'n' is the RSA composite of primes; it is a string in OpenSSL's binary format,
+    i.e., a number of bytes in big-endian.
+    """ 
+    import warnings
+    warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+    rsa = m2.rsa_new()
+    m2.rsa_set_e_bin(rsa, e)
+    m2.rsa_set_n_bin(rsa, n)
+    return RSA_pub(rsa, 1)
+
diff --git a/M2Crypto/PGP/__init__.py b/M2Crypto/PGP/__init__.py
new file mode 100644 (file)
index 0000000..27fd669
--- /dev/null
@@ -0,0 +1,14 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+
+from packet import public_key_packet, trust_packet, userid_packet,\
+    comment_packet, signature_packet, private_key_packet, cke_packet,\
+    pke_packet, literal_packet, packet_stream
+
+from PublicKey import *
+from PublicKeyRing import *
+
+
diff --git a/M2Crypto/PGP/constants.py b/M2Crypto/PGP/constants.py
new file mode 100644 (file)
index 0000000..9a7810f
--- /dev/null
@@ -0,0 +1,19 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+CTB_TAG             = 128
+
+CTB_PKE             = 1
+CTB_SIGNATURE       = 2
+CTB_MESSAGE_DIGETS  = 3
+CTB_PRIVATE_KEY     = 5
+CTB_PUBLIC_KEY      = 6
+CTB_COMPRESSED_DATA = 8
+CTB_CKE             = 9
+CTB_LITERAL_DATA    = 11
+CTB_TRUST           = 12
+CTB_USERID          = 13
+CTB_COMMENT         = 14
+
+
diff --git a/M2Crypto/PGP/packet.py b/M2Crypto/PGP/packet.py
new file mode 100644 (file)
index 0000000..d662c84
--- /dev/null
@@ -0,0 +1,379 @@
+"""M2Crypto PGP2.
+
+This module implements PGP packets per RFC1991 and various source distributions.
+
+Each packet type is represented by a class; packet classes derive from 
+the abstract 'packet' class. 
+
+The 'message digest' packet type, mentioned but not documented in RFC1991,
+is not implemented.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# XXX Work-in-progress.
+
+# Be liberal in what you accept.
+# Be conservative in what you send. 
+# Be lazy in what you eval.
+
+import struct, time
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from M2Crypto import EVP, RSA
+from M2Crypto.util import octx_to_num
+
+from constants import *
+
+_OK_VERSION     = ('\002', '\003')
+_OK_VALIDITY    = ('\000',)
+_OK_PKC         = ('\001',)
+
+
+class packet:
+    def __init__(self, ctb, body=None):
+        import warnings
+        warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+        
+        self.ctb = ctb
+        if body is not None:
+            self.body = StringIO(body)
+        else:
+            self.body = None
+
+    def validate(self):
+        return 1
+
+    def pack(self):
+        raise NotImplementedError, '%s.pack(): abstract method' % (self.__class__,)
+
+    def version(self):
+        if hasattr(self, '_version'):
+            return ord(self._version)
+        else:
+            return None
+
+    def timestamp(self):
+        if hasattr(self, '_timestamp'):
+            return struct.unpack('>L', self._timestamp)[0]
+        else:
+            return None
+
+    def validity(self):
+        if hasattr(self, '_validity'):
+            return struct.unpack('>H', self._validity)[0]
+        else:
+            return None
+
+    def pkc(self):
+        if hasattr(self, '_pkc'):
+            return self._pkc
+        else:
+            return None
+
+    def _llf(self, lenf):
+        if lenf < 256:  
+            return (0, chr(lenf))
+        elif lenf < 65536:
+            return (1, struct.pack('>H', lenf))
+        else: 
+            assert lenf < 2L**32
+            return (2, struct.pack('>L', lenf))
+
+    def _ctb(self, llf):
+        ctbv = _FACTORY[self.__class__]
+        return chr((1 << 7) | (ctbv << 2) | llf)
+
+
+class public_key_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if self.body is not None:
+            self._version = self.body.read(1)
+            self._timestamp = self.body.read(4)
+            self._validity = self.body.read(2)
+            self._pkc = self.body.read(1)
+
+            self._nlen = self.body.read(2)
+            nlen = (struct.unpack('>H', self._nlen)[0] + 7) / 8
+            self._n = self.body.read(nlen)
+            
+            self._elen = self.body.read(2)
+            elen = (struct.unpack('>H', self._elen)[0] + 7) / 8
+            self._e = self.body.read(elen)
+
+    def pack(self):
+        if self.body is None:
+            self.body = StringIO()
+            self.body.write(self._version)
+            self.body.write(self._timestamp)
+            self.body.write(self._validity)
+            self.body.write(self._pkc)
+            self.body.write(self._nlen)
+            self.body.write(self._n)
+            self.body.write(self._elen)
+            self.body.write(self._e)
+        self.body = self.body.getvalue()
+        llf, lenf = self._llf(len(self.body))
+        ctb = self._ctb(llf)
+        return '%s%s%s' % (ctb, lenf, self.body)
+
+    def pubkey(self):
+        return self._pubkey.pub()
+
+
+class trust_packet(packet):
+    # This implementation neither interprets nor emits trust packets.
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self.trust = self.body.read(1)
+
+
+class userid_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self._userid = body
+
+    def pack(self):
+        if self.body is None:
+            self.body = StringIO()
+            self.body.write(chr(len(self._userid)))
+            self.body.write(self._userid)
+            self.body = self.body.getvalue()
+        return self.ctb + self.body
+
+    def userid(self):   
+        return self._userid
+
+
+class comment_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self.comment = self.body.getvalue()
+
+    def pack(self):
+        if self.body is None:
+            self.body = StringIO()
+            self.body.write(chr(len(self.comment)))
+            self.body.write(self.comment)
+            self.body = self.body.getvalue()
+        return self.ctb + self.body
+
+
+class signature_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self._version = self.body.read(1)
+            self._len_md_stuff = self.body.read(1)
+            self._classification = self.body.read(1)
+            self._timestamp = self.body.read(4)
+            self._keyid = self.body.read(8)
+            self._pkc = self.body.read(1)
+            self._md_algo = self.body.read(1)
+            self._md_chksum = self.body.read(2)
+            self._sig = self.body.read()
+
+    def pack(self):
+        if self.body is None:
+            self.body = StringIO()
+            self.body.write(self._version)
+            self.body.write(self._len_md_stuff)
+            self.body.write(self._classification)
+            self.body.write(self._timestamp)
+            self.body.write(self._keyid)
+            self.body.write(self._pkc)
+            self.body.write(self._md_algo)
+            self.body.write(self._md_chksum)
+            self.body.write(self._sig)
+            self.body = self.body.getvalue()
+        llf, lenf = self._llf(len(body))
+        self.ctb = self.ctb | llf
+        return '%s%s%s' % (self.ctb, lenf, self.body)
+
+
+    def validate(self):
+        if self._version not in _OK_VERSION:
+            return None
+        if self._len_md_stuff != '\005':
+            return None
+
+
+class private_key_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self._version = self.body.read(1)
+            self._timestamp = self.body.read(4)
+            self._validity = self.body.read(2)
+            self._pkc = self.body.read(1)
+
+            self._nlen = self.body.read(2)
+            nlen = (struct.unpack('>H', self._nlen)[0] + 7) / 8
+            self._n = self.body.read(nlen)
+            
+            self._elen = self.body.read(2)
+            elen = (struct.unpack('>H', self._elen)[0] + 7) / 8
+            self._e = self.body.read(elen)
+
+            self._cipher = self.body.read(1)
+            if self._cipher == '\001':
+                self._iv = self.body.read(8)
+            else:
+                self._iv = None
+    
+            for param in ['d', 'p', 'q', 'u']:
+                _plen = self.body.read(2)
+                setattr(self, '_'+param+'len', _plen)
+                plen = (struct.unpack('>H', _plen)[0] + 7) / 8
+                setattr(self, '_'+param, self.body.read(plen))
+    
+            self._cksum = self.body.read(2)
+
+    def is_encrypted(self):
+        return ord(self._cipher)
+
+
+class cke_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self._iv = self.body.read(8)
+            self._cksum = self.body.read(2)
+            self._ctxt = self.body.read()
+
+
+class pke_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self._version = self.body.read(1)
+            self._keyid = self.body.read(8)
+            self._pkc = ord(self.body.read(1))
+    
+            deklen = (struct.unpack('>H', self.body.read(2))[0] + 7 ) / 8
+            self._dek = octx_to_num(self.body.read(deklen))
+
+
+class literal_packet(packet):
+    def __init__(self, ctb, body=None):
+        packet.__init__(self, ctb, body)
+        if body is not None:
+            self.fmode = self.body.read(1)
+            fnlen = self.body.read(1)
+            self.fname = self.body.read(fnlen)
+            self.ftime = self.body.read(4)
+            #self.data = self.body.read()
+
+
+class compressed_packet(packet):
+    def __init__(self, ctb, stream):
+        packet.__init__(self, ctb, '')
+        if body is not None:
+            self.algo = stream.read(1)
+            # This reads the entire stream into memory.
+            self.data = stream.read()
+
+    def validate(self):
+        return (self.algo == '\001')
+
+    def uncompress(self):
+        import zlib
+        decomp = zlib.decompressobj(-13)    # RFC 2440, pg 61.
+        # This doubles the memory usage.
+        stream = StringIO(decomp.decompress(self.data))
+        return stream
+
+
+_FACTORY = { 
+    1 : pke_packet,
+    2 : signature_packet,
+    #3 : message_digest_packet,     # XXX not implemented
+    5 : private_key_packet,
+    6 : public_key_packet,
+    #8 : compressed_packet,         # special case
+    9 : cke_packet,
+    11 : literal_packet,
+    12 : trust_packet,
+    13 : userid_packet,
+    14 : comment_packet,
+    pke_packet : 1,
+    signature_packet : 2,
+    #3 : message_digest_packet,     
+    private_key_packet : 5,
+    public_key_packet : 6,
+    #8 : compressed_packet,
+    cke_packet : 9,
+    literal_packet : 11,
+    trust_packet : 12,
+    userid_packet : 13,
+    comment_packet : 14
+}
+
+
+class packet_stream:
+    def __init__(self, input):
+        self.stream = input
+        self.under_current = None
+        self._count = 0
+
+    def close(self):
+        self.stream.close()
+        if self.under_current is not None:
+            self.under_current.close()
+
+    def read(self, keep_trying=0):
+        while 1:
+            ctb0 = self.stream.read(1)
+            if not ctb0:
+                return None
+            ctb = ord(ctb0)
+            if is_ctb(ctb):
+                break
+            elif keep_trying:
+                continue
+            else:
+                raise XXXError
+        ctbt = (ctb & 0x3c) >> 2
+
+        if ctbt == CTB_COMPRESSED_DATA:
+            self.under_current = self.stream
+            cp = compressed_packet(ctb0, self.stream)
+            self.stream = cp.uncompress()
+            return self.read()
+
+        # Decode the length of following data. See RFC for details.
+        llf = ctb & 3
+        if llf == 0:
+            lenf = ord(self.stream.read(1))
+        elif llf == 1:
+            lenf = struct.unpack('>H', self.stream.read(2))[0]
+        elif llf == 2:
+            lenf = struct.unpack('>L', self.stream.read(4))[0]
+        else: # llf == 3
+            raise XXXError, 'impossible case'
+
+        body = self.stream.read(lenf)
+        if not body or (len(body) != lenf):
+            raise XXXError, 'corrupted packet'
+
+        self._count = self.stream.tell()
+        try: 
+            return _FACTORY[ctbt](ctb0, body) 
+        except KeyError:
+            return packet(ctb0, body)
+
+    def count(self):
+        return self._count
+
+def is_ctb(ctb):
+    return ctb & 0xc0
+
+def make_ctb(value, llf):
+    return chr((1 << 7) | (value << 2) | llf)
diff --git a/M2Crypto/RC4.py b/M2Crypto/RC4.py
new file mode 100644 (file)
index 0000000..1b5d408
--- /dev/null
@@ -0,0 +1,31 @@
+"""M2Crypto wrapper for OpenSSL RC4 API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from m2 import rc4_new, rc4_free, rc4_set_key, rc4_update
+
+class RC4:
+
+    """Object interface to the stream cipher RC4."""
+
+    rc4_free = rc4_free
+
+    def __init__(self, key=None):
+        self.cipher = rc4_new()
+        if key:
+            rc4_set_key(self.cipher, key)
+            
+    def __del__(self):
+        if getattr(self, 'cipher', None):        
+            self.rc4_free(self.cipher)
+
+    def set_key(self, key):
+        rc4_set_key(self.cipher, key)   
+
+    def update(self, data):
+        return rc4_update(self.cipher, data)
+
+    def final(self):
+        return ''
+
+
diff --git a/M2Crypto/RSA.py b/M2Crypto/RSA.py
new file mode 100644 (file)
index 0000000..2dff160
--- /dev/null
@@ -0,0 +1,448 @@
+"""M2Crypto wrapper for OpenSSL RSA API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import sys
+import util, BIO, Err, m2
+
+class RSAError(Exception): pass
+
+m2.rsa_init(RSAError)
+
+no_padding = m2.no_padding
+pkcs1_padding = m2.pkcs1_padding
+sslv23_padding = m2.sslv23_padding
+pkcs1_oaep_padding = m2.pkcs1_oaep_padding
+
+
+class RSA:
+    """
+    RSA Key Pair.
+    """
+
+    m2_rsa_free = m2.rsa_free
+
+    def __init__(self, rsa, _pyfree=0):
+        assert m2.rsa_type_check(rsa), "'rsa' type error"
+        self.rsa = rsa
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_rsa_free(self.rsa)
+
+    def __len__(self):
+        return m2.rsa_size(self.rsa) << 3
+
+    def __getattr__(self, name):
+        if name == 'e':
+            return m2.rsa_get_e(self.rsa)
+        elif name == 'n':
+            return m2.rsa_get_n(self.rsa)
+        else:
+            raise AttributeError
+
+    def pub(self):
+        assert self.check_key(), 'key is not initialised'
+        return m2.rsa_get_e(self.rsa), m2.rsa_get_n(self.rsa)
+
+    def public_encrypt(self, data, padding):
+        assert self.check_key(), 'key is not initialised'
+        return m2.rsa_public_encrypt(self.rsa, data, padding)
+
+    def public_decrypt(self, data, padding):
+        assert self.check_key(), 'key is not initialised'
+        return m2.rsa_public_decrypt(self.rsa, data, padding)
+
+    def private_encrypt(self, data, padding):
+        assert self.check_key(), 'key is not initialised'
+        return m2.rsa_private_encrypt(self.rsa, data, padding)
+
+    def private_decrypt(self, data, padding):
+        assert self.check_key(), 'key is not initialised'
+        return m2.rsa_private_decrypt(self.rsa, data, padding)
+
+    def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to an M2Crypto.BIO.BIO object in PEM format.
+
+        @type bio: M2Crypto.BIO.BIO
+        @param bio: M2Crypto.BIO.BIO object to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key.
+        The default is util.passphrase_callback.
+        """
+        if cipher is None:
+            return m2.rsa_write_key_no_cipher(self.rsa, bio._ptr(), callback)
+        else:
+            ciph = getattr(m2, cipher, None)
+            if ciph is None:
+                raise RSAError, 'not such cipher %s' % cipher 
+            else:
+                ciph = ciph()
+            return m2.rsa_write_key(self.rsa, bio._ptr(), ciph, callback)
+
+    def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Save the key pair to a file in PEM format.
+
+        @type file: string
+        @param file: Name of file to save key to.
+
+        @type cipher: string
+        @param cipher: Symmetric cipher to protect the key. The default
+        cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+        in the clear.
+
+        @type callback: Python callable
+        @param callback: A Python callable object that is invoked
+        to acquire a passphrase with which to protect the key.
+        The default is util.passphrase_callback.
+        """
+        bio = BIO.openfile(file, 'wb')
+        return self.save_key_bio(bio, cipher, callback)
+
+    save_pem = save_key
+
+    def as_pem(self, cipher='aes_128_cbc', callback=util.passphrase_callback):
+        """
+        Returns the key(pair) as a string in PEM format.
+        """
+        bio = BIO.MemoryBuffer()
+        self.save_key_bio(bio, cipher, callback)
+        return bio.read()
+
+    def save_key_der_bio(self, bio):
+        """
+        Save the key pair to an M2Crypto.BIO.BIO object in DER format.
+
+        @type bio: M2Crypto.BIO.BIO
+        @param bio: M2Crypto.BIO.BIO object to save key to.
+        """
+        return m2.rsa_write_key_der(self.rsa, bio._ptr())
+
+    def save_key_der(self, file):
+        """
+        Save the key pair to a file in DER format.
+
+        @type file: str
+        @param file: Filename to save key to
+        """
+        bio = BIO.openfile(file, 'wb')
+        return self.save_key_der_bio(bio)
+
+    def save_pub_key_bio(self, bio):
+        """
+        Save the public key to an M2Crypto.BIO.BIO object in PEM format.
+
+        @type bio: M2Crypto.BIO.BIO
+        @param bio: M2Crypto.BIO.BIO object to save key to.
+        """ 
+        return m2.rsa_write_pub_key(self.rsa, bio._ptr())
+
+    def save_pub_key(self, file):
+        """
+        Save the public key to a file in PEM format.
+
+        @type file: string
+        @param file: Name of file to save key to.
+        """
+        bio = BIO.openfile(file, 'wb')
+        return m2.rsa_write_pub_key(self.rsa, bio._ptr())
+
+    def check_key(self):
+        return m2.rsa_check_key(self.rsa)
+
+    def sign_rsassa_pss(self, digest, algo='sha1', salt_length=20):
+        """
+        Signs a digest with the private key using RSASSA-PSS
+        
+        @requires: OpenSSL 0.9.7h or later.
+
+        @type digest: str
+        @param digest: A digest created by using the digest method
+
+        @type salt_length: int
+        @param salt_length: The length of the salt to use
+        
+        @type algo: str
+        @param algo: The hash algorithm to use
+
+        @return: a string which is the signature
+        """
+        hash = getattr(m2, algo, None)
+        if hash is None:
+            raise ValueError('not such hash algorithm %s' % hash_algo) 
+
+        signature = m2.rsa_padding_add_pkcs1_pss(self.rsa, digest, hash(), salt_length)
+        
+        return self.private_encrypt(signature, m2.no_padding) 
+
+    def verify_rsassa_pss(self, data, signature, algo='sha1', salt_length=20):
+        """
+        Verifies the signature RSASSA-PSS
+
+        @requires: OpenSSL 0.9.7h or later.
+
+        @type data: str
+        @param data: Data that has been signed
+
+        @type signature: str
+        @param signature: The signature signed with RSASSA-PSS
+        
+        @type salt_length: int
+        @param salt_length: The length of the salt that was used
+
+        @type algo: str
+        @param algo: The hash algorithm to use
+
+        @return: 1 or 0, depending on whether the signature was
+        verified or not.  
+        """
+        hash = getattr(m2, algo, None)
+        if hash is None:
+            raise ValueError('not such hash algorithm %s' % hash_algo) 
+
+        plain_signature = self.public_decrypt(signature, m2.no_padding)
+         
+        return m2.rsa_verify_pkcs1_pss(self.rsa, data, plain_signature, hash(), salt_length)
+
+    def sign(self, digest, algo='sha1'):
+        """
+        Signs a digest with the private key
+
+        @type digest: str
+        @param digest: A digest created by using the digest method
+
+        @type algo: str
+        @param algo: The method that created the digest.
+        Legal values are 'sha1','sha224', 'sha256', 'ripemd160', 
+        and 'md5'.
+        
+        @return: a string which is the signature
+        """
+        digest_type = getattr(m2, 'NID_' + algo, None) 
+        if digest_type is None:
+            raise ValueError, ('unknown algorithm', algo)
+        
+        return m2.rsa_sign(self.rsa, digest, digest_type) 
+    
+    def verify(self, data, signature, algo='sha1'):
+        """
+        Verifies the signature with the public key
+
+        @type data: str
+        @param data: Data that has been signed
+
+        @type signature: str
+        @param signature: The signature signed with the private key
+
+        @type algo: str
+        @param algo: The method use to create digest from the data 
+        before it was signed.  Legal values are 'sha1','sha224',
+        'sha256', 'ripemd160', and 'md5'.
+
+        @return: True or False, depending on whether the signature was
+        verified.  
+        """
+        digest_type = getattr(m2, 'NID_' + algo, None)
+        if digest_type is None:
+            raise ValueError, ('unknown algorithm', algo)
+        
+        return m2.rsa_verify(self.rsa, data, signature, digest_type) 
+
+
+class RSA_pub(RSA):
+
+    """
+    Object interface to an RSA public key.
+    """
+
+    def __setattr__(self, name, value):
+        if name in ['e', 'n']:
+            raise RSAError, \
+                'use factory function new_pub_key() to set (e, n)'
+        else:
+            self.__dict__[name] = value
+        
+    def private_encrypt(self, *argv):
+        raise RSAError, 'RSA_pub object has no private key'
+
+    def private_decrypt(self, *argv):
+        raise RSAError, 'RSA_pub object has no private key'
+
+    def save_key(self, file, *args, **kw):
+        """
+        Save public key to file.
+        """
+        return self.save_pub_key(file)
+
+    def save_key_bio(self, bio, *args, **kw):
+        """
+        Save public key to BIO.
+        """
+        return self.save_pub_key_bio(bio)
+
+    #save_key_der
+
+    #save_key_der_bio
+
+    def check_key(self):
+        return m2.rsa_check_pub_key(self.rsa)
+
+
+def rsa_error():
+    raise RSAError, m2.err_reason_error_string(m2.err_get_error())
+
+
+def keygen_callback(p, n, out=sys.stdout):
+    """
+    Default callback for gen_key().
+    """
+    ch = ['.','+','*','\n']
+    out.write(ch[p])
+    out.flush()
+
+
+def gen_key(bits, e, callback=keygen_callback):
+    """
+    Generate an RSA key pair.
+
+    @type bits: int
+    @param bits: Key length, in bits.
+
+    @type e: int
+    @param e: The RSA public exponent.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    during key generation; its usual purpose is to provide visual
+    feedback. The default callback is keygen_callback.
+
+    @rtype: M2Crypto.RSA.RSA
+    @return: M2Crypto.RSA.RSA object.
+    """ 
+    return RSA(m2.rsa_generate_key(bits, e, callback), 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+    """
+    Load an RSA key pair from file.
+
+    @type file: string
+    @param file: Name of file containing RSA public key in PEM format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to unlock the key.
+    The default is util.passphrase_callback.
+
+    @rtype: M2Crypto.RSA.RSA
+    @return: M2Crypto.RSA.RSA object.
+    """
+    bio = BIO.openfile(file)
+    return load_key_bio(bio, callback)
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+    """
+    Load an RSA key pair from an M2Crypto.BIO.BIO object.
+
+    @type bio: M2Crypto.BIO.BIO
+    @param bio: M2Crypto.BIO.BIO object containing RSA key pair in PEM
+    format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to unlock the key.
+    The default is util.passphrase_callback.
+
+    @rtype: M2Crypto.RSA.RSA
+    @return: M2Crypto.RSA.RSA object.
+    """
+    rsa = m2.rsa_read_key(bio._ptr(), callback)
+    if rsa is None:
+        rsa_error()
+    return RSA(rsa, 1)
+
+
+def load_key_string(string, callback=util.passphrase_callback):
+    """
+    Load an RSA key pair from a string.
+
+    @type string: string
+    @param string: String containing RSA key pair in PEM format.
+
+    @type callback: Python callable
+    @param callback: A Python callable object that is invoked
+    to acquire a passphrase with which to unlock the key.
+    The default is util.passphrase_callback.
+
+    @rtype: M2Crypto.RSA.RSA
+    @return: M2Crypto.RSA.RSA object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    return load_key_bio(bio, callback)
+
+
+def load_pub_key(file):
+    """
+    Load an RSA public key from file.
+
+    @type file: string
+    @param file: Name of file containing RSA public key in PEM format.
+
+    @rtype: M2Crypto.RSA.RSA_pub
+    @return: M2Crypto.RSA.RSA_pub object.
+    """
+    bio = BIO.openfile(file) 
+    return load_pub_key_bio(bio)
+
+
+def load_pub_key_bio(bio):
+    """
+    Load an RSA public key from an M2Crypto.BIO.BIO object.
+
+    @type bio: M2Crypto.BIO.BIO
+    @param bio: M2Crypto.BIO.BIO object containing RSA public key in PEM
+    format.
+
+    @rtype: M2Crypto.RSA.RSA_pub
+    @return: M2Crypto.RSA.RSA_pub object.
+    """ 
+    rsa = m2.rsa_read_pub_key(bio._ptr())
+    if rsa is None:
+        rsa_error()
+    return RSA_pub(rsa, 1)
+
+
+def new_pub_key((e, n)):
+    """
+    Instantiate an RSA_pub object from an (e, n) tuple.
+
+    @type e: string
+    @param e: The RSA public exponent; it is a string in OpenSSL's MPINT 
+    format - 4-byte big-endian bit-count followed by the appropriate 
+    number of bits.
+
+    @type n: string
+    @param n: The RSA composite of primes; it is a string in OpenSSL's MPINT 
+    format - 4-byte big-endian bit-count followed by the appropriate 
+    number of bits.
+
+    @rtype: M2Crypto.RSA.RSA_pub
+    @return: M2Crypto.RSA.RSA_pub object.
+    """ 
+    rsa = m2.rsa_new()
+    m2.rsa_set_e(rsa, e)
+    m2.rsa_set_n(rsa, n)
+    return RSA_pub(rsa, 1)
+
+
diff --git a/M2Crypto/Rand.py b/M2Crypto/Rand.py
new file mode 100644 (file)
index 0000000..1e6a918
--- /dev/null
@@ -0,0 +1,17 @@
+"""M2Crypto wrapper for OpenSSL PRNG. Requires OpenSSL 0.9.5 and above.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['rand_seed', 'rand_add', 'load_file', 'save_file', 'rand_bytes',
+           'rand_pseudo_bytes']
+
+import m2
+
+rand_seed           = m2.rand_seed
+rand_add            = m2.rand_add
+load_file           = m2.rand_load_file
+save_file           = m2.rand_save_file
+rand_bytes          = m2.rand_bytes
+rand_pseudo_bytes   = m2.rand_pseudo_bytes
+
+
diff --git a/M2Crypto/SMIME.py b/M2Crypto/SMIME.py
new file mode 100644 (file)
index 0000000..556cd8f
--- /dev/null
@@ -0,0 +1,245 @@
+"""M2Crypto wrapper for OpenSSL S/MIME API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import BIO, EVP, X509, Err, util
+import m2
+
+PKCS7_TEXT     = m2.PKCS7_TEXT
+PKCS7_NOCERTS  = m2.PKCS7_NOCERTS
+PKCS7_NOSIGS   = m2.PKCS7_NOSIGS
+PKCS7_NOCHAIN  = m2.PKCS7_NOCHAIN
+PKCS7_NOINTERN = m2.PKCS7_NOINTERN
+PKCS7_NOVERIFY = m2.PKCS7_NOVERIFY
+PKCS7_DETACHED = m2.PKCS7_DETACHED
+PKCS7_BINARY   = m2.PKCS7_BINARY
+PKCS7_NOATTR   = m2.PKCS7_NOATTR
+
+PKCS7_SIGNED           = m2.PKCS7_SIGNED
+PKCS7_ENVELOPED                = m2.PKCS7_ENVELOPED
+PKCS7_SIGNED_ENVELOPED = m2.PKCS7_SIGNED_ENVELOPED # Deprecated
+PKCS7_DATA             = m2.PKCS7_DATA 
+
+class PKCS7_Error(Exception): pass
+
+m2.pkcs7_init(PKCS7_Error)
+
+class PKCS7:
+
+    m2_pkcs7_free = m2.pkcs7_free
+
+    def __init__(self, pkcs7=None, _pyfree=0):
+        if pkcs7 is not None:
+            self.pkcs7 = pkcs7
+            self._pyfree = _pyfree
+        else:
+            self.pkcs7 = m2.pkcs7_new()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_pkcs7_free(self.pkcs7)
+
+    def _ptr(self):
+        return self.pkcs7
+
+    def type(self, text_name=0):
+        if text_name:
+            return m2.pkcs7_type_sn(self.pkcs7)
+        else:
+            return m2.pkcs7_type_nid(self.pkcs7)
+
+    def write(self, bio):
+        return m2.pkcs7_write_bio(self.pkcs7, bio._ptr())
+
+    def write_der(self, bio):
+        return m2.pkcs7_write_bio_der(self.pkcs7, bio._ptr())
+
+    def get0_signers(self, certs, flags = 0):      
+        return X509.X509_Stack(m2.pkcs7_get0_signers(self.pkcs7,
+                                                     certs.stack, flags), 1)
+
+
+def load_pkcs7(p7file):
+    bio = m2.bio_new_file(p7file, 'r')
+    if bio is None:
+        raise BIO.BIOError(Err.get_error())
+
+    try:
+        p7_ptr = m2.pkcs7_read_bio(bio)
+    finally:
+        m2.bio_free(bio)
+        
+    if p7_ptr is None:
+        raise PKCS7_Error(Err.get_error())
+    return PKCS7(p7_ptr, 1)
+    
+
+def load_pkcs7_bio(p7_bio):
+    p7_ptr = m2.pkcs7_read_bio(p7_bio._ptr())
+    if p7_ptr is None:
+        raise PKCS7_Error(Err.get_error())
+    return PKCS7(p7_ptr, 1)
+    
+
+def smime_load_pkcs7(p7file):
+    bio = m2.bio_new_file(p7file, 'r')
+    if bio is None:
+        raise BIO.BIOError(Err.get_error())
+    
+    try:
+        p7_ptr, bio_ptr = m2.smime_read_pkcs7(bio)
+    finally:
+        m2.bio_free(bio)
+    
+    if p7_ptr is None:
+        raise SMIME_Error(Err.get_error())
+    if bio_ptr is None:
+        return PKCS7(p7_ptr, 1), None
+    else:
+        return PKCS7(p7_ptr, 1), BIO.BIO(bio_ptr, 1)
+    
+
+def smime_load_pkcs7_bio(p7_bio):
+    p7_ptr, bio_ptr = m2.smime_read_pkcs7(p7_bio._ptr())
+    if p7_ptr is None:
+        raise SMIME_Error(Err.get_error())
+    if bio_ptr is None:
+        return PKCS7(p7_ptr, 1), None
+    else:
+        return PKCS7(p7_ptr, 1), BIO.BIO(bio_ptr, 1)
+    
+            
+class Cipher:
+
+    """
+    Object interface to EVP_CIPHER without all the frills of M2Crypto.EVP.Cipher.
+    """
+
+    def __init__(self, algo):
+        cipher = getattr(m2, algo, None)
+        if cipher is None:
+            raise ValueError, ('unknown cipher', algo)
+        self.cipher = cipher()
+
+    def _ptr(self):
+        return self.cipher
+
+
+class SMIME_Error(Exception): pass
+
+m2.smime_init(SMIME_Error)
+
+class SMIME:
+    def load_key(self, keyfile, certfile=None, callback=util.passphrase_callback):
+        if certfile is None:
+            certfile = keyfile
+        self.pkey = EVP.load_key(keyfile, callback)
+        self.x509 = X509.load_cert(certfile)
+
+    def load_key_bio(self, keybio, certbio=None, callback=util.passphrase_callback):
+        if certbio is None:
+            certbio = keybio
+        self.pkey = EVP.load_key_bio(keybio, callback)
+        self.x509 = X509.load_cert_bio(certbio)
+
+    def set_x509_stack(self, stack):
+        assert isinstance(stack, X509.X509_Stack)
+        self.x509_stack = stack
+
+    def set_x509_store(self, store):
+        assert isinstance(store, X509.X509_Store)
+        self.x509_store = store
+
+    def set_cipher(self, cipher):
+        assert isinstance(cipher, Cipher)
+        self.cipher = cipher
+
+    def unset_key(self):
+        del self.pkey
+        del self.x509
+
+    def unset_x509_stack(self):
+        del self.x509_stack
+
+    def unset_x509_store(self):
+        del self.x509_store
+
+    def unset_cipher(self):
+        del self.cipher
+
+    def encrypt(self, data_bio, flags=0):
+        if not hasattr(self, 'cipher'):
+            raise SMIME_Error, 'no cipher: use set_cipher()'
+        if not hasattr(self, 'x509_stack'):
+            raise SMIME_Error, 'no recipient certs: use set_x509_stack()'
+        pkcs7 = m2.pkcs7_encrypt(self.x509_stack._ptr(), data_bio._ptr(), self.cipher._ptr(), flags)
+        if pkcs7 is None:
+            raise SMIME_Error(Err.get_error())
+        return PKCS7(pkcs7, 1)
+
+    def decrypt(self, pkcs7, flags=0):
+        if not hasattr(self, 'pkey'):
+            raise SMIME_Error, 'no private key: use load_key()'
+        if not hasattr(self, 'x509'):
+            raise SMIME_Error, 'no certificate: load_key() used incorrectly?'
+        blob = m2.pkcs7_decrypt(pkcs7._ptr(), self.pkey._ptr(), self.x509._ptr(), flags)
+        if blob is None:
+            raise SMIME_Error(Err.get_error())
+        return blob
+
+    def sign(self, data_bio, flags=0):
+        if not hasattr(self, 'pkey'):
+            raise SMIME_Error, 'no private key: use load_key()'
+        if hasattr(self, 'x509_stack'):
+            pkcs7 = m2.pkcs7_sign1(self.x509._ptr(), self.pkey._ptr(), 
+                self.x509_stack._ptr(), data_bio._ptr(), flags)
+            if pkcs7 is None:
+                raise SMIME_Error(Err.get_error())
+            return PKCS7(pkcs7, 1)
+        else:
+            pkcs7 = m2.pkcs7_sign0(self.x509._ptr(), self.pkey._ptr(), 
+                data_bio._ptr(), flags)
+            if pkcs7 is None:
+                raise SMIME_Error(Err.get_error())
+            return PKCS7(pkcs7, 1)
+
+    def verify(self, pkcs7, data_bio=None, flags=0):
+        if not hasattr(self, 'x509_stack'):
+            raise SMIME_Error, 'no signer certs: use set_x509_stack()'
+        if not hasattr(self, 'x509_store'):
+            raise SMIME_Error, 'no x509 cert store: use set_x509_store()'
+        assert isinstance(pkcs7, PKCS7), 'pkcs7 not an instance of PKCS7'
+        p7 = pkcs7._ptr()
+        if data_bio is None:
+            blob = m2.pkcs7_verify0(p7, self.x509_stack._ptr(), self.x509_store._ptr(), flags)
+        else:
+            blob = m2.pkcs7_verify1(p7, self.x509_stack._ptr(), self.x509_store._ptr(), data_bio._ptr(), flags)
+        if blob is None:
+            raise SMIME_Error(Err.get_error())
+        return blob
+
+    def write(self, out_bio, pkcs7, data_bio=None, flags=0):
+        assert isinstance(pkcs7, PKCS7)
+        if data_bio is None:
+            return m2.smime_write_pkcs7(out_bio._ptr(), pkcs7._ptr(), flags)
+        else:
+            return m2.smime_write_pkcs7_multi(out_bio._ptr(), pkcs7._ptr(), data_bio._ptr(), flags)
+
+
+def text_crlf(text):
+    bio_in = BIO.MemoryBuffer(text)
+    bio_out = BIO.MemoryBuffer()
+    if m2.smime_crlf_copy(bio_in._ptr(), bio_out._ptr()):
+        return bio_out.read()
+    else:
+        raise SMIME_Error(Err.get_error())
+
+
+def text_crlf_bio(bio_in):
+    bio_out = BIO.MemoryBuffer()
+    if m2.smime_crlf_copy(bio_in._ptr(), bio_out._ptr()):
+        return bio_out
+    else:
+        raise SMIME_Error(Err.get_error())
+
diff --git a/M2Crypto/SSL/Checker.py b/M2Crypto/SSL/Checker.py
new file mode 100644 (file)
index 0000000..5218663
--- /dev/null
@@ -0,0 +1,224 @@
+"""
+SSL peer certificate checking routines
+
+Copyright (c) 2004-2007 Open Source Applications Foundation.
+All rights reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['SSLVerificationError', 'NoCertificate', 'WrongCertificate',
+           'WrongHost', 'Checker']
+
+from M2Crypto import util, EVP, m2
+import re
+
+class SSLVerificationError(Exception):
+    pass
+
+class NoCertificate(SSLVerificationError):
+    pass
+
+class WrongCertificate(SSLVerificationError):
+    pass
+
+class WrongHost(SSLVerificationError):
+    def __init__(self, expectedHost, actualHost, fieldName='commonName'):
+        """
+        This exception will be raised if the certificate returned by the
+        peer was issued for a different host than we tried to connect to.
+        This could be due to a server misconfiguration or an active attack.
+        
+        @param expectedHost: The name of the host we expected to find in the
+                             certificate.
+        @param actualHost:   The name of the host we actually found in the
+                             certificate.
+        @param fieldName:    The field name where we noticed the error. This
+                             should be either 'commonName' or 'subjectAltName'.
+        """
+        if fieldName not in ('commonName', 'subjectAltName'):
+            raise ValueError('Unknown fieldName, should be either commonName or subjectAltName')
+        
+        SSLVerificationError.__init__(self)
+        self.expectedHost = expectedHost
+        self.actualHost = actualHost
+        self.fieldName = fieldName
+        
+    def __str__(self):
+        s = 'Peer certificate %s does not match host, expected %s, got %s' \
+               % (self.fieldName, self.expectedHost, self.actualHost)
+        if isinstance(s, unicode):
+            s = s.encode('utf8')
+        return s
+
+
+class Checker:
+    
+    numericIpMatch = re.compile('^[0-9]+(\.[0-9]+)*$')
+    
+    def __init__(self, host=None, peerCertHash=None, peerCertDigest='sha1'):
+        self.host = host
+        self.fingerprint = peerCertHash
+        self.digest = peerCertDigest
+
+    def __call__(self, peerCert, host=None):
+        if peerCert is None:
+            raise NoCertificate('peer did not return certificate')
+
+        if host is not None:
+            self.host = host
+        
+        if self.fingerprint:
+            if self.digest not in ('sha1', 'md5'):
+                raise ValueError('unsupported digest "%s"' %(self.digest))
+
+            if (self.digest == 'sha1' and len(self.fingerprint) != 40) or \
+               (self.digest == 'md5' and len(self.fingerprint) != 32):
+                raise WrongCertificate('peer certificate fingerprint length does not match')
+            
+            der = peerCert.as_der()
+            md = EVP.MessageDigest(self.digest)
+            md.update(der)
+            digest = md.final()
+            if util.octx_to_num(digest) != int(self.fingerprint, 16):
+                raise WrongCertificate('peer certificate fingerprint does not match')
+
+        if self.host:
+            hostValidationPassed = False
+            self.useSubjectAltNameOnly = False
+
+            # subjectAltName=DNS:somehost[, ...]*
+            try:
+                subjectAltName = peerCert.get_ext('subjectAltName').get_value()
+                if self._splitSubjectAltName(self.host, subjectAltName):
+                    hostValidationPassed = True
+                elif self.useSubjectAltNameOnly:
+                    raise WrongHost(expectedHost=self.host, 
+                                    actualHost=subjectAltName,
+                                    fieldName='subjectAltName')
+            except LookupError:
+                pass
+
+            # commonName=somehost[, ...]*
+            if not hostValidationPassed:
+                hasCommonName = False
+                commonNames = ''
+                for entry in peerCert.get_subject().get_entries_by_nid(m2.NID_commonName):
+                    hasCommonName = True
+                    commonName = entry.get_data().as_text()
+                    if not commonNames:
+                        commonNames = commonName
+                    else:
+                        commonNames += ',' + commonName
+                    if self._match(self.host, commonName):
+                        hostValidationPassed = True
+                        break
+
+                if not hasCommonName:
+                    raise WrongCertificate('no commonName in peer certificate')
+
+                if not hostValidationPassed:
+                    raise WrongHost(expectedHost=self.host,
+                                    actualHost=commonNames,
+                                    fieldName='commonName')
+
+        return True
+
+    def _splitSubjectAltName(self, host, subjectAltName):
+        """
+        >>> check = Checker()
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com')
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:*.example.com')
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*.example.com')
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com')
+        False
+        >>> check.useSubjectAltNameOnly
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, othername:<unsupported>')
+        False
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.org')
+        False
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.com')
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com, DNS:my.example.org')
+        True
+        >>> check.useSubjectAltNameOnly
+        True
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='')
+        False
+        >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='othername:<unsupported>')
+        False
+        >>> check.useSubjectAltNameOnly
+        False
+        """
+        self.useSubjectAltNameOnly = False
+        for certHost in subjectAltName.split(','):
+            certHost = certHost.lower().strip()
+            if certHost[:4] == 'dns:':
+                self.useSubjectAltNameOnly = True
+                if self._match(host, certHost[4:]):
+                    return True
+        return False
+        
+
+    def _match(self, host, certHost):
+        """
+        >>> check = Checker()
+        >>> check._match(host='my.example.com', certHost='my.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='*.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*.EXAMPLE.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*ample.com')
+        False
+        >>> check._match(host='my.example.com', certHost='*.*.com')
+        False
+        >>> check._match(host='1.2.3.4', certHost='1.2.3.4')
+        True
+        >>> check._match(host='1.2.3.4', certHost='*.2.3.4')
+        False
+        >>> check._match(host='1234', certHost='1234')
+        True
+        """
+        # XXX See RFC 2818 and 3280 for matching rules, this is may not
+        # XXX yet be complete.
+
+        host = host.lower()
+        certHost = certHost.lower()
+
+        if host == certHost:
+            return True
+
+        if certHost.count('*') > 1:
+            # Not sure about this, but being conservative
+            return False
+
+        if self.numericIpMatch.match(host) or \
+               self.numericIpMatch.match(certHost.replace('*', '')):
+            # Not sure if * allowed in numeric IP, but think not.
+            return False
+
+        if certHost.find('\\') > -1:
+            # Not sure about this, maybe some encoding might have these.
+            # But being conservative for now, because regex below relies
+            # on this.
+            return False
+
+        # Massage certHost so that it can be used in regex
+        certHost = certHost.replace('.', '\.')
+        certHost = certHost.replace('*', '[^\.]*')
+        if re.compile('^%s$' %(certHost)).match(host):
+            return True
+
+        return False
+
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
diff --git a/M2Crypto/SSL/Cipher.py b/M2Crypto/SSL/Cipher.py
new file mode 100644 (file)
index 0000000..2d2c088
--- /dev/null
@@ -0,0 +1,44 @@
+"""SSL Ciphers
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['Cipher', 'Cipher_Stack']
+
+from M2Crypto import m2
+
+class Cipher:
+    def __init__(self, cipher):
+        self.cipher=cipher
+
+    def __len__(self):
+        return m2.ssl_cipher_get_bits(self.cipher)
+
+    def __repr__(self):
+        return "%s-%s" % (self.name(), len(self))
+
+    def __str__(self):
+        return "%s-%s" % (self.name(), len(self))
+
+    def version(self):
+        return m2.ssl_cipher_get_version(self.cipher)
+
+    def name(self):
+        return m2.ssl_cipher_get_name(self.cipher)
+
+
+class Cipher_Stack:
+    def __init__(self, stack):
+        self.stack=stack
+
+    def __len__(self):
+        return m2.sk_ssl_cipher_num(self.stack)
+
+    def __getitem__(self, idx):
+        if not 0 <= idx < m2.sk_ssl_cipher_num(self.stack):
+            raise IndexError('index out of range')
+        v=m2.sk_ssl_cipher_value(self.stack, idx)
+        return Cipher(v)
+
+    def __iter__(self):
+        for i in xrange(m2.sk_ssl_cipher_num(self.stack)):
+            yield self[i]
diff --git a/M2Crypto/SSL/Connection.py b/M2Crypto/SSL/Connection.py
new file mode 100644 (file)
index 0000000..ef6a2c8
--- /dev/null
@@ -0,0 +1,361 @@
+"""SSL Connection aka socket
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['Connection',
+           'timeout', # XXX Not really, but for documentation purposes
+           ]
+
+# Python
+import socket
+
+# M2Crypto
+from Cipher import Cipher, Cipher_Stack
+from Session import Session
+from M2Crypto import BIO, X509, m2
+import timeout
+import Checker
+
+#SSLError = getattr(__import__('M2Crypto.SSL', globals(), locals(), 'SSLError'), 'SSLError')
+from M2Crypto.SSL import SSLError
+
+def _serverPostConnectionCheck(*args, **kw):
+    return 1
+
+class Connection:
+
+    """An SSL connection."""
+
+    clientPostConnectionCheck = Checker.Checker()
+    serverPostConnectionCheck = _serverPostConnectionCheck
+
+    m2_bio_free = m2.bio_free
+    m2_ssl_free = m2.ssl_free
+    
+    def __init__(self, ctx, sock=None):
+        self.ctx = ctx
+        self.ssl = m2.ssl_new(self.ctx.ctx)
+        if sock is not None:    
+            self.socket = sock
+        else:
+            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self._fileno = self.socket.fileno()
+        
+        self.blocking = self.socket.gettimeout()
+        
+        self.ssl_close_flag = m2.bio_noclose
+
+        
+    def __del__(self):
+        if getattr(self, 'sslbio', None):
+            self.m2_bio_free(self.sslbio)
+        if getattr(self, 'sockbio', None):
+            self.m2_bio_free(self.sockbio)
+        if self.ssl_close_flag == m2.bio_noclose and getattr(self, 'ssl', None):
+            self.m2_ssl_free(self.ssl)
+        self.socket.close()
+
+    def close(self):
+        m2.ssl_shutdown(self.ssl)
+
+    def clear(self):
+        """
+        If there were errors in this connection, call clear() rather
+        than close() to end it, so that bad sessions will be cleared
+        from cache.
+        """
+        return m2.ssl_clear(self.ssl)
+
+    def set_shutdown(self, mode):
+        m2.ssl_set_shutdown1(self.ssl, mode)
+
+    def get_shutdown(self):
+        return m2.ssl_get_shutdown(self.ssl)
+
+    def bind(self, addr):
+        self.socket.bind(addr)
+
+    def listen(self, qlen=5):
+        self.socket.listen(qlen)    
+
+    def ssl_get_error(self, ret):
+        return m2.ssl_get_error(self.ssl, ret)
+
+    def set_bio(self, readbio, writebio):
+        """
+        Explicitly set read and write bios
+        """
+        m2.ssl_set_bio(self.ssl, readbio._ptr(), writebio._ptr())
+        
+    def set_client_CA_list_from_file(self, cafile):
+        """
+        Set the acceptable client CA list. If the client
+        returns a certificate, it must have been issued by
+        one of the CAs listed in cafile.
+        
+        Makes sense only for servers.
+        
+        @param cafile: Filename from which to load the CA list.
+        """
+        m2.ssl_set_client_CA_list_from_file(self.ssl, cafile)
+
+    def set_client_CA_list_from_context(self):
+        """
+        Set the acceptable client CA list. If the client
+        returns a certificate, it must have been issued by
+        one of the CAs listed in context.
+        
+        Makes sense only for servers.
+        """
+        m2.ssl_set_client_CA_list_from_context(self.ssl, self.ctx.ctx)
+
+    def setup_addr(self, addr):
+        self.addr = addr
+
+    def set_ssl_close_flag(self, flag):
+        """
+        By default, SSL struct will be freed in __del__. Call with
+        m2.bio_close to override this default.
+        """
+        if flag not in (m2.bio_close, m2.bio_noclose):
+            raise ValueError("flag must be m2.bio_close or m2.bio_noclose")
+        self.ssl_close_flag = flag
+
+    def setup_ssl(self):
+        # Make a BIO_s_socket.
+        self.sockbio = m2.bio_new_socket(self.socket.fileno(), 0)
+        # Link SSL struct with the BIO_socket.
+        m2.ssl_set_bio(self.ssl, self.sockbio, self.sockbio)
+        # Make a BIO_f_ssl.
+        self.sslbio = m2.bio_new(m2.bio_f_ssl())
+        # Link BIO_f_ssl with the SSL struct.
+        m2.bio_set_ssl(self.sslbio, self.ssl, m2.bio_noclose)
+
+    def _setup_ssl(self, addr):
+        """Deprecated"""
+        self.setup_addr(addr)
+        self.setup_ssl()
+
+    def set_accept_state(self):
+        m2.ssl_set_accept_state(self.ssl)
+
+    def accept_ssl(self):
+        return m2.ssl_accept(self.ssl)
+
+    def accept(self):
+        """Accept an SSL connection. The return value is a pair (ssl, addr) where
+        ssl is a new SSL connection object and addr is the address bound to
+        the other end of the SSL connection."""
+        sock, addr = self.socket.accept()
+        ssl = Connection(self.ctx, sock)
+        ssl.addr = addr
+        ssl.setup_ssl()
+        ssl.set_accept_state()
+        ssl.accept_ssl()
+        check = getattr(self, 'postConnectionCheck', self.serverPostConnectionCheck)
+        if check is not None:
+            if not check(ssl.get_peer_cert(), ssl.addr[0]):
+                raise Checker.SSLVerificationError, 'post connection check failed'
+        return ssl, addr
+
+    def set_connect_state(self):
+        m2.ssl_set_connect_state(self.ssl)
+
+    def connect_ssl(self):
+        return m2.ssl_connect(self.ssl)
+
+    def connect(self, addr):
+        self.socket.connect(addr)
+        self.addr = addr
+        self.setup_ssl()
+        self.set_connect_state()
+        ret = self.connect_ssl()
+        check = getattr(self, 'postConnectionCheck', self.clientPostConnectionCheck)
+        if check is not None:
+            if not check(self.get_peer_cert(), self.addr[0]):
+                raise Checker.SSLVerificationError, 'post connection check failed'
+        return ret
+
+    def shutdown(self, how):
+        m2.ssl_set_shutdown(self.ssl, how)
+
+    def renegotiate(self):
+        """Renegotiate this connection's SSL parameters."""
+        return m2.ssl_renegotiate(self.ssl)
+
+    def pending(self):
+        """Return the numbers of octets that can be read from the 
+        connection."""
+        return m2.ssl_pending(self.ssl)
+
+    def _write_bio(self, data):
+        return m2.ssl_write(self.ssl, data)
+
+    def _write_nbio(self, data):
+        return m2.ssl_write_nbio(self.ssl, data)
+
+    def _read_bio(self, size=1024):
+        if size <= 0:
+            raise ValueError, 'size <= 0'
+        return m2.ssl_read(self.ssl, size)
+
+    def _read_nbio(self, size=1024):
+        if size <= 0:
+            raise ValueError, 'size <= 0'
+        return m2.ssl_read_nbio(self.ssl, size)
+
+    def write(self, data):
+        if self.blocking:
+            return self._write_bio(data)
+        return self._write_nbio(data)
+    sendall = send = write
+    
+    def read(self, size=1024):
+        if self.blocking:
+            return self._read_bio(size)
+        return self._read_nbio(size)
+    recv = read
+
+    def setblocking(self, mode):
+        """Set this connection's underlying socket to _mode_."""
+        self.socket.setblocking(mode)
+        self.blocking = mode
+
+    def fileno(self):
+        return self.socket.fileno()
+
+    def getsockopt(self, *args):
+        return apply(self.socket.getsockopt, args)
+
+    def setsockopt(self, *args):
+        return apply(self.socket.setsockopt, args)
+
+    def get_context(self):
+        """Return the SSL.Context object associated with this 
+        connection."""
+        return m2.ssl_get_ssl_ctx(self.ssl)
+
+    def get_state(self):
+        """Return the SSL state of this connection."""
+        return m2.ssl_get_state(self.ssl)
+
+    def verify_ok(self):
+        return (m2.ssl_get_verify_result(self.ssl) == m2.X509_V_OK)
+
+    def get_verify_mode(self):
+        """Return the peer certificate verification mode."""
+        return m2.ssl_get_verify_mode(self.ssl)
+
+    def get_verify_depth(self):
+        """Return the peer certificate verification depth."""
+        return m2.ssl_get_verify_depth(self.ssl)
+
+    def get_verify_result(self):
+        """Return the peer certificate verification result."""
+        return m2.ssl_get_verify_result(self.ssl)
+
+    def get_peer_cert(self):
+        """Return the peer certificate; if the peer did not provide 
+        a certificate, return None."""
+        c=m2.ssl_get_peer_cert(self.ssl)
+        if c is None:
+            return None
+        # Need to free the pointer coz OpenSSL doesn't.
+        return X509.X509(c, 1)
+    
+    def get_peer_cert_chain(self):
+        """Return the peer certificate chain; if the peer did not provide 
+        a certificate chain, return None.
+        
+        @warning: The returned chain will be valid only for as long as the
+        connection object is alive. Once the connection object gets freed,
+        the chain will be freed as well.
+        """
+        c=m2.ssl_get_peer_cert_chain(self.ssl)
+        if c is None:
+            return None
+        # No need to free the pointer coz OpenSSL does.
+        return X509.X509_Stack(c)
+    
+    def get_cipher(self):
+        """Return an M2Crypto.SSL.Cipher object for this connection; if the 
+        connection has not been initialised with a cipher suite, return None."""
+        c=m2.ssl_get_current_cipher(self.ssl)
+        if c is None:
+            return None
+        return Cipher(c)
+    
+    def get_ciphers(self):
+        """Return an M2Crypto.SSL.Cipher_Stack object for this connection; if the
+        connection has not been initialised with cipher suites, return None."""
+        c=m2.ssl_get_ciphers(self.ssl)
+        if c is None:
+            return None
+        return Cipher_Stack(c)
+
+    def get_cipher_list(self, idx=0):
+        """Return the cipher suites for this connection as a string object."""
+        return m2.ssl_get_cipher_list(self.ssl, idx)
+
+    def set_cipher_list(self, cipher_list):
+        """Set the cipher suites for this connection."""
+        return m2.ssl_set_cipher_list(self.ssl, cipher_list)
+
+    def makefile(self, mode='rb', bufsize='ignored'):
+        r = 'r' in mode or '+' in mode
+        w = 'w' in mode or 'a' in mode or '+' in mode
+        b = 'b' in mode
+        m2mode = ['', 'r'][r] + ['', 'w'][w] + ['', 'b'][b]      
+        # XXX Need to dup().
+        bio = BIO.BIO(self.sslbio, _close_cb=self.close)
+        m2.bio_do_handshake(bio._ptr())
+        return BIO.IOBuffer(bio, m2mode, _pyfree=0)
+
+    def getsockname(self):
+        return self.socket.getsockname()
+
+    def getpeername(self):
+        return self.socket.getpeername()
+
+    def set_session_id_ctx(self, id):
+        ret = m2.ssl_set_session_id_context(self.ssl, id)
+        if not ret:
+            raise SSLError(m2.err_reason_error_string(m2.err_get_error()))
+
+    def get_session(self):
+        sess = m2.ssl_get_session(self.ssl)
+        return Session(sess)
+
+    def set_session(self, session):
+        m2.ssl_set_session(self.ssl, session._ptr())
+
+    def get_default_session_timeout(self):
+        return m2.ssl_get_default_session_timeout(self.ssl)
+
+    def get_socket_read_timeout(self):
+        return timeout.struct_to_timeout(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout.struct_size()))
+
+    def get_socket_write_timeout(self):
+        return timeout.struct_to_timeout(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeout.struct_size()))
+
+    def set_socket_read_timeout(self, timeo):
+        assert isinstance(timeo, timeout.timeout)
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeo.pack())
+
+    def set_socket_write_timeout(self, timeo):
+        assert isinstance(timeo, timeout.timeout)
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeo.pack())
+
+    def get_version(self):
+        "Return the TLS/SSL protocol version for this connection."
+        return m2.ssl_get_version(self.ssl)
+
+    def set_post_connection_check_callback(self, postConnectionCheck):
+        self.postConnectionCheck = postConnectionCheck
diff --git a/M2Crypto/SSL/Context.py b/M2Crypto/SSL/Context.py
new file mode 100644 (file)
index 0000000..b8d159f
--- /dev/null
@@ -0,0 +1,254 @@
+"""SSL Context
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['map', 'Context']
+
+from weakref import WeakValueDictionary
+
+# M2Crypto
+import cb
+from M2Crypto import util, BIO, Err, RSA, m2, X509
+
+class _ctxmap:
+    singleton = None
+    def __init__(self):
+        self.map = WeakValueDictionary()
+
+    def __getitem__(self, key):
+        return self.map[key] 
+
+    def __setitem__(self, key, value):
+        self.map[key] = value
+
+    def __delitem__(self, key):
+        del self.map[key]
+
+def map():
+    if _ctxmap.singleton is None:
+        _ctxmap.singleton = _ctxmap()
+    return _ctxmap.singleton
+
+
+class Context:
+
+    """'Context' for SSL connections."""
+
+    m2_ssl_ctx_free = m2.ssl_ctx_free
+
+    def __init__(self, protocol='sslv23', weak_crypto=None):
+        proto = getattr(m2, protocol + '_method', None)
+        if proto is None:
+            raise ValueError, "no such protocol '%s'" % protocol
+        self.ctx = m2.ssl_ctx_new(proto())
+        self.allow_unknown_ca = 0
+        map()[long(self.ctx)] = self
+        m2.ssl_ctx_set_cache_size(self.ctx, 128L)
+        if weak_crypto is None:
+            if protocol == 'sslv23':
+                self.set_options(m2.SSL_OP_ALL | m2.SSL_OP_NO_SSLv2)
+            self.set_cipher_list('ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH')
+        
+    def __del__(self):
+        if getattr(self, 'ctx', None):
+            self.m2_ssl_ctx_free(self.ctx)
+
+    def close(self):
+        del map()[long(self.ctx)]
+        
+    def load_cert(self, certfile, keyfile=None, callback=util.passphrase_callback):
+        """Load certificate and private key into the context.
+        
+        @param certfile: File that contains the PEM-encoded certificate.
+        @type certfile:  str
+        @param keyfile:  File that contains the PEM-encoded private key.
+                         Default value of None indicates that the private key
+                         is to be found in 'certfile'.
+        @type keyfile:   str
+
+        @param callback: Callable object to be invoked if the private key is
+                         passphrase-protected. Default callback provides a
+                         simple terminal-style input for the passphrase.
+        """
+        m2.ssl_ctx_passphrase_callback(self.ctx, callback)
+        m2.ssl_ctx_use_cert(self.ctx, certfile)
+        if not keyfile: 
+            keyfile = certfile
+        m2.ssl_ctx_use_privkey(self.ctx, keyfile)
+        if not m2.ssl_ctx_check_privkey(self.ctx):
+            raise ValueError, 'public/private key mismatch'
+
+    def load_cert_chain(self, certchainfile, keyfile=None, callback=util.passphrase_callback):
+        """Load certificate chain and private key into the context.
+        
+        @param certchainfile: File object containing the PEM-encoded
+                              certificate chain.
+        @type  certchainfile: str
+        @param keyfile:       File object containing the PEM-encoded private
+                              key. Default value of None indicates that the
+                              private key is to be found in 'certchainfile'.
+        @type keyfile:        str  
+
+        @param callback:      Callable object to be invoked if the private key
+                              is passphrase-protected. Default callback 
+                              provides a simple terminal-style input for the
+                              passphrase.
+        """
+        m2.ssl_ctx_passphrase_callback(self.ctx, callback)
+        m2.ssl_ctx_use_cert_chain(self.ctx, certchainfile)
+        if not keyfile: 
+            keyfile = certchainfile
+        m2.ssl_ctx_use_privkey(self.ctx, keyfile)
+        if not m2.ssl_ctx_check_privkey(self.ctx):
+            raise ValueError, 'public/private key mismatch'
+
+    def set_client_CA_list_from_file(self, cafile):
+        """Load CA certs into the context. These CA certs are sent to the
+        peer during *SSLv3 certificate request*.
+        
+        @param cafile: File object containing one or more PEM-encoded CA
+                       certificates concatenated together.
+        @type cafile:  str
+        """
+        m2.ssl_ctx_set_client_CA_list_from_file(self.ctx, cafile)
+
+    # Deprecated.
+    load_client_CA = load_client_ca = set_client_CA_list_from_file
+
+    def load_verify_locations(self, cafile=None, capath=None):
+        """Load CA certs into the context. These CA certs are used during
+        verification of the peer's certificate.
+
+        @param cafile: File containing one or more PEM-encoded CA certificates
+                       concatenated together.
+        @type cafile:  str
+        @param capath: Directory containing PEM-encoded CA certificates
+                       (one certificate per file).
+        @type capath:  str
+        """
+        if cafile is None and capath is None:
+            raise ValueError("cafile and capath can not both be None.")
+        return m2.ssl_ctx_load_verify_locations(self.ctx, cafile, capath)
+
+    # Deprecated.
+    load_verify_info = load_verify_locations
+
+    def set_session_id_ctx(self, id):
+        ret = m2.ssl_ctx_set_session_id_context(self.ctx, id)
+        if not ret:
+            raise Err.SSLError(Err.get_error_code(), '')
+
+    def set_allow_unknown_ca(self, ok): 
+        """Set the context to accept/reject a peer certificate if the 
+        certificate's CA is unknown.
+
+        @param ok:       True to accept, False to reject.
+        @type ok:        boolean
+        """
+        self.allow_unknown_ca = ok
+
+    def get_allow_unknown_ca(self):
+        """Get the context's setting that accepts/rejects a peer
+        certificate if the certificate's CA is unknown.
+        """
+        return self.allow_unknown_ca
+
+    def set_verify(self, mode, depth, callback=None):
+        """
+        Set verify options. Most applications will need to call this
+        method with the right options to make a secure SSL connection.
+        
+        @param mode:     The verification mode to use. Typically at least
+                         SSL.verify_peer is used. Clients would also typically
+                         add SSL.verify_fail_if_no_peer_cert.
+        @type mode:      int                 
+        @param depth:    The maximum allowed depth of the certificate chain
+                         returned by the peer.
+        @type depth:     int
+        @param callback: Callable that can be used to specify custom
+                         verification checks.
+        """
+        if callback is None:
+            m2.ssl_ctx_set_verify_default(self.ctx, mode)
+        else:
+            m2.ssl_ctx_set_verify(self.ctx, mode, callback)
+        m2.ssl_ctx_set_verify_depth(self.ctx, depth)
+
+    def get_verify_mode(self):
+        return m2.ssl_ctx_get_verify_mode(self.ctx)
+
+    def get_verify_depth(self):
+        return m2.ssl_ctx_get_verify_depth(self.ctx)
+
+    def set_tmp_dh(self, dhpfile):
+        """Load ephemeral DH parameters into the context.
+
+        @param dhpfile: File object containing the PEM-encoded DH 
+                        parameters.
+        @type dhpfile:  str
+        """
+        f = BIO.openfile(dhpfile)
+        dhp = m2.dh_read_parameters(f.bio_ptr())
+        return m2.ssl_ctx_set_tmp_dh(self.ctx, dhp)
+
+    def set_tmp_dh_callback(self, callback=None):
+        if callback is not None:
+            m2.ssl_ctx_set_tmp_dh_callback(self.ctx, callback) 
+
+    def set_tmp_rsa(self, rsa):
+        """Load ephemeral RSA key into the context.
+
+        @param rsa: M2Crypto.RSA.RSA instance.
+        """
+        if isinstance(rsa, RSA.RSA):
+            return m2.ssl_ctx_set_tmp_rsa(self.ctx, rsa.rsa)
+        else:
+            raise TypeError, "Expected an instance of RSA.RSA, got %s." % (rsa,)
+
+    def set_tmp_rsa_callback(self, callback=None):
+        if callback is not None:
+            m2.ssl_ctx_set_tmp_rsa_callback(self.ctx, callback) 
+
+    def set_info_callback(self, callback=cb.ssl_info_callback):
+        """
+        Set a callback function that can be used to get state information
+        about the SSL connections that are created from this context.
+        
+        @param callback: Callback function. The default prints information to
+                         stderr.
+        """
+        m2.ssl_ctx_set_info_callback(self.ctx, callback) 
+
+    def set_cipher_list(self, cipher_list):
+        return m2.ssl_ctx_set_cipher_list(self.ctx, cipher_list)
+
+    def add_session(self, session):
+        return m2.ssl_ctx_add_session(self.ctx, session._ptr())
+
+    def remove_session(self, session):
+        return m2.ssl_ctx_remove_session(self.ctx, session._ptr())
+
+    def get_session_timeout(self):
+        return m2.ssl_ctx_get_session_timeout(self.ctx)
+
+    def set_session_timeout(self, timeout):
+        return m2.ssl_ctx_set_session_timeout(self.ctx, timeout)
+
+    def set_session_cache_mode(self, mode):
+        return m2.ssl_ctx_set_session_cache_mode(self.ctx, mode)
+
+    def get_session_cache_mode(self):
+        return m2.ssl_ctx_get_session_cache_mode(self.ctx)
+
+    def set_options(self, op):
+        return m2.ssl_ctx_set_options(self.ctx, op)
+
+    def get_cert_store(self):
+        """
+        Get the certificate store associated with this context.
+        
+        @warning: The store is NOT refcounted, and as such can not be relied
+        to be valid once the context goes away or is changed.
+        """
+        return X509.X509_Store(m2.ssl_ctx_get_cert_store(self.ctx))
+    
diff --git a/M2Crypto/SSL/SSLServer.py b/M2Crypto/SSL/SSLServer.py
new file mode 100644 (file)
index 0000000..d894d3b
--- /dev/null
@@ -0,0 +1,53 @@
+"""SSLServer
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['SSLServer', 'ForkingSSLServer', 'ThreadingSSLServer']
+
+# Python
+import socket, SocketServer
+
+# M2Crypto
+from Connection import Connection
+from M2Crypto.SSL import SSLError
+from M2Crypto import m2
+
+
+class SSLServer(SocketServer.TCPServer):
+    def __init__(self, server_address, RequestHandlerClass, ssl_context, bind_and_activate=True):
+        """ 
+        Superclass says: Constructor. May be extended, do not override.
+        This class says: Ho-hum.
+        """
+        SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
+        self.ssl_ctx=ssl_context
+        self.socket=Connection(self.ssl_ctx)
+        if bind_and_activate:
+            self.server_bind()
+            self.server_activate()        
+
+    def handle_request(self):
+        request = None
+        client_address = None
+        try:
+            request, client_address = self.get_request()
+            if self.verify_request(request, client_address):
+                self.process_request(request, client_address)
+        except SSLError:
+            self.handle_error(request, client_address)
+
+    def handle_error(self, request, client_address):
+        print '-'*40
+        import traceback
+        traceback.print_exc()
+        print '-'*40
+
+
+class ForkingSSLServer(SocketServer.ForkingMixIn, SSLServer):
+    pass
+
+
+class ThreadingSSLServer(SocketServer.ThreadingMixIn, SSLServer):
+    pass
+
+
diff --git a/M2Crypto/SSL/Session.py b/M2Crypto/SSL/Session.py
new file mode 100644 (file)
index 0000000..1edf5b0
--- /dev/null
@@ -0,0 +1,58 @@
+"""SSL Session
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['Session', 'load_session']
+
+from M2Crypto import BIO, Err, m2
+
+class Session:
+
+    m2_ssl_session_free = m2.ssl_session_free
+
+    def __init__(self, session, _pyfree=0):
+        assert session is not None
+        self.session = session
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_ssl_session_free(self.session)
+
+    def _ptr(self):
+        return self.session
+
+    def as_text(self):
+        buf = BIO.MemoryBuffer()
+        m2.ssl_session_print(buf.bio_ptr(), self.session)
+        return buf.read_all()
+
+    def as_der(self):
+        buf = BIO.MemoryBuffer()
+        m2.i2d_ssl_session(buf.bio_ptr(), self.session)
+        return buf.read_all()
+
+    def write_bio(self, bio):
+        return m2.ssl_session_write_bio(bio.bio_ptr(), self.session)
+
+    def get_time(self):
+        return m2.ssl_session_get_time(self.session)
+
+    def set_time(self, t):
+        return m2.ssl_session_set_time(self.session, t)
+
+    def get_timeout(self):
+        return m2.ssl_session_get_timeout(self.session)
+
+    def set_timeout(self, t):
+        return m2.ssl_session_set_timeout(self.session, t)
+
+
+def load_session(pemfile):
+    f = BIO.openfile(pemfile)
+    cptr = m2.ssl_session_read_pem(f.bio_ptr())
+    f.close()
+    if cptr is None:
+        from M2Crypto.SSL import SSLError
+        raise SSLError(Err.get_error())
+    return Session(cptr, 1)
diff --git a/M2Crypto/SSL/TwistedProtocolWrapper.py b/M2Crypto/SSL/TwistedProtocolWrapper.py
new file mode 100644 (file)
index 0000000..930dd32
--- /dev/null
@@ -0,0 +1,410 @@
+"""
+Make Twisted use M2Crypto for SSL
+
+Copyright (c) 2004-2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+__all__ = ['connectSSL', 'connectTCP', 'listenSSL', 'listenTCP',
+           'TLSProtocolWrapper']
+
+import twisted.protocols.policies as policies
+import twisted.internet.reactor
+from twisted.protocols.policies import ProtocolWrapper
+from twisted.internet.interfaces import ITLSTransport
+from zope.interface import implements
+
+import M2Crypto # for M2Crypto.BIO.BIOError
+from M2Crypto import m2, X509
+from M2Crypto.SSL import Checker
+
+
+def _alwaysSucceedsPostConnectionCheck(peerX509, expectedHost):
+    return 1
+
+
+def connectSSL(host, port, factory, contextFactory, timeout=30,
+               bindAddress=None,
+               reactor=twisted.internet.reactor,
+               postConnectionCheck=Checker.Checker()):
+    """
+    A convenience function to start an SSL/TLS connection using Twisted.
+    
+    See IReactorSSL interface in Twisted. 
+    """
+    wrappingFactory = policies.WrappingFactory(factory)
+    wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+        TLSProtocolWrapper(factory,
+                           wrappedProtocol,
+                           startPassThrough=0,
+                           client=1,
+                           contextFactory=contextFactory,
+                           postConnectionCheck=postConnectionCheck)
+    return reactor.connectTCP(host, port, wrappingFactory, timeout, bindAddress)
+        
+
+def connectTCP(host, port, factory, timeout=30, bindAddress=None,
+               reactor=twisted.internet.reactor,
+               postConnectionCheck=Checker.Checker()):
+    """
+    A convenience function to start a TCP connection using Twisted. 
+
+    NOTE: You must call startTLS(ctx) to go into SSL/TLS mode.
+
+    See IReactorTCP interface in Twisted. 
+    """
+    wrappingFactory = policies.WrappingFactory(factory)
+    wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+        TLSProtocolWrapper(factory,
+                           wrappedProtocol,
+                           startPassThrough=1,
+                           client=1,
+                           contextFactory=None,
+                           postConnectionCheck=postConnectionCheck)
+    return reactor.connectTCP(host, port, wrappingFactory, timeout, bindAddress)
+
+
+def listenSSL(port, factory, contextFactory, backlog=5, interface='',
+              reactor=twisted.internet.reactor,  
+              postConnectionCheck=_alwaysSucceedsPostConnectionCheck):
+    """
+    A convenience function to listen for SSL/TLS connections using Twisted. 
+
+    See IReactorSSL interface in Twisted. 
+    """
+    wrappingFactory = policies.WrappingFactory(factory)
+    wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+        TLSProtocolWrapper(factory,
+                           wrappedProtocol,
+                           startPassThrough=0,
+                           client=0,
+                           contextFactory=contextFactory,
+                           postConnectionCheck=postConnectionCheck)
+    return reactor.listenTCP(port, wrappingFactory, backlog, interface)
+
+
+def listenTCP(port, factory, backlog=5, interface='',
+              reactor=twisted.internet.reactor,  
+              postConnectionCheck=None):
+    """
+    A convenience function to listen for TCP connections using Twisted. 
+    
+    NOTE: You must call startTLS(ctx) to go into SSL/TLS mode.
+
+    See IReactorTCP interface in Twisted. 
+    """
+    wrappingFactory = policies.WrappingFactory(factory)
+    wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+        TLSProtocolWrapper(factory,
+                           wrappedProtocol,
+                           startPassThrough=1,
+                           client=0,
+                           contextFactory=None,
+                           postConnectionCheck=postConnectionCheck)
+    return reactor.listenTCP(port, wrappingFactory, backlog, interface)
+
+
+class _BioProxy:
+    """
+    The purpose of this class is to eliminate the __del__ method from
+    TLSProtocolWrapper, and thus letting it be garbage collected.
+    """
+    
+    m2_bio_free_all = m2.bio_free_all
+
+    def __init__(self, bio):
+        self.bio = bio
+        
+    def _ptr(self):
+        return self.bio
+    
+    def __del__(self):
+        if self.bio is not None:
+            self.m2_bio_free_all(self.bio)
+
+
+class _SSLProxy:
+    """
+    The purpose of this class is to eliminate the __del__ method from
+    TLSProtocolWrapper, and thus letting it be garbage collected.
+    """
+    
+    m2_ssl_free = m2.ssl_free
+
+    def __init__(self, ssl):
+        self.ssl = ssl
+        
+    def _ptr(self):
+        return self.ssl
+    
+    def __del__(self):
+        if self.ssl is not None:
+            self.m2_ssl_free(self.ssl)
+
+
+class TLSProtocolWrapper(ProtocolWrapper):
+    """
+    A SSL/TLS protocol wrapper to be used with Twisted. Typically
+    you would not use this class directly. Use connectTCP, 
+    connectSSL, listenTCP, listenSSL functions defined above,
+    which will hook in this class.
+    """
+
+    implements(ITLSTransport)
+    
+    def __init__(self, factory, wrappedProtocol, startPassThrough, client,
+                 contextFactory, postConnectionCheck):
+        """
+        @param factory:
+        @param wrappedProtocol:
+        @param startPassThrough:    If true we won't encrypt at all. Need to
+                                    call startTLS() later to switch to SSL/TLS.
+        @param client:              True if this should be a client protocol.
+        @param contextFactory:      Factory that creates SSL.Context objects.
+                                    The called function is getContext().
+        @param postConnectionCheck: The post connection check callback that
+                                    will be called just after connection has
+                                    been established but before any real data
+                                    has been exchanged. The first argument to
+                                    this function is an X509 object, the second
+                                    is the expected host name string.
+        """
+        #ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+        #XXX: Twisted 2.0 has a new addition where the wrappingFactory is
+        #     set as the factory of the wrappedProtocol. This is an issue
+        #     as the wrap should be transparent. What we want is 
+        #     the factory of the wrappedProtocol to be the wrappedFactory and
+        #     not the outer wrappingFactory. This is how it was implemented in
+        #     Twisted 1.3
+        self.factory = factory
+        self.wrappedProtocol = wrappedProtocol
+        
+        # wrappedProtocol == client/server instance
+        # factory.wrappedFactory == client/server factory
+
+        self.data = '' # Clear text to encrypt and send
+        self.encrypted = '' # Encrypted data we need to decrypt and pass on
+        self.tlsStarted = 0 # SSL/TLS mode or pass through
+        self.checked = 0 # Post connection check done or not
+        self.isClient = client
+        self.helloDone = 0 # True when hello has been sent
+        if postConnectionCheck is None:
+            self.postConnectionCheck = _alwaysSucceedsPostConnectionCheck
+        else:
+            self.postConnectionCheck = postConnectionCheck
+
+        if not startPassThrough:
+            self.startTLS(contextFactory.getContext())
+            
+    def clear(self):
+        """
+        Clear this instance, after which it is ready for reuse.
+        """
+        if getattr(self, 'tlsStarted', 0):
+            self.sslBio = None
+            self.ssl = None
+            self.internalBio = None
+            self.networkBio = None
+        self.data = ''
+        self.encrypted = ''
+        self.tlsStarted = 0
+        self.checked = 0
+        self.isClient = 1
+        self.helloDone = 0
+        # We can reuse self.ctx and it will be deleted automatically
+        # when this instance dies
+        
+    def startTLS(self, ctx):
+        """
+        Start SSL/TLS. If this is not called, this instance just passes data
+        through untouched.
+        """
+        # NOTE: This method signature must match the startTLS() method Twisted
+        #       expects transports to have. This will be called automatically
+        #       by Twisted in STARTTLS situations, for example with SMTP.
+        if self.tlsStarted:
+            raise Exception, 'TLS already started'
+
+        self.ctx = ctx
+
+        self.internalBio = m2.bio_new(m2.bio_s_bio())
+        m2.bio_set_write_buf_size(self.internalBio, 0)
+        self.networkBio = _BioProxy(m2.bio_new(m2.bio_s_bio()))
+        m2.bio_set_write_buf_size(self.networkBio._ptr(), 0)
+        m2.bio_make_bio_pair(self.internalBio, self.networkBio._ptr())
+
+        self.sslBio = _BioProxy(m2.bio_new(m2.bio_f_ssl()))
+
+        self.ssl = _SSLProxy(m2.ssl_new(self.ctx.ctx))
+
+        if self.isClient:
+            m2.ssl_set_connect_state(self.ssl._ptr())
+        else:
+            m2.ssl_set_accept_state(self.ssl._ptr())
+            
+        m2.ssl_set_bio(self.ssl._ptr(), self.internalBio, self.internalBio)
+        m2.bio_set_ssl(self.sslBio._ptr(), self.ssl._ptr(), m2.bio_noclose)
+
+        # Need this for writes that are larger than BIO pair buffers
+        mode = m2.ssl_get_mode(self.ssl._ptr())
+        m2.ssl_set_mode(self.ssl._ptr(),
+                        mode |
+                        m2.SSL_MODE_ENABLE_PARTIAL_WRITE |
+                        m2.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)
+
+        self.tlsStarted = 1
+
+    def write(self, data):
+        if not self.tlsStarted:
+            ProtocolWrapper.write(self, data)
+            return
+
+        try:
+            encryptedData = self._encrypt(data)
+            ProtocolWrapper.write(self, encryptedData)
+            self.helloDone = 1
+        except M2Crypto.BIO.BIOError, e:
+            # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+            # for the error codes returned by SSL_get_verify_result.
+            e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+            raise e
+
+    def writeSequence(self, data):
+        if not self.tlsStarted:
+            ProtocolWrapper.writeSequence(self, ''.join(data))
+            return
+
+        self.write(''.join(data))
+
+    def loseConnection(self):
+        # XXX Do we need to do m2.ssl_shutdown(self.ssl._ptr())?
+        ProtocolWrapper.loseConnection(self)
+
+    def connectionMade(self):
+        ProtocolWrapper.connectionMade(self)
+        if self.tlsStarted and self.isClient and not self.helloDone:
+            self._clientHello()
+
+    def dataReceived(self, data):
+        if not self.tlsStarted:
+            ProtocolWrapper.dataReceived(self, data)
+            return
+
+        self.encrypted += data
+
+        try:
+            while 1:
+                decryptedData = self._decrypt()
+
+                self._check()
+
+                encryptedData = self._encrypt()
+                ProtocolWrapper.write(self, encryptedData)
+
+                ProtocolWrapper.dataReceived(self, decryptedData)
+
+                if decryptedData == '' and encryptedData == '':
+                    break
+        except M2Crypto.BIO.BIOError, e:
+            # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+            # for the error codes returned by SSL_get_verify_result.
+            e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+            raise e
+
+    def connectionLost(self, reason):
+        self.clear()
+        ProtocolWrapper.connectionLost(self, reason)
+
+    def _check(self):
+        if not self.checked and m2.ssl_is_init_finished(self.ssl._ptr()):
+            x509 = m2.ssl_get_peer_cert(self.ssl._ptr())
+            if x509 is not None:
+                x509 = X509.X509(x509, 1)
+            if self.isClient:
+                host = self.transport.addr[0]
+            else:
+                host = self.transport.getPeer().host
+            if not self.postConnectionCheck(x509, host):
+                raise Checker.SSLVerificationError, 'post connection check'
+            self.checked = 1
+
+    def _clientHello(self):
+        try:
+            # We rely on OpenSSL implicitly starting with client hello
+            # when we haven't yet established an SSL connection
+            encryptedData = self._encrypt(clientHello=1)
+            ProtocolWrapper.write(self, encryptedData)
+            self.helloDone = 1
+        except M2Crypto.BIO.BIOError, e:
+            # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+            # for the error codes returned by SSL_get_verify_result.
+            e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+            raise e
+
+    def _encrypt(self, data='', clientHello=0):
+        # XXX near mirror image of _decrypt - refactor
+        encryptedData = ''
+        self.data += data
+        # Optimizations to reduce attribute accesses
+        sslBioPtr = self.sslBio._ptr()
+        networkBio = self.networkBio._ptr()
+        m2bio_ctrl_get_write_guarantee = m2.bio_ctrl_get_write_guarantee
+        m2bio_write = m2.bio_write
+        m2bio_should_retry = m2.bio_should_retry
+        m2bio_ctrl_pending = m2.bio_ctrl_pending
+        m2bio_read = m2.bio_read
+        
+        while 1:
+            g = m2bio_ctrl_get_write_guarantee(sslBioPtr)
+            if g > 0 and self.data != '' or clientHello:
+                r = m2bio_write(sslBioPtr, self.data)
+                if r <= 0:
+                    assert(m2bio_should_retry(sslBioPtr))
+                else:
+                    assert(self.checked)               
+                    self.data = self.data[r:]
+                  
+            pending = m2bio_ctrl_pending(networkBio)
+            if pending:
+                d = m2bio_read(networkBio, pending)
+                if d is not None: # This is strange, but d can be None
+                    encryptedData += d
+                else:
+                    assert(m2bio_should_retry(networkBio))
+            else:
+                break
+        return encryptedData
+
+    def _decrypt(self, data=''):
+        # XXX near mirror image of _encrypt - refactor
+        self.encrypted += data
+        decryptedData = ''
+        # Optimizations to reduce attribute accesses
+        sslBioPtr = self.sslBio._ptr()
+        networkBio = self.networkBio._ptr()
+        m2bio_ctrl_get_write_guarantee = m2.bio_ctrl_get_write_guarantee
+        m2bio_write = m2.bio_write
+        m2bio_should_retry = m2.bio_should_retry
+        m2bio_ctrl_pending = m2.bio_ctrl_pending
+        m2bio_read = m2.bio_read
+        
+        while 1:
+            g = m2bio_ctrl_get_write_guarantee(networkBio)
+            if g > 0 and self.encrypted != '':
+                r = m2bio_write(networkBio, self.encrypted)
+                if r <= 0:
+                    assert(m2bio_should_retry(networkBio))
+                else:
+                    self.encrypted = self.encrypted[r:]
+                              
+            pending = m2bio_ctrl_pending(sslBioPtr)
+            if pending:
+                d = m2bio_read(sslBioPtr, pending)
+                if d is not None: # This is strange, but d can be None
+                    decryptedData += d
+                else:
+                    assert(m2bio_should_retry(sslBioPtr))
+            else:
+                break
+
+        return decryptedData
diff --git a/M2Crypto/SSL/__init__.py b/M2Crypto/SSL/__init__.py
new file mode 100644 (file)
index 0000000..a013569
--- /dev/null
@@ -0,0 +1,28 @@
+"""M2Crypto SSL services.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+from M2Crypto import m2
+
+class SSLError(Exception): pass
+m2.ssl_init(SSLError)
+
+# M2Crypto.SSL
+from Cipher import Cipher, Cipher_Stack
+from Context import Context
+from Connection import Connection
+from SSLServer import SSLServer, ForkingSSLServer, ThreadingSSLServer
+from ssl_dispatcher import ssl_dispatcher
+from timeout import timeout
+
+verify_none = m2.SSL_VERIFY_NONE
+verify_peer = m2.SSL_VERIFY_PEER
+verify_fail_if_no_peer_cert = m2.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
+verify_client_once = m2.SSL_VERIFY_CLIENT_ONCE
+
+SSL_SENT_SHUTDOWN = m2.SSL_SENT_SHUTDOWN
+SSL_RECEIVED_SHUTDOWN = m2.SSL_RECEIVED_SHUTDOWN
+
+op_all = m2.SSL_OP_ALL
+op_no_sslv2 = m2.SSL_OP_NO_SSLv2
diff --git a/M2Crypto/SSL/cb.py b/M2Crypto/SSL/cb.py
new file mode 100644 (file)
index 0000000..c1710cc
--- /dev/null
@@ -0,0 +1,83 @@
+"""SSL callbacks
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['unknown_issuer', 'ssl_verify_callback_stub', 'ssl_verify_callback',
+           'ssl_verify_callback_allow_unknown_ca', 'ssl_info_callback']
+
+# Python
+import sys
+
+# M2Crypto
+import Context
+from M2Crypto import m2
+
+def ssl_verify_callback_stub(ssl_ctx_ptr, x509_ptr, errnum, errdepth, ok):
+    # Deprecated
+    return ok
+
+unknown_issuer = [
+    m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+    m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+    m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+    m2.X509_V_ERR_CERT_UNTRUSTED,
+    ]
+
+def ssl_verify_callback(ssl_ctx_ptr, x509_ptr, errnum, errdepth, ok):
+    # Deprecated
+    ssl_ctx = Context.map()[long(ssl_ctx_ptr)]
+    if errnum in unknown_issuer: 
+        if ssl_ctx.get_allow_unknown_ca():
+            sys.stderr.write("policy: %s: permitted...\n" % (m2.x509_get_verify_error(errnum)))
+            sys.stderr.flush()
+            ok = 1
+    # CRL checking goes here...
+    if ok:
+        if ssl_ctx.get_verify_depth() >= errdepth:
+            ok = 1
+        else:
+            ok = 0
+    return ok
+
+def ssl_verify_callback_allow_unknown_ca(ok, store):
+    errnum = store.get_error()
+    if errnum in unknown_issuer:
+        ok = 1
+    return ok
+
+# Cribbed from OpenSSL's apps/s_cb.c.
+def ssl_info_callback(where, ret, ssl_ptr):
+
+    w = where & ~m2.SSL_ST_MASK
+    if (w & m2.SSL_ST_CONNECT):
+        state = "SSL connect"
+    elif (w & m2.SSL_ST_ACCEPT):
+        state = "SSL accept"
+    else:
+        state = "SSL state unknown"
+
+    if (where & m2.SSL_CB_LOOP):
+        sys.stderr.write("LOOP: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+        sys.stderr.flush()
+        return
+
+    if (where & m2.SSL_CB_EXIT):
+        if not ret:
+            sys.stderr.write("FAILED: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+            sys.stderr.flush()
+        else:
+            sys.stderr.write("INFO: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+            sys.stderr.flush()
+        return
+
+    if (where & m2.SSL_CB_ALERT):
+        if (where & m2.SSL_CB_READ):
+            w = 'read'
+        else:
+            w = 'write'
+        sys.stderr.write("ALERT: %s: %s: %s\n" % \
+            (w, m2.ssl_get_alert_type_v(ret), m2.ssl_get_alert_desc_v(ret)))
+        sys.stderr.flush()
+        return
+
+
diff --git a/M2Crypto/SSL/ssl_dispatcher.py b/M2Crypto/SSL/ssl_dispatcher.py
new file mode 100644 (file)
index 0000000..1e8c875
--- /dev/null
@@ -0,0 +1,36 @@
+"""SSL dispatcher
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['ssl_dispatcher']
+
+# Python
+import asyncore, socket
+
+# M2Crypto
+from Connection import Connection
+from M2Crypto import Err, m2
+
+
+class ssl_dispatcher(asyncore.dispatcher):
+
+    def create_socket(self, ssl_context):
+        self.family_and_type=socket.AF_INET, socket.SOCK_STREAM
+        self.ssl_ctx=ssl_context
+        self.socket=Connection(self.ssl_ctx)
+        #self.socket.setblocking(0)
+        self.add_channel()
+
+    def connect(self, addr):
+        self.socket.setblocking(1)
+        self.socket.connect(addr)
+        self.socket.setblocking(0)
+
+    def recv(self, buffer_size=4096):
+        """Receive data over SSL."""
+        return self.socket.recv(buffer_size)
+
+    def send(self, buffer):
+        """Send data over SSL."""
+        return self.socket.send(buffer)
+
diff --git a/M2Crypto/SSL/timeout.py b/M2Crypto/SSL/timeout.py
new file mode 100644 (file)
index 0000000..d76556d
--- /dev/null
@@ -0,0 +1,30 @@
+"""Support for SSL socket timeouts.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['DEFAULT_TIMEOUT', 'timeout', 'struct_to_timeout', 'struct_size']
+
+import struct
+from M2Crypto import m2
+
+DEFAULT_TIMEOUT = 600 
+
+class timeout:
+
+    def __init__(self, sec=DEFAULT_TIMEOUT, microsec=0):
+        self.sec = sec
+        self.microsec = microsec
+
+    def pack(self):
+        return struct.pack('ll', self.sec, self.microsec)
+
+
+def struct_to_timeout(binstr):
+    (s, ms) = struct.unpack('ll', binstr)
+    return timeout(s, ms)
+
+def struct_size():
+    return struct.calcsize('ll')
diff --git a/M2Crypto/X509.py b/M2Crypto/X509.py
new file mode 100644 (file)
index 0000000..eef83fe
--- /dev/null
@@ -0,0 +1,1104 @@
+"""M2Crypto wrapper for OpenSSL X509 API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+Author: Heikki Toivonen
+"""
+
+# M2Crypto
+from M2Crypto import ASN1, BIO, Err, EVP, util
+import m2
+
+FORMAT_DER = 0
+FORMAT_PEM = 1
+
+class X509Error(Exception): pass
+
+m2.x509_init(X509Error)
+
+V_OK = m2.X509_V_OK
+
+def new_extension(name, value, critical=0, _pyfree=1):
+    """
+    Create new X509_Extension instance.
+    """
+    if name == 'subjectKeyIdentifier' and \
+        value.strip('0123456789abcdefABCDEF:') is not '':
+        raise ValueError('value must be precomputed hash')
+    lhash = m2.x509v3_lhash()
+    ctx = m2.x509v3_set_conf_lhash(lhash)
+    x509_ext_ptr = m2.x509v3_ext_conf(lhash, ctx, name, value)
+    x509_ext = X509_Extension(x509_ext_ptr, _pyfree)
+    x509_ext.set_critical(critical)
+    return x509_ext 
+
+
+class X509_Extension:
+    """
+    X509 Extension
+    """
+    
+    m2_x509_extension_free = m2.x509_extension_free
+    
+    def __init__(self, x509_ext_ptr=None, _pyfree=1):
+        self.x509_ext = x509_ext_ptr
+        self._pyfree = _pyfree
+
+    def __del__(self):
+        if getattr(self, '_pyfree', 0) and self.x509_ext:
+            self.m2_x509_extension_free(self.x509_ext)
+
+    def _ptr(self):
+        return self.x509_ext
+
+    def set_critical(self, critical=1):
+        """
+        Mark this extension critical or noncritical. By default an
+        extension is not critical.
+
+        @type critical:  int
+        @param critical: Nonzero sets this extension as critical.
+                         Calling this method without arguments will
+                         set this extension to critical.
+        """
+        return m2.x509_extension_set_critical(self.x509_ext, critical)
+    
+    def get_critical(self):
+        """
+        Return whether or not this is a critical extension.
+
+        @rtype:   int
+        @return:  Nonzero if this is a critical extension.
+        """
+        return m2.x509_extension_get_critical(self.x509_ext)
+    
+    def get_name(self):
+        """
+        Get the extension name, for example 'subjectAltName'.
+        """
+        return m2.x509_extension_get_name(self.x509_ext)
+
+    def get_value(self, flag=0, indent=0):
+        """
+        Get the extension value, for example 'DNS:www.example.com'.
+        
+        @param flag:   Flag to control what and how to print.
+        @param indent: How many spaces to print before actual value.
+        """
+        buf=BIO.MemoryBuffer()
+        m2.x509_ext_print(buf.bio_ptr(), self.x509_ext, flag, indent)
+        return buf.read_all()    
+
+
+class X509_Extension_Stack:
+    """
+    X509 Extension Stack
+    
+    @warning: Do not modify the underlying OpenSSL stack
+    except through this interface, or use any OpenSSL functions that do so
+    indirectly. Doing so will get the OpenSSL stack and the internal pystack
+    of this class out of sync, leading to python memory leaks, exceptions
+    or even python crashes!
+    """
+
+    m2_sk_x509_extension_free = m2.sk_x509_extension_free
+
+    def __init__(self, stack=None, _pyfree=0):
+        if stack is not None:
+            self.stack = stack
+            self._pyfree = _pyfree
+            num = m2.sk_x509_extension_num(self.stack)
+            for i in range(num):
+                self.pystack.append(X509_Extension(m2.sk_x509_extension_value(self.stack, i),
+                                                   _pyfree=_pyfree))
+        else:
+            self.stack = m2.sk_x509_extension_new_null()
+            self._pyfree = 1
+            self.pystack = [] # This must be kept in sync with self.stack
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_sk_x509_extension_free(self.stack)
+
+    def __len__(self):
+        assert m2.sk_x509_extension_num(self.stack) == len(self.pystack)
+        return len(self.pystack)
+
+    def __getitem__(self, idx):
+        return self.pystack[idx]
+    
+    def __iter__(self):
+        return iter(self.pystack)
+    def _ptr(self):
+        return self.stack
+
+    def push(self, x509_ext):
+        """
+        Push X509_Extension object onto the stack.
+
+        @type x509_ext: M2Crypto.X509.X509_Extension
+        @param x509_ext: X509_Extension object to be pushed onto the stack.
+        @return: The number of extensions on the stack.
+        """
+        self.pystack.append(x509_ext)
+        ret = m2.sk_x509_extension_push(self.stack, x509_ext._ptr())
+        assert ret == len(self.pystack)
+        return ret
+
+    def pop(self):
+        """
+        Pop X509_Extension object from the stack.
+        
+        @return: X509_Extension popped
+        """
+        x509_ext_ptr = m2.sk_x509_extension_pop(self.stack)
+        if x509_ext_ptr is None:
+            assert len(self.pystack) == 0
+            return None
+        return self.pystack.pop()
+
+
+class X509_Name_Entry:
+    """
+    X509 Name Entry
+    """
+
+    m2_x509_name_entry_free = m2.x509_name_entry_free
+
+    def __init__(self, x509_name_entry, _pyfree=0):
+        self.x509_name_entry = x509_name_entry
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_name_entry_free(self.x509_name_entry)
+
+    def _ptr(self):
+        return self.x509_name_entry
+
+    def set_object(self, asn1obj):
+        return m2.x509_name_entry_set_object(self.x509_name_entry,
+                                             asn1obj._ptr())
+
+    def set_data(self, data, type=ASN1.MBSTRING_ASC):
+        return m2.x509_name_entry_set_data(self.x509_name_entry,
+                                           type, data)
+
+    def get_object(self):
+        return ASN1.ASN1_Object(m2.x509_name_entry_get_object(self.x509_name_entry))
+        
+    def get_data(self):
+        return ASN1.ASN1_String(m2.x509_name_entry_get_data(self.x509_name_entry))
+
+    def create_by_txt( self, field, type, entry, len):
+        return m2.x509_name_entry_create_by_txt(self.x509_name_entry._ptr(),
+                                                field, type, entry, len)
+    
+
+class X509_Name:
+    """
+    X509 Name
+    """
+
+    nid = {'C'                      : m2.NID_countryName,
+           'SP'                     : m2.NID_stateOrProvinceName,
+           'ST'                     : m2.NID_stateOrProvinceName,
+           'stateOrProvinceName'    : m2.NID_stateOrProvinceName,
+           'L'                      : m2.NID_localityName,
+           'localityName'           : m2.NID_localityName,
+           'O'                      : m2.NID_organizationName,
+           'organizationName'       : m2.NID_organizationName,
+           'OU'                     : m2.NID_organizationalUnitName,
+           'organizationUnitName'   : m2.NID_organizationalUnitName,
+           'CN'                     : m2.NID_commonName,
+           'commonName'             : m2.NID_commonName,
+           'Email'                  : m2.NID_pkcs9_emailAddress,
+           'emailAddress'           : m2.NID_pkcs9_emailAddress,
+           'serialNumber'           : m2.NID_serialNumber,
+           'SN'                     : m2.NID_surname,
+           'surname'                : m2.NID_surname,
+           'GN'                     : m2.NID_givenName,
+           'givenName'              : m2.NID_givenName
+           }
+
+    m2_x509_name_free = m2.x509_name_free
+
+    def __init__(self, x509_name=None, _pyfree=0):
+        if x509_name is not None:
+            assert m2.x509_name_type_check(x509_name), "'x509_name' type error"
+            self.x509_name = x509_name
+            self._pyfree = _pyfree
+        else:
+            self.x509_name = m2.x509_name_new ()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_name_free(self.x509_name)
+
+    def __str__(self):
+        assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error" 
+        return m2.x509_name_oneline(self.x509_name)
+
+    def __getattr__(self, attr):
+        if attr in self.nid:
+            assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error" 
+            return m2.x509_name_by_nid(self.x509_name, self.nid[attr])
+
+        if attr in self.__dict__:
+            return self.__dict__[attr]
+
+        raise AttributeError, (self, attr)
+
+    def __setattr__(self, attr, value):
+        if attr in self.nid:
+            assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+            return m2.x509_name_set_by_nid(self.x509_name, self.nid[attr], value)
+
+        self.__dict__[attr] = value
+
+    def __len__(self):
+        return m2.x509_name_entry_count(self.x509_name)
+    
+    def __getitem__(self, idx):
+        if not 0 <= idx < self.entry_count():
+            raise IndexError("index out of range")
+        return X509_Name_Entry(m2.x509_name_get_entry(self.x509_name, idx))
+
+    def __iter__(self):
+        for i in xrange(self.entry_count()):
+            yield self[i]
+
+    def _ptr(self):
+        #assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error" 
+        return self.x509_name
+
+    def add_entry_by_txt(self, field, type, entry, len, loc, set):
+        return m2.x509_name_add_entry_by_txt(self.x509_name, field, type,
+                                             entry, len, loc, set )
+
+    def entry_count( self ):
+        return m2.x509_name_entry_count( self.x509_name )
+    
+    def get_entries_by_nid(self, nid):
+        ret = []
+        lastpos = -1
+
+        while True:
+            lastpos = m2.x509_name_get_index_by_nid(self.x509_name, nid,
+                                                    lastpos)
+            if lastpos == -1:
+                break
+            
+            ret.append(self[lastpos])
+        
+        return ret
+    
+    def as_text(self, indent=0, flags=m2.XN_FLAG_COMPAT):
+        """
+        as_text returns the name as a string.
+        
+        @param indent: Each line in multiline format is indented 
+                       by this many spaces.
+        @param flags:  Flags that control how the output should be formatted.
+        """
+        assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+        buf=BIO.MemoryBuffer()
+        m2.x509_name_print_ex(buf.bio_ptr(), self.x509_name, indent, flags)
+        return buf.read_all()
+
+    def as_der(self):
+        assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+        return m2.x509_name_get_der(self.x509_name)
+
+    def as_hash(self):
+        assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+        return m2.x509_name_hash(self.x509_name)
+
+class X509:
+    """
+    X.509 Certificate
+    """
+
+    m2_x509_free = m2.x509_free
+
+    def __init__(self, x509=None, _pyfree=0):
+        if x509 is not None:
+            assert m2.x509_type_check(x509), "'x509' type error"
+            self.x509 = x509
+            self._pyfree = _pyfree
+        else:
+            self.x509 = m2.x509_new ()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_free(self.x509)
+
+    def _ptr(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return self.x509
+
+    def as_text(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        buf=BIO.MemoryBuffer()
+        m2.x509_print(buf.bio_ptr(), self.x509)
+        return buf.read_all()
+
+    def as_der(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.i2d_x509(self.x509)
+
+    def as_pem(self):
+        buf=BIO.MemoryBuffer()
+        m2.x509_write_pem(buf.bio_ptr(), self.x509)
+        return buf.read_all()
+
+    def save_pem(self, filename):
+        """
+        save_pem
+        """
+        bio=BIO.openfile(filename, 'wb')
+        return m2.x509_write_pem(bio.bio_ptr(), self.x509)
+
+    def save(self, filename, format=FORMAT_PEM):
+        """
+        Saves X.509 certificate to a file. Default output
+        format is PEM.
+
+        @type filename: string
+        @param filename: Name of the file the cert will be saved to.
+        @type format: int
+        @param format: Controls what output format is used to save the cert.
+        Either FORMAT_PEM or FORMAT_DER to save in PEM or DER format.
+        Raises a ValueError if an unknow format is used.
+        """
+        bio = BIO.openfile(filename, 'wb')
+        if format == FORMAT_PEM:
+            return m2.x509_write_pem(bio.bio_ptr(), self.x509)
+        elif format == FORMAT_DER:
+            return m2.i2d_x509_bio(bio.bio_ptr(), self.x509)
+        else:
+            raise ValueError("Unknown filetype. Must be either FORMAT_PEM or FORMAT_DER")
+
+    def set_version(self, version):
+        """
+        Set version.
+
+        @type version:  int
+        @param version: Version number.
+        @rtype:         int
+        @return:        Returns 0 on failure.
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_version(self.x509, version)
+
+    def set_not_before(self, asn1_utctime):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_not_before(self.x509, asn1_utctime._ptr())
+
+    def set_not_after(self, asn1_utctime):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_not_after(self.x509, asn1_utctime._ptr())
+
+    def set_subject_name(self, name):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_subject_name(self.x509, name.x509_name)
+
+    def set_issuer_name(self, name):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_issuer_name(self.x509, name.x509_name)
+
+    def get_version(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_get_version(self.x509)
+
+    def get_serial_number(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        asn1_integer = m2.x509_get_serial_number(self.x509)
+        return m2.asn1_integer_get(asn1_integer)
+
+    def set_serial_number(self, serial):
+        """
+        Set serial number.
+
+        @type serial:   int
+        @param serial:  Serial number.
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        # This "magically" changes serial since asn1_integer
+        # is C pointer to x509's internal serial number.
+        asn1_integer = m2.x509_get_serial_number(self.x509)
+        return m2.asn1_integer_set(asn1_integer, serial)
+        # XXX Or should I do this?
+        #asn1_integer = m2.asn1_integer_new()
+        #m2.asn1_integer_set(asn1_integer, serial)
+        #return m2.x509_set_serial_number(self.x509, asn1_integer)
+
+    def get_not_before(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return ASN1.ASN1_UTCTIME(m2.x509_get_not_before(self.x509))
+
+    def get_not_after(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return ASN1.ASN1_UTCTIME(m2.x509_get_not_after(self.x509))
+
+    def get_pubkey(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return EVP.PKey(m2.x509_get_pubkey(self.x509), _pyfree=1)
+
+    def set_pubkey(self, pkey):
+        """
+        Set the public key for the certificate
+
+        @type pkey:  EVP_PKEY
+        @param pkey: Public key
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_pubkey(self.x509, pkey.pkey)
+
+    def get_issuer(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return X509_Name(m2.x509_get_issuer_name(self.x509))
+
+    def set_issuer(self, name):
+        """
+        Set issuer name.
+
+        @type name:     X509_Name
+        @param name:    subjectName field.
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_issuer_name(self.x509, name.x509_name)
+
+    def get_subject(self):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return X509_Name(m2.x509_get_subject_name(self.x509))
+
+    def set_subject(self, name):
+        """
+        Set subject name.
+
+        @type name:     X509_Name
+        @param name:    subjectName field.
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_set_subject_name(self.x509, name.x509_name)
+
+    def add_ext(self, ext):
+        """
+        Add X509 extension to this certificate.
+
+        @type ext:     X509_Extension
+        @param ext:    Extension
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        return m2.x509_add_ext(self.x509, ext.x509_ext, -1)
+
+    def get_ext(self, name):
+        """
+        Get X509 extension by name.
+
+        @type name:    Name of the extension
+        @param name:   str
+        @return:       X509_Extension
+        """
+        # Optimizations to reduce attribute accesses
+        m2x509_get_ext = m2.x509_get_ext
+        m2x509_extension_get_name = m2.x509_extension_get_name
+        x509 = self.x509
+        
+        for i in range(m2.x509_get_ext_count(x509)):
+            extPtr = m2x509_get_ext(x509, i)
+            if m2x509_extension_get_name(extPtr) == name:
+                return X509_Extension(extPtr, _pyfree=0)
+
+        raise LookupError
+
+    def get_ext_at(self, index):
+        """
+        Get X509 extension by index.
+
+        @type index:    Name of the extension
+        @param index:   int
+        @return:        X509_Extension
+        """
+        if index < 0 or index >= self.get_ext_count():
+            raise IndexError
+        
+        return X509_Extension(m2.x509_get_ext(self.x509, index),
+                              _pyfree=0)
+
+    def get_ext_count(self):
+        """
+        Get X509 extension count.
+        """
+        return m2.x509_get_ext_count(self.x509)        
+
+    def sign(self, pkey, md):
+        """
+        Sign the certificate.
+
+        @type pkey:  EVP_PKEY
+        @param pkey: Public key
+        @type md:    str
+        @param md:   Message digest algorithm to use for signing,
+                     for example 'sha1'.
+        """
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        mda = getattr(m2, md, None)
+        if mda is None:
+            raise ValueError, ('unknown message digest', md)
+        return m2.x509_sign(self.x509, pkey.pkey, mda())
+
+    def verify(self, pkey=None):
+        assert m2.x509_type_check(self.x509), "'x509' type error"
+        if pkey:
+            return m2.x509_verify(self.x509, pkey.pkey)
+        else:
+            return m2.x509_verify(self.x509, self.get_pubkey().pkey)
+            
+    def check_ca(self):
+        """
+        Check if the certificate is a Certificate Authority (CA) certificate.
+        
+        @return: 0 if the certificate is not CA, nonzero otherwise.
+        
+        @requires: OpenSSL 0.9.8 or newer 
+        """
+        return m2.x509_check_ca(self.x509)
+        
+    def check_purpose(self, id, ca):
+        """
+        Check if the certificate's purpose matches the asked purpose.
+        
+        @param id: Purpose id. See X509_PURPOSE_* constants.
+        @param ca: 1 if the certificate should be CA, 0 otherwise.
+        @return: 0 if the certificate purpose does not match, nonzero otherwise.
+        """
+        return m2.x509_check_purpose(self.x509, id, ca)
+
+    def get_fingerprint(self, md='md5'):
+        """
+        Get the fingerprint of the certificate.
+        
+        @param md: Message digest algorithm to use.
+        @return:   String containing the fingerprint in hex format.
+        """
+        der = self.as_der()
+        md = EVP.MessageDigest(md)
+        md.update(der)
+        digest = md.final()
+        return hex(util.octx_to_num(digest))[2:-1].upper()
+
+def load_cert(file, format=FORMAT_PEM):
+    """
+    Load certificate from file.
+
+    @type file: string
+    @param file: Name of file containing certificate in either DER or PEM format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the file to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.X509
+    @return: M2Crypto.X509.X509 object.
+    """
+    bio = BIO.openfile(file)
+    if format == FORMAT_PEM:
+        return load_cert_bio(bio)
+    elif format == FORMAT_DER:
+        cptr = m2.d2i_x509(bio._ptr())
+        if cptr is None:
+            raise X509Error(Err.get_error())
+        return X509(cptr, _pyfree=1)
+    else:
+        raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+
+def load_cert_bio(bio, format=FORMAT_PEM):
+    """
+    Load certificate from a bio.
+
+    @type bio: M2Crypto.BIO.BIO
+    @param bio: BIO pointing at a certificate in either DER or PEM format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the cert to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.X509
+    @return: M2Crypto.X509.X509 object.
+    """
+    if format == FORMAT_PEM:
+        cptr = m2.x509_read_pem(bio._ptr())
+    elif format == FORMAT_DER:
+        cptr = m2.d2i_x509(bio._ptr())
+    else:
+        raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+    if cptr is None:
+        raise X509Error(Err.get_error())
+    return X509(cptr, _pyfree=1)
+
+def load_cert_string(string, format=FORMAT_PEM):
+    """
+    Load certificate from a string.
+
+    @type string: string
+    @param string: String containing a certificate in either DER or PEM format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the cert to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.X509
+    @return: M2Crypto.X509.X509 object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    return load_cert_bio(bio, format)
+
+def load_cert_der_string(string):
+    """
+    Load certificate from a string.
+
+    @type string: string
+    @param string: String containing a certificate in DER format.
+
+    @rtype: M2Crypto.X509.X509
+    @return: M2Crypto.X509.X509 object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    cptr = m2.d2i_x509(bio._ptr())
+    if cptr is None:
+        raise X509Error(Err.get_error())
+    return X509(cptr, _pyfree=1)
+
+class X509_Store_Context:
+    """
+    X509 Store Context
+    """
+
+    m2_x509_store_ctx_free = m2.x509_store_ctx_free
+
+    def __init__(self, x509_store_ctx, _pyfree=0):
+        self.ctx = x509_store_ctx
+        self._pyfree = _pyfree
+        
+    def __del__(self):
+        if self._pyfree:
+            self.m2_x509_store_ctx_free(self.ctx)
+            
+    def _ptr(self):
+        return self.ctx
+            
+    def get_current_cert(self):
+        """
+        Get current X.509 certificate.
+        
+        @warning: The returned certificate is NOT refcounted, so you can not
+        rely on it being valid once the store context goes away or is modified.
+        """
+        return X509(m2.x509_store_ctx_get_current_cert(self.ctx), _pyfree=0)
+
+    def get_error(self):
+        """
+        Get error code.
+        """
+        return m2.x509_store_ctx_get_error(self.ctx)
+        
+    def get_error_depth(self):
+        """
+        Get error depth.
+        """
+        return m2.x509_store_ctx_get_error_depth(self.ctx)
+    
+    def get1_chain(self):
+        """
+        Get certificate chain.
+        
+        @return: Reference counted (i.e. safe to use even after the store
+                 context goes away) stack of certificates in the chain.
+        @rtype:  X509_Stack
+        """
+        return X509_Stack(m2.x509_store_ctx_get1_chain(self.ctx), 1, 1)
+        
+
+class X509_Store:
+    """
+    X509 Store
+    """
+
+    m2_x509_store_free = m2.x509_store_free
+
+    def __init__(self, store=None, _pyfree=0):
+        if store is not None:
+            self.store = store
+            self._pyfree = _pyfree
+        else:
+            self.store = m2.x509_store_new()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_store_free(self.store)
+
+    def _ptr(self):
+        return self.store
+
+    def load_info(self, file):
+        ret = m2.x509_store_load_locations(self.store, file) 
+        if ret < 1:
+            raise X509Error(Err.get_error())
+        return ret
+
+    load_locations = load_info
+                 
+    def add_x509(self, x509):
+        assert isinstance(x509, X509)
+        return m2.x509_store_add_cert(self.store, x509._ptr())
+        
+    add_cert = add_x509
+
+
+class X509_Stack:
+    """
+    X509 Stack
+
+    @warning: Do not modify the underlying OpenSSL stack
+    except through this interface, or use any OpenSSL functions that do so
+    indirectly. Doing so will get the OpenSSL stack and the internal pystack
+    of this class out of sync, leading to python memory leaks, exceptions
+    or even python crashes!
+    """
+
+    m2_sk_x509_free = m2.sk_x509_free
+
+    def __init__(self, stack=None, _pyfree=0, _pyfree_x509=0):
+        if stack is not None:
+            self.stack = stack
+            self._pyfree = _pyfree
+            self.pystack = [] # This must be kept in sync with self.stack
+            num = m2.sk_x509_num(self.stack)
+            for i in range(num):
+                self.pystack.append(X509(m2.sk_x509_value(self.stack, i),
+                                         _pyfree=_pyfree_x509))
+        else:
+            self.stack = m2.sk_x509_new_null()
+            self._pyfree = 1
+            self.pystack = [] # This must be kept in sync with self.stack
+        
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_sk_x509_free(self.stack)
+            
+    def __len__(self):
+        assert m2.sk_x509_num(self.stack) == len(self.pystack)
+        return len(self.pystack)
+
+    def __getitem__(self, idx):
+        return self.pystack[idx]
+    
+    def __iter__(self):
+        return iter(self.pystack)
+
+    def _ptr(self):
+        return self.stack
+
+    def push(self, x509):
+        """
+        push an X509 certificate onto the stack.
+        
+        @param x509: X509 object.
+        @return: The number of X509 objects currently on the stack.
+        """
+        assert isinstance(x509, X509)
+        self.pystack.append(x509)
+        ret = m2.sk_x509_push(self.stack, x509._ptr())
+        assert ret == len(self.pystack)
+        return ret
+
+    def pop(self):
+        """
+        pop a certificate from the stack.
+        
+        @return: X509 object that was popped, or None if there is nothing
+        to pop. 
+        """
+        x509_ptr = m2.sk_x509_pop(self.stack)
+        if x509_ptr is None:
+            assert len(self.pystack) == 0
+            return None
+        return self.pystack.pop()
+
+    def as_der(self):
+        """
+        Return the stack as a DER encoded string
+        """
+        return m2.get_der_encoding_stack(self.stack)     
+
+
+def new_stack_from_der(der_string):
+    """
+    Create a new X509_Stack from DER string.
+    
+    @return: X509_Stack
+    """
+    stack_ptr = m2.make_stack_from_der_sequence(der_string)
+    if stack_ptr is None:
+        raise X509Error(Err.get_error())
+    return X509_Stack(stack_ptr, 1, 1)
+
+
+class Request:
+    """
+    X509 Certificate Request.
+    """
+
+    m2_x509_req_free = m2.x509_req_free
+
+    def __init__(self, req=None, _pyfree=0):
+        if req is not None:
+            self.req = req
+            self._pyfree = _pyfree
+        else:
+            self.req = m2.x509_req_new()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_req_free(self.req)
+            
+    def as_text(self):
+        buf=BIO.MemoryBuffer()
+        m2.x509_req_print(buf.bio_ptr(), self.req)
+        return buf.read_all()
+
+    def as_pem(self):
+        buf=BIO.MemoryBuffer()
+        m2.x509_req_write_pem(buf.bio_ptr(), self.req)
+        return buf.read_all()
+
+    def as_der(self):
+        buf = BIO.MemoryBuffer()
+        m2.i2d_x509_req_bio(buf.bio_ptr(), self.req)
+        return buf.read_all()
+
+    def save_pem(self, filename):
+        bio=BIO.openfile(filename, 'wb')
+        return m2.x509_req_write_pem(bio.bio_ptr(), self.req)
+    
+    def save(self, filename, format=FORMAT_PEM):
+        """
+        Saves X.509 certificate request to a file. Default output
+        format is PEM.
+
+        @type filename: string
+        @param filename: Name of the file the request will be saved to.
+        @type format: int
+        @param format: Controls what output format is used to save the request.
+        Either FORMAT_PEM or FORMAT_DER to save in PEM or DER format.
+        Raises ValueError if an unknown format is used.
+        """
+        bio = BIO.openfile(filename, 'wb')
+        if format == FORMAT_PEM:
+            return m2.x509_req_write_pem(bio.bio_ptr(), self.req)
+        elif format == FORMAT_DER:
+            return m2.i2d_x509_req_bio(bio.bio_ptr(), self.req)
+        else:
+            raise ValueError("Unknown filetype. Must be either FORMAT_DER or FORMAT_PEM")
+
+    def get_pubkey(self):
+        """
+        Get the public key for the request.
+
+        @rtype:      EVP_PKEY
+        @return:     Public key from the request.
+        """
+        return EVP.PKey(m2.x509_req_get_pubkey(self.req), _pyfree=1)
+
+    def set_pubkey(self, pkey):
+        """
+        Set the public key for the request.
+
+        @type pkey:  EVP_PKEY
+        @param pkey: Public key
+
+        @rtype:      int
+        @return:     Return 1 for success and 0 for failure.
+        """
+        return m2.x509_req_set_pubkey( self.req, pkey.pkey )
+
+    def get_version(self):
+        """
+        Get version.
+
+        @rtype:         int
+        @return:        Returns version.
+        """
+        return m2.x509_req_get_version(self.req)
+
+    def set_version(self, version):
+        """
+        Set version.
+
+        @type version:  int
+        @param version: Version number.
+        @rtype:         int
+        @return:        Returns 0 on failure.
+        """
+        return m2.x509_req_set_version( self.req, version )
+
+    def get_subject(self):
+        return X509_Name(m2.x509_req_get_subject_name( self.req ))
+
+    def set_subject_name(self, name):
+        """
+        Set subject name.
+
+        @type name:     X509_Name
+        @param name:    subjectName field.
+        """
+        return m2.x509_req_set_subject_name( self.req, name.x509_name )
+
+    set_subject = set_subject_name
+
+    def add_extensions(self, ext_stack):
+        """
+        Add X509 extensions to this request.
+
+        @type ext_stack:  X509_Extension_Stack
+        @param ext_stack: Stack of extensions to add.
+        """
+        return m2.x509_req_add_extensions(self.req, ext_stack._ptr())
+
+    def verify(self, pkey):
+        return m2.x509_req_verify(self.req, pkey.pkey)
+
+    def sign(self, pkey, md):
+        mda = getattr(m2, md, None)
+        if mda is None:
+            raise ValueError, ('unknown message digest', md)
+        return m2.x509_req_sign(self.req, pkey.pkey, mda())
+
+
+def load_request(file, format=FORMAT_PEM):
+    """
+    Load certificate request from file.
+
+    @type file: string
+    @param file: Name of file containing certificate request in either PEM or DER format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the file to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.Request
+    @return: M2Crypto.X509.Request object.
+    """
+    f=BIO.openfile(file)
+    if format == FORMAT_PEM:
+        cptr=m2.x509_req_read_pem(f.bio_ptr())
+    elif format == FORMAT_DER:
+        cptr = m2.d2i_x509_req(f.bio_ptr())
+    else:
+        raise ValueError("Unknown filetype. Must be either FORMAT_PEM or FORMAT_DER")
+    f.close()
+    if cptr is None:
+        raise X509Error(Err.get_error())
+    return Request(cptr, 1)
+
+def load_request_bio(bio, format=FORMAT_PEM):
+    """
+    Load certificate request from a bio.
+
+    @type bio: M2Crypto.BIO.BIO
+    @param bio: BIO pointing at a certificate request in either DER or PEM format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the request to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.Request
+    @return: M2Crypto.X509.Request object.
+    """
+    if format == FORMAT_PEM:
+        cptr = m2.x509_req_read_pem(bio._ptr())
+    elif format == FORMAT_DER:
+        cptr = m2.d2i_x509_req(bio._ptr())
+    else:
+        raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+    if cptr is None:
+        raise X509Error(Err.get_error())
+    return Request(cptr, _pyfree=1)
+
+def load_request_string(string, format=FORMAT_PEM):
+    """
+    Load certificate request from a string.
+
+    @type string: string
+    @param string: String containing a certificate request in either DER or PEM format.
+    @type format: int, either FORMAT_PEM or FORMAT_DER
+    @param format: Describes the format of the request to be loaded, either PEM or DER.
+
+    @rtype: M2Crypto.X509.Request
+    @return: M2Crypto.X509.Request object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    return load_request_bio(bio, format)
+
+def load_request_der_string(string):
+    """
+    Load certificate request from a string.
+
+    @type string: string
+    @param string: String containing a certificate request in DER format.
+
+    @rtype: M2Crypto.X509.Request
+    @return: M2Crypto.X509.Request object.
+    """
+    bio = BIO.MemoryBuffer(string)
+    return load_request_bio(bio, FORMAT_DER)
+
+
+class CRL:
+    """
+    X509 Certificate Revocation List
+    """
+
+    m2_x509_crl_free = m2.x509_crl_free
+
+    def __init__(self, crl=None, _pyfree=0):
+        if crl is not None:
+            self.crl = crl
+            self._pyfree = _pyfree
+        else:
+            self.crl = m2.x509_crl_new()
+            self._pyfree = 1
+            
+    def __del__(self):
+        if getattr(self, '_pyfree', 0):
+            self.m2_x509_crl_free(self.crl)
+
+    def as_text(self):
+        """
+        Return CRL in PEM format in a string.
+
+        @rtype: string
+        @return: String containing the CRL in PEM format.
+        """
+        buf=BIO.MemoryBuffer()
+        m2.x509_crl_print(buf.bio_ptr(), self.crl)
+        return buf.read_all()
+
+
+def load_crl(file):
+    """
+    Load CRL from file.
+
+    @type file: string
+    @param file: Name of file containing CRL in PEM format.
+
+    @rtype: M2Crypto.X509.CRL
+    @return: M2Crypto.X509.CRL object.
+    """
+    f=BIO.openfile(file)
+    cptr=m2.x509_crl_read_pem(f.bio_ptr())
+    f.close()
+    if cptr is None:
+        raise X509Error(Err.get_error())
+    return CRL(cptr, 1)
+
+
diff --git a/M2Crypto/__init__.py b/M2Crypto/__init__.py
new file mode 100644 (file)
index 0000000..e7acfe7
--- /dev/null
@@ -0,0 +1,60 @@
+"""
+M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+functionality to implement clients and servers; HTTPS extensions to
+Python's httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies
+for web session management; FTP/TLS client and server; S/MIME; ZServerSSL:
+A HTTPS server for Zope and ZSmime: An S/MIME messenger for Zope.
+M2Crypto can also be used to provide SSL for Twisted. Smartcards supported
+through the Engine interface.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008-2011 Heikki Toivonen. All rights reserved.
+"""
+
+version_info = (0, 21, 1)
+version = '.'.join([str(_v) for _v in version_info])
+
+import __m2crypto
+import m2
+import ASN1
+import AuthCookie
+import BIO
+import BN
+import Rand
+import DH
+import DSA
+if m2.OPENSSL_VERSION_NUMBER >= 0x90800F and m2.OPENSSL_NO_EC == 0:
+    import EC
+import Err
+import Engine
+import EVP
+import RSA
+import RC4
+import SMIME
+import SSL
+import X509
+import PGP
+import m2urllib
+# Backwards compatibility.
+urllib2 = m2urllib
+
+import sys
+if sys.version_info >= (2,4):
+    import m2urllib2
+del sys
+
+import ftpslib
+import httpslib
+import m2xmlrpclib
+import threading
+import util
+
+encrypt=1
+decrypt=0
+
+__m2crypto.lib_init()
diff --git a/M2Crypto/callback.py b/M2Crypto/callback.py
new file mode 100644 (file)
index 0000000..46613ca
--- /dev/null
@@ -0,0 +1,9 @@
+"""Deprecated, use the util module instead.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import warnings
+
+warnings.warn('Use the util module instead', DeprecationWarning)
+
+from util import genparam_callback, passphrase_callback
diff --git a/M2Crypto/ftpslib.py b/M2Crypto/ftpslib.py
new file mode 100644 (file)
index 0000000..b7d82fd
--- /dev/null
@@ -0,0 +1,94 @@
+"""M2Crypto client-side FTP/TLS.
+
+This implementation complies with draft-murray-auth-ftp-ssl-07.txt.
+
+Example:
+
+>>> from M2Crypto import ftpslib
+>>> f = ftpslib.FTP_TLS()
+>>> f.connect('', 9021)
+'220 spinnaker.dyndns.org M2Crypto (Medusa) FTP/TLS server v0.07 ready.'
+>>> f.auth_tls()
+>>> f.set_pasv(0)
+>>> f.login('ftp', 'ngps@')
+'230 Ok.'
+>>> f.retrlines('LIST')
+-rw-rw-r--   1 0        198          2326 Jul  3  1996 apache_pb.gif
+drwxrwxr-x   7 0        198          1536 Oct 10  2000 manual
+drwxrwxr-x   2 0        198           512 Oct 31  2000 modpy
+drwxrwxr-x   2 0        198           512 Oct 31  2000 bobo
+drwxr-xr-x   2 0        198         14336 May 28 15:54 postgresql
+drwxr-xr-x   4 100      198           512 May 16 17:19 home
+drwxr-xr-x   7 100      100          3584 Sep 23  2000 openacs
+drwxr-xr-x  10 0        0             512 Aug  5  2000 python1.5
+-rw-r--r--   1 100      198           326 Jul 29 03:29 index.html
+drwxr-xr-x  12 0        0             512 May 31 17:08 python2.1
+'226 Transfer complete'
+>>> f.quit()
+'221 Goodbye.'
+>>>
+
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+from ftplib import *
+from ftplib import parse150, parse227
+from ftplib import error_reply, error_temp, error_perm, error_proto
+import socket, time
+
+# M2Crypto
+import SSL
+
+DEFAULT_PROTOCOL='sslv23'
+
+class FTP_TLS(FTP):
+
+    """Python OO interface to client-side FTP/TLS."""
+
+    def __init__(self, host=None, ssl_ctx=None):
+        """Initialise the client. If 'host' is supplied, connect to it."""
+        if ssl_ctx is not None:
+            self.ssl_ctx = ssl_ctx
+        else:
+            self.ssl_ctx = SSL.Context(DEFAULT_PROTOCOL)
+        FTP.__init__(self, host)
+        self.prot = 0
+
+    def auth_tls(self):
+        """Secure the control connection per AUTH TLS, aka AUTH TLS-C."""
+        self.voidcmd('AUTH TLS')
+        s = SSL.Connection(self.ssl_ctx, self.sock)
+        s.setup_ssl()
+        s.set_connect_state()
+        s.connect_ssl()
+        self.sock = s
+        self.file = self.sock.makefile()
+
+    def auth_ssl(self):
+        """Secure the control connection per AUTH SSL, aka AUTH TLS-P."""
+        raise NotImplementedError
+
+    def prot_p(self):
+        """Set up secure data connection."""
+        self.voidcmd('PBSZ 0')
+        self.voidcmd('PROT P')
+        self.prot = 1
+            
+    def prot_c(self):
+        """Set up data connection in the clear."""
+        self.voidcmd('PROT C')
+        self.prot = 0
+            
+    def ntransfercmd(self, cmd, rest=None):
+        """Initiate a data transfer."""
+        conn, size = FTP.ntransfercmd(self, cmd, rest)
+        if self.prot:
+            conn = SSL.Connection(self.ssl_ctx, conn)
+            conn.setup_ssl()
+            conn.set_connect_state()
+            conn.set_session(self.sock.get_session())
+            conn.connect_ssl()
+        return conn, size
+
diff --git a/M2Crypto/httpslib.py b/M2Crypto/httpslib.py
new file mode 100644 (file)
index 0000000..c1bfd78
--- /dev/null
@@ -0,0 +1,212 @@
+"""M2Crypto support for Python's httplib. 
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import string, sys
+import socket
+from urlparse import urlsplit, urlunsplit
+import base64
+
+from httplib import *
+from httplib import HTTPS_PORT # This is not imported with just '*'
+import SSL
+
+class HTTPSConnection(HTTPConnection):
+
+    """
+    This class allows communication via SSL using M2Crypto.
+    """
+
+    default_port = HTTPS_PORT
+
+    def __init__(self, host, port=None, strict=None, **ssl):
+        self.session = None
+        keys = ssl.keys()
+        try: 
+            keys.remove('key_file')
+        except ValueError:
+            pass
+        try:
+            keys.remove('cert_file')
+        except ValueError:
+            pass
+        try:
+            keys.remove('ssl_context')
+        except ValueError:
+            pass
+        if keys:
+            raise ValueError('unknown keyword argument')
+        try:
+            self.ssl_ctx = ssl['ssl_context']
+            assert isinstance(self.ssl_ctx, SSL.Context), self.ssl_ctx
+        except KeyError:
+            self.ssl_ctx = SSL.Context('sslv23')
+        HTTPConnection.__init__(self, host, port, strict)
+
+    def connect(self):
+        self.sock = SSL.Connection(self.ssl_ctx)
+        if self.session:
+            self.sock.set_session(self.session)
+        self.sock.connect((self.host, self.port))
+
+    def close(self):
+        # This kludges around line 545 of httplib.py,
+        # which closes the connection in this object;
+        # the connection remains open in the response
+        # object.
+        #
+        # M2Crypto doesn't close-here-keep-open-there,
+        # so, in effect, we don't close until the whole 
+        # business is over and gc kicks in.
+        #
+        # XXX Long-running callers beware leakage.
+        #
+        # XXX 05-Jan-2002: This module works with Python 2.2,
+        # XXX but I've not investigated if the above conditions
+        # XXX remain.
+        pass
+    
+    def get_session(self):
+        return self.sock.get_session()
+
+    def set_session(self, session):
+        self.session = session
+        
+
+class HTTPS(HTTP):
+    
+    _connection_class = HTTPSConnection
+
+    def __init__(self, host='', port=None, strict=None, **ssl):
+        HTTP.__init__(self, host, port, strict)
+        try:
+            self.ssl_ctx = ssl['ssl_context']
+        except KeyError:
+            self.ssl_ctx = SSL.Context('sslv23')
+        assert isinstance(self._conn, HTTPSConnection)
+        self._conn.ssl_ctx = self.ssl_ctx
+
+
+class ProxyHTTPSConnection(HTTPSConnection):
+
+    """
+    An HTTPS Connection that uses a proxy and the CONNECT request.
+
+    When the connection is initiated, CONNECT is first sent to the proxy (along
+    with authorization headers, if supplied). If successful, an SSL connection
+    will be established over the socket through the proxy and to the target
+    host.
+
+    Finally, the actual request is sent over the SSL connection tunneling
+    through the proxy.
+    """
+
+    _ports = {'http' : 80, 'https' : 443}
+    _AUTH_HEADER = "Proxy-Authorization"
+    _UA_HEADER = "User-Agent"
+
+    def __init__(self, host, port=None, strict=None, username=None,
+        password=None, **ssl):
+        """
+        Create the ProxyHTTPSConnection object.
+
+        host and port are the hostname and port number of the proxy server.
+        """
+        HTTPSConnection.__init__(self, host, port, strict, **ssl)
+
+        self._username = username
+        self._password = password
+        self._proxy_auth = None
+        self._proxy_UA = None
+
+    def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
+        #putrequest is called before connect, so can interpret url and get
+        #real host/port to be used to make CONNECT request to proxy
+        proto, netloc, path, query, fragment = urlsplit(url)
+        if not proto:
+            raise ValueError, "unknown URL type: %s" % url
+        
+        #get host & port
+        try:
+            username_password, host_port = netloc.split('@')
+        except ValueError:
+            host_port = netloc
+
+        try:
+            host, port = host_port.split(':')
+        except ValueError:
+            host = host_port
+            #try to get port from proto
+            try:
+                port = self._ports[proto]
+            except KeyError:
+                raise ValueError, "unknown protocol for: %s" % url
+
+        self._real_host = host
+        self._real_port = int(port)
+        rest = urlunsplit((None, None, path, query, fragment))
+        if sys.version_info < (2,4):
+            HTTPSConnection.putrequest(self, method, rest, skip_host)
+        else:
+            HTTPSConnection.putrequest(self, method, rest, skip_host, skip_accept_encoding)
+
+    def putheader(self, header, value):
+        # Store the auth header if passed in.
+        if header.lower() == self._UA_HEADER.lower():
+            self._proxy_UA = value
+        if header.lower() == self._AUTH_HEADER.lower():
+            self._proxy_auth = value
+        else:
+            HTTPSConnection.putheader(self, header, value)
+
+    def endheaders(self):
+        # We've recieved all of hte headers. Use the supplied username
+        # and password for authorization, possibly overriding the authstring
+        # supplied in the headers.
+        if not self._proxy_auth:
+            self._proxy_auth = self._encode_auth()
+
+        HTTPSConnection.endheaders(self)
+
+    def connect(self):
+        HTTPConnection.connect(self)
+
+        #send proxy CONNECT request
+        self.sock.sendall(self._get_connect_msg())
+        response = HTTPResponse(self.sock)
+        response.begin()
+        
+        code = response.status
+        if code != 200:
+            #proxy returned and error, abort connection, and raise exception
+            self.close()
+            raise socket.error, "Proxy connection failed: %d" % code
+       
+        self._start_ssl()
+
+    def _get_connect_msg(self):
+        """ Return an HTTP CONNECT request to send to the proxy. """
+        msg = "CONNECT %s:%d HTTP/1.1\r\n" % (self._real_host, self._real_port)
+        msg = msg + "Host: %s:%d\r\n" % (self._real_host, self._real_port)
+        if self._proxy_UA:
+            msg = msg + "%s: %s\r\n" % (self._UA_HEADER, self._proxy_UA)
+        if self._proxy_auth:
+            msg = msg + "%s: %s\r\n" % (self._AUTH_HEADER, self._proxy_auth) 
+        msg = msg + "\r\n"
+        return msg
+
+    def _start_ssl(self):
+        """ Make this connection's socket SSL-aware. """
+        self.sock = SSL.Connection(self.ssl_ctx, self.sock)
+        self.sock.setup_ssl()
+        self.sock.set_connect_state()
+        self.sock.connect_ssl()
+
+    def _encode_auth(self):
+        """ Encode the username and password for use in the auth header. """
+        if not (self._username and self._password):
+            return None
+        # Authenticated proxy
+        userpass = "%s:%s" % (self._username, self._password)
+        enc_userpass = base64.encodestring(userpass).replace("\n", "")
+        return "Basic %s" % enc_userpass
diff --git a/M2Crypto/m2.py b/M2Crypto/m2.py
new file mode 100644 (file)
index 0000000..e4bb695
--- /dev/null
@@ -0,0 +1,31 @@
+"""M2Crypto low level OpenSSL wrapper functions.
+
+m2 is the low level wrapper for OpenSSL functions. Typically you would not
+need to use these directly, since these will be called by the higher level
+objects you should try to use instead.
+
+Naming conventions: All functions wrapped by m2 are all lower case,
+words separated by underscores.
+
+Examples:
+
+OpenSSL                   M2Crypto
+
+X509_get_version          m2.x509_get_version
+X509_get_notBefore        m2.x509_get_not_before
+X509_REQ_verify           m2.x509_req_verify
+
+Exceptions to naming rules:
+
+XXX TDB
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+from __m2crypto import *
+lib_init()
+
+
diff --git a/M2Crypto/m2urllib.py b/M2Crypto/m2urllib.py
new file mode 100644 (file)
index 0000000..d951eb2
--- /dev/null
@@ -0,0 +1,70 @@
+"""M2Crypto enhancement to Python's urllib for handling 
+'https' url's.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import string, sys, urllib
+from urllib import *
+
+import SSL
+import httpslib
+
+DEFAULT_PROTOCOL='sslv23'
+
+def open_https(self, url, data=None, ssl_context=None):
+    if ssl_context is not None and isinstance(ssl_context, SSL.Context):
+        self.ctx = ssl_context
+    else:
+        self.ctx = SSL.Context(DEFAULT_PROTOCOL)
+    user_passwd = None
+    if type(url) is type(""):
+        host, selector = splithost(url)
+        if host:
+            user_passwd, host = splituser(host)
+            host = unquote(host)
+        realhost = host
+    else:
+        host, selector = url
+        urltype, rest = splittype(selector)
+        url = rest
+        user_passwd = None
+        if string.lower(urltype) != 'http':
+            realhost = None
+        else:
+            realhost, rest = splithost(rest)
+            if realhost:
+                user_passwd, realhost = splituser(realhost)
+            if user_passwd:
+                selector = "%s://%s%s" % (urltype, realhost, rest)
+        #print "proxy via http:", host, selector
+    if not host: raise IOError, ('http error', 'no host given')
+    if user_passwd:
+        import base64
+        auth = string.strip(base64.encodestring(user_passwd))
+    else:
+        auth = None
+    # Start here!
+    h = httpslib.HTTPSConnection(host=host, ssl_context=self.ctx)
+    #h.set_debuglevel(1)
+    # Stop here!
+    if data is not None:
+        h.putrequest('POST', selector)
+        h.putheader('Content-type', 'application/x-www-form-urlencoded')
+        h.putheader('Content-length', '%d' % len(data))
+    else:
+        h.putrequest('GET', selector)
+    if auth: h.putheader('Authorization', 'Basic %s' % auth)
+    for args in self.addheaders: apply(h.putheader, args)
+    h.endheaders()
+    if data is not None:
+        h.send(data + '\r\n')
+    # Here again!
+    resp = h.getresponse()
+    fp = resp.fp
+    return urllib.addinfourl(fp, resp.msg, "https:" + url)
+    # Stop again.
+
+# Minor brain surgery. 
+URLopener.open_https = open_https
+
diff --git a/M2Crypto/m2urllib2.py b/M2Crypto/m2urllib2.py
new file mode 100644 (file)
index 0000000..e500410
--- /dev/null
@@ -0,0 +1,152 @@
+"""
+M2Crypto enhancement to Python's urllib2 for handling 
+'https' url's.
+
+Code from urllib2 is Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007
+Python Software Foundation; All Rights Reserved
+
+Summary of changes:
+ - Use an HTTPSProxyConnection if the request is going through a proxy.
+ - Add the SSL context to the https connection when performing https_open.
+ - Add the M2Crypto HTTPSHandler when building a default opener.
+"""
+
+import socket
+from urllib2 import *
+import urlparse
+
+import SSL
+import httpslib
+
+
+class _closing_fileobject(socket._fileobject):
+    '''socket._fileobject that propagates self.close() to the socket.
+
+    Python 2.5 provides this as socket._fileobject(sock, close=True).
+    '''
+
+    def __init__(self, sock):
+        socket._fileobject.__init__(self, sock)
+
+    def close(self):
+        sock = self._sock
+        socket._fileobject.close(self)
+        sock.close()
+
+class HTTPSHandler(AbstractHTTPHandler):
+    def __init__(self, ssl_context = None):
+        AbstractHTTPHandler.__init__(self)
+
+        if ssl_context is not None:
+            assert isinstance(ssl_context, SSL.Context), ssl_context
+            self.ctx = ssl_context
+        else:
+            self.ctx = SSL.Context()
+
+    # Copied from urllib2, so we can set the ssl context.
+    def https_open(self, req):
+        """Return an addinfourl object for the request, using http_class.
+
+        http_class must implement the HTTPConnection API from httplib.
+        The addinfourl return value is a file-like object.  It also
+        has methods and attributes including:
+            - info(): return a mimetools.Message object for the headers
+            - geturl(): return the original request URL
+            - code: HTTP status code
+        """
+        host = req.get_host()
+        if not host:
+            raise URLError('no host given')
+
+        # Our change: Check to see if we're using a proxy.
+        # Then create an appropriate ssl-aware connection.
+        full_url = req.get_full_url() 
+        target_host = urlparse.urlparse(full_url)[1]
+
+        if (target_host != host):
+            h = httpslib.ProxyHTTPSConnection(host = host, ssl_context = self.ctx)
+        else:
+            h = httpslib.HTTPSConnection(host = host, ssl_context = self.ctx)
+        # End our change
+        h.set_debuglevel(self._debuglevel)
+
+        headers = dict(req.headers)
+        headers.update(req.unredirected_hdrs)
+        # We want to make an HTTP/1.1 request, but the addinfourl
+        # class isn't prepared to deal with a persistent connection.
+        # It will try to read all remaining data from the socket,
+        # which will block while the server waits for the next request.
+        # So make sure the connection gets closed after the (only)
+        # request.
+        headers["Connection"] = "close"
+        try:
+            h.request(req.get_method(), req.get_selector(), req.data, headers)
+            r = h.getresponse()
+        except socket.error, err: # XXX what error?
+            raise URLError(err)
+
+        # Pick apart the HTTPResponse object to get the addinfourl
+        # object initialized properly.
+
+        # Wrap the HTTPResponse object in socket's file object adapter
+        # for Windows.  That adapter calls recv(), so delegate recv()
+        # to read().  This weird wrapping allows the returned object to
+        # have readline() and readlines() methods.
+
+        # XXX It might be better to extract the read buffering code
+        # out of socket._fileobject() and into a base class.
+
+        r.recv = r.read
+        fp = _closing_fileobject(r)
+
+        resp = addinfourl(fp, r.msg, req.get_full_url())
+        resp.code = r.status
+        resp.msg = r.reason
+        return resp
+
+        
+    https_request = AbstractHTTPHandler.do_request_
+
+
+# Copied from urllib2 with modifications for ssl
+def build_opener(ssl_context = None, *handlers):
+    """Create an opener object from a list of handlers.
+
+    The opener will use several default handlers, including support
+    for HTTP and FTP.
+
+    If any of the handlers passed as arguments are subclasses of the
+    default handlers, the default handlers will not be used.
+    """
+    import types
+    def isclass(obj):
+        return isinstance(obj, types.ClassType) or hasattr(obj, "__bases__")
+
+    opener = OpenerDirector()
+    default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
+                       HTTPDefaultErrorHandler, HTTPRedirectHandler,
+                       FTPHandler, FileHandler, HTTPErrorProcessor]
+    skip = []
+    for klass in default_classes:
+        for check in handlers:
+            if isclass(check):
+                if issubclass(check, klass):
+                    skip.append(klass)
+            elif isinstance(check, klass):
+                skip.append(klass)
+    for klass in skip:
+        default_classes.remove(klass)
+
+    for klass in default_classes:
+        opener.add_handler(klass())
+
+    # Add the HTTPS handler with ssl_context
+    if HTTPSHandler not in skip:
+        opener.add_handler(HTTPSHandler(ssl_context))
+
+
+    for h in handlers:
+        if isclass(h):
+            h = h()
+        opener.add_handler(h)
+    return opener
diff --git a/M2Crypto/m2xmlrpclib.py b/M2Crypto/m2xmlrpclib.py
new file mode 100644 (file)
index 0000000..bb50a01
--- /dev/null
@@ -0,0 +1,65 @@
+"""M2Crypto enhancement to xmlrpclib.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import base64, string, sys
+
+from xmlrpclib import *
+import M2Crypto
+import SSL, httpslib, m2urllib
+
+__version__ = M2Crypto.version
+
+class SSL_Transport(Transport):
+
+    user_agent = "M2Crypto_XMLRPC/%s - %s" % (__version__, Transport.user_agent)
+
+    def __init__(self, ssl_context=None, *args, **kw):
+        if getattr(Transport, '__init__', None) is not None:
+            Transport.__init__(self, *args, **kw)
+        if ssl_context is None:
+            self.ssl_ctx=SSL.Context('sslv23')
+        else:
+            self.ssl_ctx=ssl_context
+
+    def request(self, host, handler, request_body, verbose=0):
+        # Handle username and password.
+        user_passwd, host_port = m2urllib.splituser(host)
+        _host, _port = m2urllib.splitport(host_port)
+        h = httpslib.HTTPS(_host, int(_port), ssl_context=self.ssl_ctx)
+        if verbose:
+            h.set_debuglevel(1)
+
+        # What follows is as in xmlrpclib.Transport. (Except the authz bit.)
+        h.putrequest("POST", handler)
+
+        # required by HTTP/1.1
+        h.putheader("Host", _host)
+
+        # required by XML-RPC
+        h.putheader("User-Agent", self.user_agent)
+        h.putheader("Content-Type", "text/xml")
+        h.putheader("Content-Length", str(len(request_body)))
+
+        # Authorisation.
+        if user_passwd is not None:
+            auth=string.strip(base64.encodestring(user_passwd))
+            h.putheader('Authorization', 'Basic %s' % auth)
+
+        h.endheaders()
+
+        if request_body:
+            h.send(request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise ProtocolError(
+                host + handler,
+                errcode, errmsg,
+                headers
+                )
+
+        self.verbose = verbose
+        return self.parse_response(h.getfile())
+
diff --git a/M2Crypto/threading.py b/M2Crypto/threading.py
new file mode 100644 (file)
index 0000000..4cb8149
--- /dev/null
@@ -0,0 +1,20 @@
+"""
+M2Crypto threading support, required for multithreaded applications. 
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+import m2
+
+def init():
+    """
+    Initialize threading support.
+    """
+    m2.threading_init()
+
+def cleanup():
+    """
+    End and cleanup threading support.
+    """
+    m2.threading_cleanup()
+
diff --git a/M2Crypto/util.py b/M2Crypto/util.py
new file mode 100644 (file)
index 0000000..12103fc
--- /dev/null
@@ -0,0 +1,71 @@
+"""
+    M2Crypto utility routines.
+    
+    Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+    
+    Portions created by Open Source Applications Foundation (OSAF) are
+    Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+import sys
+import m2
+
+class UtilError(Exception): pass
+
+m2.util_init(UtilError)
+
+def h2b(s):
+    import array, string
+    ar=array.array('c')
+    start=0
+    if s[:2]=='0x':
+        start=2
+    for i in range(start, len(s), 2):
+        num=string.atoi("%s"%(s[i:i+2],), 16)
+        ar.append(chr(num))
+    return ar.tostring()        
+
+def pkcs5_pad(data, blklen=8):
+    pad=(8-(len(data)%8))
+    return data+chr(pad)*pad
+
+def pkcs7_pad(data, blklen):
+    if blklen>255:
+        raise ValueError, 'illegal block size'
+    pad=(blklen-(len(data)%blklen))
+    return data+chr(pad)*pad
+
+def octx_to_num(x):
+    v = 0L
+    lx = len(x)
+    for i in range(lx):
+        v = v + ord(x[i]) * (256L ** (lx-i-1))
+    return v
+
+def genparam_callback(p, n, out=sys.stdout):
+    ch = ['.','+','*','\n']
+    out.write(ch[p])
+    out.flush()
+
+def quiet_genparam_callback(p, n, out):
+    pass
+
+def passphrase_callback(v, prompt1='Enter passphrase:', 
+                           prompt2='Verify passphrase:'):
+    from getpass import getpass
+    while 1:
+        try:
+            p1=getpass(prompt1)
+            if v:
+                p2=getpass(prompt2)
+                if p1==p2:
+                    break
+            else:
+                break
+        except KeyboardInterrupt:
+            return None
+    return p1
+
+def no_passphrase_callback(*args):
+    return ''
+
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..3972df5
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: M2Crypto
+Version: 0.21.1
+Summary: M2Crypto: A Python crypto and SSL toolkit
+Home-page: http://chandlerproject.org/Projects/MeTooCrypto
+Author: Heikki Toivonen
+Author-email: heikki@osafoundation.org
+License: BSD-style license
+Description: M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+        DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+        functionality to implement clients and servers; HTTPS extensions to Python's
+        httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+        session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+        server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+        used to provide SSL for Twisted.
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Security :: Cryptography
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..99affa0
--- /dev/null
+++ b/README
@@ -0,0 +1,61 @@
+=========
+ M2Crypto
+=========
+
+:Maintainer: Heikki Toivonen
+:Web-Site: http://chandlerproject.org/Projects/MeTooCrypto
+
+
+M2Crypto = Python + OpenSSL + SWIG
+------------------------------------
+
+M2Crypto is a crypto and SSL toolkit for Python.
+
+M2 stands for "me, too!"
+
+M2Crypto comes with the following:
+
+- **RSA**, **DSA**, **DH**, **HMACs**, **message digests**,
+  **symmetric ciphers** including **AES**,
+
+- **SSL** functionality to implement **clients and servers**.
+
+- **Example SSL client and server programs**, which are variously
+  **threading**, **forking** or based on **non-blocking socket IO**.
+
+- **HTTPS** extensions to Python's **httplib, urllib and xmlrpclib**.
+
+- Unforgeable HMAC'ing **AuthCookies** for **web session management**.
+
+- **FTP/TLS** client and server.
+
+- **S/MIME v2**.
+
+- **ZServerSSL**: A **HTTPS server for Zope**.
+
+- **ZSmime**: An S/MIME messenger for **Zope**.
+
+- And much more.
+
+M2Crypto is released under a very liberal BSD-style licence. See 
+LICENCE for details.
+
+To install, see the file INSTALL.
+
+Look at the tests and demos for example use. Recommended reading before
+deploying in production is "Network Security with OpenSSL" by John Viega,
+Matt Messier and Pravir Chandra, ISBN 059600270X.
+
+Note these caveats:
+
+- Possible memory leaks, because some objects need to be freed on the
+  Python side and other objects on the C side, and these may change
+  between OpenSSL versions. (Multiple free's lead to crashes very
+  quickly, so these should be relatively rare.)
+    
+- No memory locking/clearing for keys, passphrases, etc. because AFAIK
+  Python does not provide the features needed. On the C (OpenSSL) side
+  things are cleared when the Python objects are deleted.
+    
+
+Have fun! Your feedback is welcome.
diff --git a/SWIG/Makefile b/SWIG/Makefile
new file mode 100644 (file)
index 0000000..7d73a3b
--- /dev/null
@@ -0,0 +1,25 @@
+# $Id: Makefile 299 2005-06-09 17:32:28Z heikki $
+
+CFLAGS    = -DTHREADING -g
+INCLUDE           = -I/usr/local/include -I.
+LIBS       = -L/usr/local/lib -lssl -lcrypto
+#if PYTHON_VERSION
+PYVER = $(PYTHON_VERSION)
+#else
+PYVER = 2.3
+#endif
+PYINCLUDE  = -DHAVE_CONFIG_H -I/usr/local/include/python$(PYVER) \
+       -I/usr/local/lib/python$(PYVER)/config
+PYLIB      = /usr/local/lib/python$(PYVER)/config
+
+all:   _m2crypto
+
+_m2crypto:     _m2crypto.i
+       swig -python -shadow _m2crypto.i
+       cc -c -fpic $(CFLAGS) $(INCLUDE) $(PYINCLUDE) _m2crypto_wrap.c
+       ld -Bshareable -o __m2crypto.so _m2crypto_wrap.o $(LIBS) 
+       cp _m2crypto.py __m2crypto.so ../M2Crypto
+
+clean:
+       rm -f *_wrap* *.o *.so _*.py *.pyc
+
diff --git a/SWIG/Makefile.mw b/SWIG/Makefile.mw
new file mode 100644 (file)
index 0000000..02f5ae5
--- /dev/null
@@ -0,0 +1,34 @@
+# $Id: Makefile.mw 299 2005-06-09 17:32:28Z heikki $
+
+# Python
+PYFLAGS=-D__WIN32__ -DHAVE_CONFIG_H -Ic:/pkg/py23/include
+PYLIB=c:/pkg/py23/libs/libpython23.a
+PYINCLUDE=-Ic:/pkg/py23/include
+
+# OpenSSL
+SSLINCLUDE=-Ic:/pkg/openssl/include
+SSLLIB=c:/pkg/openssl/lib/libssl32.a c:/pkg/openssl/lib/libeay32.a
+
+# Windoze
+INCLUDE=$(PYINCLUDE) $(SSLINCLUDE) -I.
+LIBS=$(PYLIB) $(SSLLIB)
+
+SWIG=c:/pkg/swig/swig.exe
+SWIGFLAGS=-shadow -python #-verbose
+
+CP=cp
+
+all:   swig
+
+swig: _m2crypto.i
+       $(SWIG) $(SWIGFLAGS) _m2crypto.i
+       gcc -c -DTHREADING -g $(INCLUDE) _m2crypto_wrap.c
+       dllwrap --dllname __m2crypto.pyd --driver-name gcc \
+            --def _m2crypto.def -o __m2crypto.pyd _m2crypto_wrap.o \
+            -s --entry _DllMain@12 --target=i386-mingw32 $(LIBS)
+       $(CP) _m2crypto.py ..\M2Crypto
+       $(CP) __m2crypto.pyd ..\M2Crypto
+
+clean:
+       del *wrap* *.o *.dll *.exp *.ilk *.pdb *.lib _*.py *.pyc
+
diff --git a/SWIG/Makefile.osx b/SWIG/Makefile.osx
new file mode 100644 (file)
index 0000000..a8508a6
--- /dev/null
@@ -0,0 +1,32 @@
+# $Id: Makefile.osx 299 2005-06-09 17:32:28Z heikki $
+
+# 2003-10-26, ngps: Beware any mixup of tabs and spaces caused by me.
+
+WHICHOPENSSL = /usr/local
+
+CFLAGS    = -DTHREADING -DHAVE_CONFIG -g -O2
+INCLUDE           = -I. -I/Library/Frameworks/Python.framework/Headers
+LIBS       = $(WHICHOPENSSL)/lib/libssl.a      \
+               $(WHICHOPENSSL)/lib/libcrypto.a   \
+               /Library/Frameworks/Python.framework/python
+
+all:   __m2crypto.so
+
+_m2crypto_wrap.c: _m2crypto.i
+       swig -shadow -python _m2crypto.i
+
+_m2crypto_wrap.o: _m2crypto_wrap.c
+       cc  -c  $(CFLAGS)  $(INCLUDES)  _m2crypto_wrap.c
+
+__m2crypto.so: _m2crypto_wrap.o
+       cc -bundle _m2crypto_wrap.o $(LIBS) -lcc_dynamic -o __m2crypto.so 
+       cp __m2crypto.so ../M2Crypto
+
+clean:
+       rm -f *_wrap* *.o *.so _*.py *.pyc
+
+versions:
+       python -c "import sys, os;                               \
+               print os.popen('gcc --version').readlines()[0],;     \
+               print 'Python '+sys.version.split()[0];              \
+               print os.popen('$(WHICHOPENSSL)/bin/openssl version').readlines()[0]"
diff --git a/SWIG/_aes.i b/SWIG/_aes.i
new file mode 100644 (file)
index 0000000..be0af87
--- /dev/null
@@ -0,0 +1,85 @@
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/* $Id: _aes.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <openssl/evp.h>
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#include <openssl/aes.h>
+#endif
+
+/* 
+// 2004-10-10, ngps: 
+// CTR mode is not included in the default OpenSSL build.
+// To use the AES CTR ciphers, link with your own copy of OpenSSL.
+*/
+#ifdef HAVE_AES_CTR
+extern EVP_CIPHER const *EVP_aes_128_ctr(void);
+extern EVP_CIPHER const *EVP_aes_192_ctr(void);
+extern EVP_CIPHER const *EVP_aes_256_ctr(void);
+#endif
+%}
+
+%apply Pointer NONNULL { AES_KEY * };
+
+%constant int AES_BLOCK_SIZE = AES_BLOCK_SIZE;
+
+%inline %{
+AES_KEY *aes_new(void) {
+    AES_KEY *key;
+    
+    if (!(key = (AES_KEY *)PyMem_Malloc(sizeof(AES_KEY))))
+        PyErr_SetString(PyExc_MemoryError, "aes_new");
+    return key;
+}   
+
+void AES_free(AES_KEY *key) {
+    PyMem_Free((void *)key);
+}
+
+/* 
+// op == 0: decrypt
+// otherwise: encrypt (Python code will supply the value 1.)
+*/
+PyObject *AES_set_key(AES_KEY *key, PyObject *value, int bits, int op) { 
+    const void *vbuf; 
+    Py_ssize_t vlen;
+
+    if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (op == 0) 
+        AES_set_encrypt_key(vbuf, bits, key);
+    else
+        AES_set_decrypt_key(vbuf, bits, key);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* 
+// op == 0: decrypt
+// otherwise: encrypt (Python code will supply the value 1.)
+*/
+PyObject *AES_crypt(const AES_KEY *key, PyObject *in, int outlen, int op) {
+    const void *buf;
+    Py_ssize_t len;
+    unsigned char *out;
+
+    if (PyObject_AsReadBuffer(in, &buf, &len) == -1)
+        return NULL;
+
+    if (!(out=(unsigned char *)PyMem_Malloc(outlen))) {
+        PyErr_SetString(PyExc_MemoryError, "AES_crypt");
+        return NULL;
+    }
+    if (op == 0)
+        AES_encrypt((const unsigned char *)in, out, key);
+    else
+        AES_decrypt((const unsigned char *)in, out, key);
+    return PyString_FromStringAndSize((char*)out, outlen);
+}
+
+int AES_type_check(AES_KEY *key) {
+    return 1;
+}
+%}
diff --git a/SWIG/_asn1.i b/SWIG/_asn1.i
new file mode 100644 (file)
index 0000000..6dab7ff
--- /dev/null
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.  */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004 OSAF. All Rights Reserved.
+*/
+/* $Id: _asn1.i 696 2009-07-28 03:43:19Z heikki $ */
+
+%{
+#include <openssl/asn1.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { ASN1_OBJECT * };
+%apply Pointer NONNULL { ASN1_STRING * };
+%apply Pointer NONNULL { ASN1_INTEGER * };
+%apply Pointer NONNULL { ASN1_UTCTIME * };
+
+%rename(asn1_object_new) ASN1_OBJECT_new;
+extern ASN1_OBJECT *ASN1_OBJECT_new( void );
+%rename(asn1_object_create) ASN1_OBJECT_create;
+extern ASN1_OBJECT *ASN1_OBJECT_create( int, unsigned char *, int, const char *, const char *);
+%rename(asn1_object_free) ASN1_OBJECT_free;
+extern void ASN1_OBJECT_free( ASN1_OBJECT *);
+%rename(i2d_asn1_object) i2d_ASN1_OBJECT;
+extern int i2d_ASN1_OBJECT( ASN1_OBJECT *, unsigned char **);
+%rename(c2i_asn1_object) c2i_ASN1_OBJECT;
+extern ASN1_OBJECT *c2i_ASN1_OBJECT( ASN1_OBJECT **, CONST098 unsigned char **, long);
+%rename(d2i_asn1_object) d2i_ASN1_OBJECT;
+extern ASN1_OBJECT *d2i_ASN1_OBJECT( ASN1_OBJECT **, CONST098 unsigned char **, long);
+
+%rename(asn1_bit_string_new) ASN1_BIT_STRING_new;
+extern ASN1_BIT_STRING *ASN1_BIT_STRING_new( void );
+
+%rename(asn1_string_new) ASN1_STRING_new;
+extern ASN1_STRING *ASN1_STRING_new( void );
+%rename(asn1_string_free) ASN1_STRING_free;
+extern void ASN1_STRING_free( ASN1_STRING *);
+
+%typemap(in) (const void *, int) { 
+    if (PyString_Check($input)) {
+        Py_ssize_t len;
+
+        $1 = PyString_AsString($input); 
+        len = PyString_Size($input);
+        if (len > INT_MAX) {
+            PyErr_SetString(PyExc_ValueError, "object too large");
+            return NULL;
+        }
+        $2 = len;
+    } else {
+        PyErr_SetString(PyExc_TypeError, "expected string");
+        return NULL;
+    }
+}
+
+%rename(asn1_string_set) ASN1_STRING_set;
+extern int ASN1_STRING_set( ASN1_STRING *, const void *, int);
+
+%typemap(in) (const void *, int);
+
+%rename(asn1_string_print) ASN1_STRING_print;
+%threadallow ASN1_STRING_print;
+extern int ASN1_STRING_print(BIO *, ASN1_STRING *);
+%threadallow ASN1_STRING_print_ex;
+%rename(asn1_string_print_ex) ASN1_STRING_print_ex;
+extern int ASN1_STRING_print_ex(BIO *, ASN1_STRING *, unsigned long);
+
+%rename(asn1_utctime_new) ASN1_UTCTIME_new;
+extern ASN1_UTCTIME *ASN1_UTCTIME_new( void );
+%rename(asn1_utctime_free) ASN1_UTCTIME_free;
+extern void ASN1_UTCTIME_free(ASN1_UTCTIME *);
+%rename(asn1_utctime_check) ASN1_UTCTIME_check;
+extern int ASN1_UTCTIME_check(ASN1_UTCTIME *);
+%rename(asn1_utctime_set) ASN1_UTCTIME_set;
+extern ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *, long);
+%rename(asn1_utctime_set_string) ASN1_UTCTIME_set_string;
+extern int ASN1_UTCTIME_set_string(ASN1_UTCTIME *, CONST098 char *);
+%rename(asn1_utctime_print) ASN1_UTCTIME_print;
+%threadallow ASN1_UTCTIME_print;
+extern int ASN1_UTCTIME_print(BIO *, ASN1_UTCTIME *);
+
+%rename(asn1_integer_new) ASN1_INTEGER_new;
+extern ASN1_INTEGER *ASN1_INTEGER_new( void );
+%rename(asn1_integer_free) ASN1_INTEGER_free;
+extern void ASN1_INTEGER_free( ASN1_INTEGER *);
+%rename(asn1_integer_cmp) ASN1_INTEGER_cmp;
+extern int ASN1_INTEGER_cmp(ASN1_INTEGER *, ASN1_INTEGER *);
+
+%constant int ASN1_STRFLGS_ESC_2253 = 1;
+%constant int ASN1_STRFLGS_ESC_CTRL = 2;
+%constant int ASN1_STRFLGS_ESC_MSB = 4;
+%constant int ASN1_STRFLGS_ESC_QUOTE = 8;
+%constant int ASN1_STRFLGS_UTF8_CONVERT = 0x10;
+%constant int ASN1_STRFLGS_DUMP_UNKNOWN = 0x100;
+%constant int ASN1_STRFLGS_DUMP_DER = 0x200;
+%constant int ASN1_STRFLGS_RFC2253 = (ASN1_STRFLGS_ESC_2253 | \
+                ASN1_STRFLGS_ESC_CTRL | \
+                ASN1_STRFLGS_ESC_MSB | \
+                ASN1_STRFLGS_UTF8_CONVERT | \
+                ASN1_STRFLGS_DUMP_UNKNOWN | \
+                ASN1_STRFLGS_DUMP_DER);
+
+%inline %{
+/* ASN1_UTCTIME_set_string () is a macro */
+int asn1_utctime_type_check(ASN1_UTCTIME *ASN1_UTCTIME) {
+    return 1;
+}
+
+PyObject *asn1_integer_get(ASN1_INTEGER *asn1) {
+    BIGNUM *bn;
+    PyObject *ret;
+    char *hex;
+
+    bn = ASN1_INTEGER_to_BN(asn1, NULL);
+
+    if (!bn){
+        PyErr_SetString(
+          PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+
+    hex = BN_bn2hex(bn);
+
+    if (!hex){
+        PyErr_SetString(
+          PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+        BN_free(bn);
+        return NULL;
+    }
+
+    BN_free(bn);
+
+    ret = PyLong_FromString(hex, NULL, 16);
+
+    OPENSSL_free(hex);
+
+    return ret;
+}
+
+int asn1_integer_set(ASN1_INTEGER *asn1, PyObject *value) {
+    BIGNUM *bn = NULL;
+    PyObject *fmt, *args, *hex;
+
+    if (PyInt_Check(value))
+        return ASN1_INTEGER_set(asn1, PyInt_AS_LONG(value));
+
+    if (!PyLong_Check(value)){
+        PyErr_SetString(PyExc_TypeError, "expected int or long");
+        return 0;
+    }
+
+    fmt = PyString_FromString("%x");
+
+    if (!fmt)
+        return 0;
+
+    args = PyTuple_New(1);
+
+    if (!args){
+        Py_DECREF(fmt);
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() failed");
+        return 0;
+    }
+
+    Py_INCREF(value);
+    PyTuple_SET_ITEM(args, 0, value);
+    hex = PyString_Format(fmt, args);
+
+    if (!hex){
+        PyErr_SetString(PyExc_RuntimeError, "PyString_Format() failed");
+        Py_DECREF(fmt);
+        Py_DECREF(args);
+        return 0;
+    }
+
+    Py_DECREF(fmt);
+    Py_DECREF(args);
+
+    if (BN_hex2bn(&bn, PyString_AsString(hex)) <= 0){
+        PyErr_SetString(
+          PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+        Py_DECREF(hex);
+        return 0;
+    }
+
+    Py_DECREF(hex);
+
+    if (!BN_to_ASN1_INTEGER(bn, asn1)){
+        PyErr_SetString(
+          PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+        BN_free(bn); 
+        return 0;
+    }
+
+    BN_free(bn);
+
+    return 1;
+}
+
+%}
diff --git a/SWIG/_bio.i b/SWIG/_bio.i
new file mode 100644 (file)
index 0000000..441ba06
--- /dev/null
@@ -0,0 +1,227 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved.
+ *
+ * Portions created by Open Source Applications Foundation (OSAF) are
+ * Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+ * Author: Heikki Toivonen
+*/
+/* $Id: _bio.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/bio.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { BIO_METHOD * };
+
+%rename(bio_s_bio) BIO_s_bio;
+extern BIO_METHOD *BIO_s_bio(void);
+%rename(bio_s_mem) BIO_s_mem;
+extern BIO_METHOD *BIO_s_mem(void);
+%rename(bio_s_socket) BIO_s_socket;
+extern BIO_METHOD *BIO_s_socket(void);
+%rename(bio_f_ssl) BIO_f_ssl;
+extern BIO_METHOD *BIO_f_ssl(void);
+%rename(bio_f_buffer) BIO_f_buffer;
+extern BIO_METHOD *BIO_f_buffer(void);
+%rename(bio_f_cipher) BIO_f_cipher;
+extern BIO_METHOD *BIO_f_cipher(void);
+
+%rename(bio_new) BIO_new;
+extern BIO *BIO_new(BIO_METHOD *);
+%rename(bio_new_socket) BIO_new_socket;
+extern BIO *BIO_new_socket(int, int);
+%rename(bio_new_fd) BIO_new_fd;
+extern BIO *BIO_new_fd(int, int);
+%rename(bio_new_fp) BIO_new_fp;
+extern BIO *BIO_new_fp(FILE *, int);
+%rename(bio_new_file) BIO_new_file;
+extern BIO *BIO_new_file(const char *, const char *);
+%rename(bio_free) BIO_free;
+%threadallow BIO_free;
+extern int BIO_free(BIO *);
+%rename(bio_free_all) BIO_free_all;
+%threadallow BIO_free_all;
+extern void BIO_free_all(BIO *);
+%rename(bio_dup_chain) BIO_dup_chain;
+extern BIO *BIO_dup_chain(BIO *);
+
+%rename(bio_push) BIO_push;
+extern BIO *BIO_push(BIO *, BIO *);
+%rename(bio_pop) BIO_pop;
+extern BIO *BIO_pop(BIO *);
+
+%constant int bio_noclose             = BIO_NOCLOSE;
+%constant int bio_close               = BIO_CLOSE;
+%constant int BIO_FLAGS_READ          = 0x01;
+%constant int BIO_FLAGS_WRITE         = 0x02;
+%constant int BIO_FLAGS_IO_SPECIAL    = 0x04;
+%constant int BIO_FLAGS_RWS = (BIO_FLAGS_READ|BIO_FLAGS_WRITE|BIO_FLAGS_IO_SPECIAL);
+%constant int BIO_FLAGS_SHOULD_RETRY  = 0x08;
+%constant int BIO_FLAGS_MEM_RDONLY    = 0x200;
+
+%inline %{
+static PyObject *_bio_err;
+
+void bio_init(PyObject *bio_err) {
+    Py_INCREF(bio_err);
+    _bio_err = bio_err;
+}
+
+PyObject *bio_read(BIO *bio, int num) {
+    PyObject *blob;
+    void *buf;
+    int r;
+
+    if (!(buf = PyMem_Malloc(num))) {
+        PyErr_SetString(PyExc_MemoryError, "bio_read");
+        return NULL;
+    }
+    Py_BEGIN_ALLOW_THREADS
+    r = BIO_read(bio, buf, num);
+    Py_END_ALLOW_THREADS
+    if (r < 0) {
+        PyMem_Free(buf);
+        if (ERR_peek_error()) {
+            PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+            return NULL;
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    blob = PyString_FromStringAndSize(buf, r);
+    PyMem_Free(buf);
+    return blob;
+}
+
+PyObject *bio_gets(BIO *bio, int num) {
+    PyObject *blob;
+    void *buf;
+    int r;
+
+    if (!(buf = PyMem_Malloc(num))) {
+        PyErr_SetString(PyExc_MemoryError, "bio_gets");
+        return NULL;
+    }
+    Py_BEGIN_ALLOW_THREADS
+    r = BIO_gets(bio, buf, num);
+    Py_END_ALLOW_THREADS
+    if (r < 0) {
+        PyMem_Free(buf);
+        if (ERR_peek_error()) {
+            PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+            return NULL;
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    blob = PyString_FromStringAndSize(buf, r);
+    PyMem_Free(buf);
+    return blob;
+}
+
+int bio_write(BIO *bio, PyObject *from) {
+    const void *fbuf;
+    int flen, ret;
+
+    if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+        return -1;
+
+    Py_BEGIN_ALLOW_THREADS
+    ret = BIO_write(bio, fbuf, flen);
+    Py_END_ALLOW_THREADS
+    if (ret < 0) {
+        if (ERR_peek_error()) {
+            PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+        }
+    }
+    return ret;
+}
+
+/* XXX Casting size_t to int. */
+int bio_ctrl_pending(BIO *bio) {
+    return (int)BIO_ctrl_pending(bio);
+}
+
+int bio_ctrl_wpending(BIO *bio) {
+    return (int)BIO_ctrl_wpending(bio);
+}
+
+int bio_ctrl_get_write_guarantee(BIO *a) {
+    return BIO_ctrl_get_write_guarantee(a);
+}
+
+int bio_reset(BIO *bio) {
+    return (int)BIO_reset(bio);
+}
+%}
+
+%threadallow bio_flush;
+%inline %{
+int bio_flush(BIO *bio) {
+    return (int)BIO_flush(bio);
+}
+
+int bio_seek(BIO *bio, int offset) {
+    return (int)BIO_seek(bio, offset);
+}
+
+void bio_set_flags(BIO *bio, int flags) {
+    BIO_set_flags(bio, flags);
+}
+
+int bio_get_flags(BIO *bio) {
+    return BIO_get_flags(bio);
+}
+
+PyObject *bio_set_cipher(BIO *b, EVP_CIPHER *c, PyObject *key, PyObject *iv, int op) {
+    const void *kbuf, *ibuf;
+    Py_ssize_t klen, ilen;
+
+    if ((PyObject_AsReadBuffer(key, &kbuf, &klen) == -1)
+        || (PyObject_AsReadBuffer(iv, &ibuf, &ilen) == -1))
+        return NULL;
+
+    BIO_set_cipher(b, (const EVP_CIPHER *)c, 
+        (unsigned char *)kbuf, (unsigned char *)ibuf, op);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+int bio_set_mem_eof_return(BIO *b, int v) {
+    return (int)BIO_set_mem_eof_return(b, v);
+}
+
+int bio_get_fd(BIO *bio) {
+    return BIO_get_fd(bio, NULL);
+}
+%}
+
+%threadallow bio_do_handshake;
+%inline %{
+int bio_do_handshake(BIO *bio) {
+    return BIO_do_handshake(bio);
+}
+
+/* macro */
+int bio_make_bio_pair(BIO* b1, BIO* b2) {
+    return BIO_make_bio_pair(b1, b2);
+}
+
+int bio_set_write_buf_size(BIO* b, size_t size) {
+    return BIO_set_write_buf_size(b, size);
+}
+
+int bio_should_retry(BIO* a) {
+    return BIO_should_retry(a);
+}
+
+int bio_should_read(BIO* a) {
+    return BIO_should_read(a);
+}
+
+int bio_should_write(BIO* a) {
+    return BIO_should_write(a);
+}
+%}
+
diff --git a/SWIG/_bn.i b/SWIG/_bn.i
new file mode 100755 (executable)
index 0000000..a3b73c4
--- /dev/null
@@ -0,0 +1,114 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 2005-2006 Open Source Applications Foundation. All rights reserved. */
+
+/* We are converting between the Python arbitrarily long integer and
+ * the BIGNUM arbitrarily long integer by converting to and from
+ * a string representation of the number (in hexadecimal).
+ * Direct manipulation would be a possibility, but would require
+ * tighter integration with the Python and OpenSSL internals.
+ */
+
+
+%{
+#include <openssl/bn.h>
+%}
+
+
+%inline %{
+PyObject *bn_rand(int bits, int top, int bottom)
+{
+    BIGNUM rnd;
+    PyObject *ret;
+    char *randhex;
+    
+    BN_init(&rnd);
+    if (!BN_rand(&rnd, bits, top, bottom)) {
+        /*Custom errors?*/
+        PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+        BN_free(&rnd);
+        return NULL;
+    }
+    
+    randhex = BN_bn2hex(&rnd);
+    if (!randhex) {
+        /*Custom errors?*/
+        PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+        BN_free(&rnd);
+        return NULL;
+    }
+    BN_free(&rnd);
+        
+    ret = PyLong_FromString(randhex, NULL, 16);
+    OPENSSL_free(randhex);
+    return ret;
+}
+
+
+PyObject *bn_rand_range(PyObject *range)
+{
+    BIGNUM rnd;
+    BIGNUM *rng = NULL;
+    PyObject *ret, *tuple;
+    PyObject *format, *rangePyString;
+    char *randhex, *rangehex;
+    
+    /* Wow, it's a lot of work to convert into a hex string in C! */
+    format = PyString_FromString("%x");
+    if (!format) {
+        return NULL;
+    }
+    tuple = PyTuple_New(1);
+    if (!tuple) {
+        Py_DECREF(format);
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+        return NULL;
+    }
+    Py_INCREF(range);
+    PyTuple_SET_ITEM(tuple, 0, range);
+    rangePyString = PyString_Format(format, tuple);
+    if (!rangePyString) {
+        PyErr_SetString(PyExc_Exception, "PyString_Format failed");    
+        Py_DECREF(format);
+        Py_DECREF(tuple);
+        return NULL;    
+    }
+    Py_DECREF(format);
+    Py_DECREF(tuple);
+    rangehex = PyString_AsString(rangePyString);
+    
+    if (!BN_hex2bn(&rng, rangehex)) {
+        /*Custom errors?*/
+        PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+        Py_DECREF(rangePyString);
+        return NULL;             
+    }
+
+    Py_DECREF(rangePyString);
+                 
+    BN_init(&rnd);
+
+     if (!BN_rand_range(&rnd, rng)) {
+        /*Custom errors?*/
+        PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+        BN_free(&rnd);
+        BN_free(rng);
+        return NULL;         
+     }
+
+    BN_free(rng);
+
+    randhex = BN_bn2hex(&rnd);
+    if (!randhex) {
+        /*Custom errors?*/
+        PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+        BN_free(&rnd);
+        return NULL;
+    }
+    BN_free(&rnd);
+        
+    ret = PyLong_FromString(randhex, NULL, 16);
+    OPENSSL_free(randhex);
+    return ret;
+}
+
+%}
diff --git a/SWIG/_dh.i b/SWIG/_dh.i
new file mode 100644 (file)
index 0000000..675b39f
--- /dev/null
@@ -0,0 +1,186 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _dh.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/dh.h>
+%}
+
+%apply Pointer NONNULL { DH * };
+
+%rename(dh_new) DH_new;
+extern DH *DH_new(void);
+%rename(dh_free) DH_free;
+extern void DH_free(DH *);
+%rename(dh_size) DH_size;
+extern int DH_size(const DH *);
+%rename(dh_generate_key) DH_generate_key;
+extern int DH_generate_key(DH *);
+%rename(dhparams_print) DHparams_print;
+%threadallow DHparams_print;
+extern int DHparams_print(BIO *, const DH *);
+
+%constant int dh_check_ok             = 0;
+%constant int dh_check_p_not_prime    = DH_CHECK_P_NOT_PRIME;
+%constant int dh_check_p_not_strong   = DH_CHECK_P_NOT_STRONG_PRIME;
+%constant int dh_check_g_failed       = DH_UNABLE_TO_CHECK_GENERATOR;
+%constant int dh_check_bad_g          = DH_NOT_SUITABLE_GENERATOR;
+
+%constant DH_GENERATOR_2          = 2;
+%constant DH_GENERATOR_5          = 5;
+
+%inline %{
+static PyObject *_dh_err;
+
+void dh_init(PyObject *dh_err) {
+    Py_INCREF(dh_err);
+    _dh_err = dh_err;
+}
+
+int dh_type_check(DH *dh) {
+    /* Our getting here means we passed Swig's type checking,
+    XXX Still need to check the pointer for sanity? */
+    return 1;
+}
+%}
+
+%threadallow dh_read_parameters;
+%inline %{
+DH *dh_read_parameters(BIO *bio) {
+    return PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+}
+
+void gendh_callback(int p, int n, void *arg) {
+    PyObject *argv, *ret, *cbfunc;
+    cbfunc = (PyObject *)arg;
+    argv = Py_BuildValue("(ii)", p, n);
+    ret = PyEval_CallObject(cbfunc, argv);
+    PyErr_Clear();
+    Py_DECREF(argv);
+    Py_XDECREF(ret);
+}
+
+DH *dh_generate_parameters(int plen, int g, PyObject *pyfunc) {
+    DH *dh;
+
+    Py_INCREF(pyfunc);
+    dh = DH_generate_parameters(plen, g, gendh_callback, (void *)pyfunc);
+    Py_DECREF(pyfunc);
+    if (!dh) 
+        PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+    return dh;
+}
+
+/* Note return value shenanigan. */
+int dh_check(DH *dh) {
+    int err;
+
+    return (DH_check(dh, &err)) ? 0 : err;
+}
+
+PyObject *dh_compute_key(DH *dh, PyObject *pubkey) {
+    const void *pkbuf;
+    int pklen, klen;
+    void *key;
+    BIGNUM *pk;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(pubkey, &pkbuf, &pklen) == -1)
+        return NULL;
+
+    if (!(pk = BN_mpi2bn((unsigned char *)pkbuf, pklen, NULL))) {
+        PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (!(key = PyMem_Malloc(DH_size(dh)))) {
+        BN_free(pk);
+        PyErr_SetString(PyExc_MemoryError, "dh_compute_key");
+        return NULL;
+    }
+    if ((klen = DH_compute_key((unsigned char *)key, pk, dh)) == -1) {
+        BN_free(pk);
+        PyMem_Free(key);
+        PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)key, klen);
+    BN_free(pk);
+    PyMem_Free(key);
+    return ret;
+}
+        
+PyObject *dh_get_p(DH *dh) {
+    if (!dh->p) {
+        PyErr_SetString(_dh_err, "'p' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dh->p);
+}
+
+PyObject *dh_get_g(DH *dh) {
+    if (!dh->g) {
+        PyErr_SetString(_dh_err, "'g' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dh->g);
+}
+
+PyObject *dh_get_pub(DH *dh) {
+    if (!dh->pub_key) {
+        PyErr_SetString(_dh_err, "'pub' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dh->pub_key);
+}
+
+PyObject *dh_get_priv(DH *dh) {
+    if (!dh->priv_key) {
+        PyErr_SetString(_dh_err, "'priv' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dh->priv_key);
+}
+
+PyObject *dh_set_p(DH *dh, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (dh->p)
+        BN_free(dh->p);
+    dh->p = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *dh_set_g(DH *dh, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (dh->g)
+        BN_free(dh->g);
+    dh->g = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+%}
+
diff --git a/SWIG/_dsa.i b/SWIG/_dsa.i
new file mode 100644 (file)
index 0000000..addf33a
--- /dev/null
@@ -0,0 +1,349 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved. */
+/* $Id: _dsa.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/dsa.h>
+
+PyObject *dsa_sig_get_r(DSA_SIG *dsa_sig) {
+    return bn_to_mpi(dsa_sig->r);
+}
+
+PyObject *dsa_sig_get_s(DSA_SIG *dsa_sig) {
+    return bn_to_mpi(dsa_sig->s);
+}
+%}
+
+%apply Pointer NONNULL { DSA * };
+
+%rename(dsa_new) DSA_new;
+extern DSA *DSA_new(void);
+%rename(dsa_free) DSA_free;
+extern void DSA_free(DSA *);
+%rename(dsa_size) DSA_size;
+extern int DSA_size(const DSA *); /* assert(dsa->q); */
+%rename(dsa_gen_key) DSA_generate_key;
+extern int DSA_generate_key(DSA *);
+
+%inline %{
+static PyObject *_dsa_err;
+
+void dsa_init(PyObject *dsa_err) {
+    Py_INCREF(dsa_err);
+    _dsa_err = dsa_err;
+}
+
+void genparam_callback(int p, int n, void *arg) {
+    PyObject *argv, *ret, *cbfunc;
+
+    cbfunc = (PyObject *)arg; 
+    argv = Py_BuildValue("(ii)", p, n);
+    ret = PyEval_CallObject(cbfunc, argv);
+    PyErr_Clear();
+    Py_DECREF(argv);
+    Py_XDECREF(ret);
+}
+
+DSA *dsa_generate_parameters(int bits, PyObject *pyfunc) {
+    DSA *dsa;
+
+    Py_INCREF(pyfunc);
+    dsa = DSA_generate_parameters(bits, NULL, 0, NULL, NULL, genparam_callback, (void *)pyfunc);
+    Py_DECREF(pyfunc);
+    if (!dsa)
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+    return dsa;
+}
+
+PyObject *dsa_get_p(DSA *dsa) {
+    if (!dsa->p) {
+        PyErr_SetString(_dsa_err, "'p' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dsa->p);
+}
+
+PyObject *dsa_get_q(DSA *dsa) {
+    if (!dsa->q) {
+        PyErr_SetString(_dsa_err, "'q' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dsa->q);
+}
+
+PyObject *dsa_get_g(DSA *dsa) {
+    if (!dsa->g) {
+        PyErr_SetString(_dsa_err, "'g' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dsa->g);
+}
+
+PyObject *dsa_get_pub(DSA *dsa) {
+    if (!dsa->pub_key) {
+        PyErr_SetString(_dsa_err, "'pub' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dsa->pub_key);
+}
+
+PyObject *dsa_get_priv(DSA *dsa) {
+    if (!dsa->priv_key) {
+        PyErr_SetString(_dsa_err, "'priv' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(dsa->priv_key);
+}
+
+PyObject *dsa_set_p(DSA *dsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (dsa->p)
+        BN_free(dsa->p);
+    dsa->p = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *dsa_set_q(DSA *dsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (dsa->q)
+        BN_free(dsa->q);
+    dsa->q = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *dsa_set_g(DSA *dsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (dsa->g)
+        BN_free(dsa->g);
+    dsa->g = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+%}
+
+%inline %{
+DSA *dsa_read_params(BIO *f, PyObject *pyfunc) {
+    DSA *ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_read_bio_DSAparams(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%threadallow dsa_write_params_bio;
+%inline %{
+int dsa_write_params_bio(DSA* dsa, BIO* f) {
+    return PEM_write_bio_DSAparams(f, dsa);
+}
+%}
+
+%inline %{
+int dsa_write_key_bio(DSA* dsa, BIO* f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_DSAPrivateKey(f, dsa, cipher, NULL, 0,
+                                        passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+int dsa_write_key_bio_no_cipher(DSA* dsa, BIO* f, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_DSAPrivateKey(f, dsa, NULL, NULL, 0,
+                                        passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%threadallow dsa_write_pub_key_bio;
+%inline %{
+int dsa_write_pub_key_bio(DSA* dsa, BIO* f) {
+    return PEM_write_bio_DSA_PUBKEY(f, dsa);
+}
+%}
+
+%inline %{
+DSA *dsa_read_key(BIO *f, PyObject *pyfunc) {
+    DSA *ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_read_bio_DSAPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+DSA *dsa_read_pub_key(BIO *f, PyObject *pyfunc) {
+    DSA *ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_read_bio_DSA_PUBKEY(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+
+PyObject *dsa_sign(DSA *dsa, PyObject *value) {
+    const void *vbuf;
+    int vlen;
+    PyObject *tuple;
+    DSA_SIG *sig; 
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(sig = DSA_do_sign(vbuf, vlen, dsa))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (!(tuple = PyTuple_New(2))) {
+        DSA_SIG_free(sig);
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tuple, 0, dsa_sig_get_r(sig));
+    PyTuple_SET_ITEM(tuple, 1, dsa_sig_get_s(sig));
+    DSA_SIG_free(sig);
+    return tuple;
+}
+
+int dsa_verify(DSA *dsa, PyObject *value, PyObject *r, PyObject *s) {
+    const void *vbuf, *rbuf, *sbuf;
+    int vlen, rlen, slen;
+    DSA_SIG *sig;
+    int ret;
+
+    if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(r, &rbuf, &rlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(s, &sbuf, &slen) == -1))
+        return -1;
+
+    if (!(sig = DSA_SIG_new())) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    if (!(sig->r = BN_mpi2bn((unsigned char *)rbuf, rlen, NULL))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        DSA_SIG_free(sig);
+        return -1;
+    }
+    if (!(sig->s = BN_mpi2bn((unsigned char *)sbuf, slen, NULL))) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        DSA_SIG_free(sig);
+        return -1;
+    }
+    ret = DSA_do_verify(vbuf, vlen, sig, dsa);
+    DSA_SIG_free(sig);
+    if (ret == -1)
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+    return ret;
+}
+
+PyObject *dsa_sign_asn1(DSA *dsa, PyObject *value) {
+    const void *vbuf;
+    int vlen;
+    void *sigbuf;
+    unsigned int siglen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(sigbuf = PyMem_Malloc(DSA_size(dsa)))) {
+        PyErr_SetString(PyExc_MemoryError, "dsa_sign_asn1");
+        return NULL;
+    }
+    if (!DSA_sign(0, vbuf, vlen, (unsigned char *)sigbuf, &siglen, dsa)) {
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+        PyMem_Free(sigbuf);
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize(sigbuf, siglen);
+    PyMem_Free(sigbuf);
+    return ret;
+}
+
+int dsa_verify_asn1(DSA *dsa, PyObject *value, PyObject *sig) {
+    const void *vbuf; 
+    void *sbuf;
+    int vlen, slen, ret;
+
+    if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(sig, (const void **)&sbuf, &slen)
+        == -1))
+        return -1;
+
+    if ((ret = DSA_verify(0, vbuf, vlen, sbuf, slen, dsa)) == -1)
+        PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+    return ret;
+}
+
+int dsa_check_key(DSA *dsa) {
+    return (dsa->pub_key) && (dsa->priv_key);
+}
+
+int dsa_check_pub_key(DSA *dsa) {
+    return dsa->pub_key ? 1 : 0;
+}
+
+int dsa_keylen(DSA *dsa) {
+    return BN_num_bits(dsa->p);
+}
+
+int dsa_type_check(DSA *dsa) {
+    return 1;
+}
+%}
+
diff --git a/SWIG/_ec.i b/SWIG/_ec.i
new file mode 100644 (file)
index 0000000..f0e52bd
--- /dev/null
@@ -0,0 +1,419 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved. 
+ Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+
+ Most code originally from _dsa.i, _rsa.i and _dh.i and adjusted for EC use.
+*/
+
+%include <openssl/opensslconf.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x0090800fL || defined(OPENSSL_NO_EC)
+#undef OPENSSL_NO_EC
+%constant OPENSSL_NO_EC = 1;
+#else
+%constant OPENSSL_NO_EC = 0;
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/ecdsa.h>
+#include <openssl/ecdh.h>
+%}
+
+%apply Pointer NONNULL { EC_KEY * };
+
+%rename(ec_key_new) EC_KEY_new;
+extern EC_KEY *EC_KEY_new(void);
+%rename(ec_key_free) EC_KEY_free;
+extern void EC_KEY_free(EC_KEY *);
+%rename(ec_key_size) ECDSA_size;
+extern int ECDSA_size(const EC_KEY *); 
+%rename(ec_key_gen_key) EC_KEY_generate_key;
+extern int EC_KEY_generate_key(EC_KEY *);
+%rename(ec_key_check_key) EC_KEY_check_key;
+extern int EC_KEY_check_key(const EC_KEY *);
+
+/* Curve identifier constants from OpenSSL */
+%constant int NID_secp112r1 = NID_secp112r1;
+%constant int NID_secp112r2 = NID_secp112r2;
+%constant int NID_secp128r1 = NID_secp128r1;
+%constant int NID_secp128r2 = NID_secp128r2;
+%constant int NID_secp160k1 = NID_secp160k1;
+%constant int NID_secp160r1 = NID_secp160r1;
+%constant int NID_secp160r2 = NID_secp160r2;
+%constant int NID_secp192k1 = NID_secp192k1;
+%constant int NID_secp224k1 = NID_secp224k1;
+%constant int NID_secp224r1 = NID_secp224r1;
+%constant int NID_secp256k1 = NID_secp256k1;
+%constant int NID_secp384r1 = NID_secp384r1;
+%constant int NID_secp521r1 = NID_secp521r1;
+%constant int NID_sect113r1 = NID_sect113r1;
+%constant int NID_sect113r2 = NID_sect113r2;
+%constant int NID_sect131r1 = NID_sect131r1;
+%constant int NID_sect131r2 = NID_sect131r2;
+%constant int NID_sect163k1 = NID_sect163k1;
+%constant int NID_sect163r1 = NID_sect163r1;
+%constant int NID_sect163r2 = NID_sect163r2;
+%constant int NID_sect193r1 = NID_sect193r1;
+%constant int NID_sect193r2 = NID_sect193r2;
+%constant int NID_sect233k1 = NID_sect233k1;
+%constant int NID_sect233r1 = NID_sect233r1;
+%constant int NID_sect239k1 = NID_sect239k1;
+%constant int NID_sect283k1 = NID_sect283k1;
+%constant int NID_sect283r1 = NID_sect283r1;
+%constant int NID_sect409k1 = NID_sect409k1;
+%constant int NID_sect409r1 = NID_sect409r1;
+%constant int NID_sect571k1 = NID_sect571k1;
+%constant int NID_sect571r1 = NID_sect571r1;
+
+%constant int NID_X9_62_prime192v1 = NID_X9_62_prime192v1;
+%constant int NID_X9_62_prime192v2 = NID_X9_62_prime192v2;
+%constant int NID_X9_62_prime192v3 = NID_X9_62_prime192v3;
+%constant int NID_X9_62_prime239v1 = NID_X9_62_prime239v1;
+%constant int NID_X9_62_prime239v2 = NID_X9_62_prime239v2;
+%constant int NID_X9_62_prime239v3 = NID_X9_62_prime239v3;
+%constant int NID_X9_62_prime256v1 = NID_X9_62_prime256v1;
+%constant int NID_X9_62_c2pnb163v1 = NID_X9_62_c2pnb163v1;
+%constant int NID_X9_62_c2pnb163v2 = NID_X9_62_c2pnb163v2;
+%constant int NID_X9_62_c2pnb163v3 = NID_X9_62_c2pnb163v3;
+%constant int NID_X9_62_c2pnb176v1 = NID_X9_62_c2pnb176v1;
+%constant int NID_X9_62_c2tnb191v1 = NID_X9_62_c2tnb191v1;
+%constant int NID_X9_62_c2tnb191v2 = NID_X9_62_c2tnb191v2;
+%constant int NID_X9_62_c2tnb191v3 = NID_X9_62_c2tnb191v3;
+%constant int NID_X9_62_c2pnb208w1 = NID_X9_62_c2pnb208w1;
+%constant int NID_X9_62_c2tnb239v1 = NID_X9_62_c2tnb239v1;
+%constant int NID_X9_62_c2tnb239v2 = NID_X9_62_c2tnb239v2;
+%constant int NID_X9_62_c2tnb239v3 = NID_X9_62_c2tnb239v3;
+%constant int NID_X9_62_c2pnb272w1 = NID_X9_62_c2pnb272w1;
+%constant int NID_X9_62_c2pnb304w1 = NID_X9_62_c2pnb304w1;
+%constant int NID_X9_62_c2tnb359v1 = NID_X9_62_c2tnb359v1;
+%constant int NID_X9_62_c2pnb368w1 = NID_X9_62_c2pnb368w1;
+%constant int NID_X9_62_c2tnb431r1 = NID_X9_62_c2tnb431r1;
+
+%constant int NID_wap_wsg_idm_ecid_wtls1  = NID_wap_wsg_idm_ecid_wtls1;
+%constant int NID_wap_wsg_idm_ecid_wtls3  = NID_wap_wsg_idm_ecid_wtls3;
+%constant int NID_wap_wsg_idm_ecid_wtls4  = NID_wap_wsg_idm_ecid_wtls4;
+%constant int NID_wap_wsg_idm_ecid_wtls5  = NID_wap_wsg_idm_ecid_wtls5;
+%constant int NID_wap_wsg_idm_ecid_wtls6  = NID_wap_wsg_idm_ecid_wtls6;
+%constant int NID_wap_wsg_idm_ecid_wtls7  = NID_wap_wsg_idm_ecid_wtls7;
+%constant int NID_wap_wsg_idm_ecid_wtls8  = NID_wap_wsg_idm_ecid_wtls8;
+%constant int NID_wap_wsg_idm_ecid_wtls9  = NID_wap_wsg_idm_ecid_wtls9;
+%constant int NID_wap_wsg_idm_ecid_wtls10 = NID_wap_wsg_idm_ecid_wtls10;
+%constant int NID_wap_wsg_idm_ecid_wtls11 = NID_wap_wsg_idm_ecid_wtls11;
+%constant int NID_wap_wsg_idm_ecid_wtls12 = NID_wap_wsg_idm_ecid_wtls12;
+
+%constant int NID_ipsec3 = NID_ipsec3;
+%constant int NID_ipsec4 = NID_ipsec4;
+
+
+%inline %{
+static PyObject *_ec_err;
+
+void ec_init(PyObject *ec_err) {
+    Py_INCREF(ec_err);
+    _ec_err = ec_err;
+}
+
+EC_KEY* ec_key_new_by_curve_name(int nid)
+{
+    EC_KEY   *key;
+    EC_GROUP *group;
+    int ret  =0;
+    point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
+    int      asn1_flag = OPENSSL_EC_NAMED_CURVE;
+
+    /* If I simply do "return EC_KEY_new_by_curve_name(nid);"
+     * I get large public keys (222 vs 84 bytes for sect233k1 curve).
+     * I don't know why that is, but 'openssl ecparam -genkey ...' sets
+     * the ASN.1 flag and the point conversion form, and gets the
+     * small pub keys. So let's do that too.
+     */
+    key = EC_KEY_new();
+    if (!key) {
+        PyErr_SetString(PyExc_MemoryError, "ec_key_new_by_curve_name");
+        return NULL;
+    }
+    group = EC_GROUP_new_by_curve_name(nid);
+    if (!group) {
+        EC_KEY_free(key);
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    EC_GROUP_set_asn1_flag(group, asn1_flag);
+    EC_GROUP_set_point_conversion_form(group, form);
+    ret = EC_KEY_set_group(key, group);
+    EC_GROUP_free(group);
+    if (ret == 0)
+    {
+        /* EC_KEY_set_group only returns 0 or 1, and does not set error. */
+        PyErr_SetString(_ec_err, "cannot set key's group");
+        EC_KEY_free(key);
+        return NULL;
+    }
+
+    return key;
+}
+
+PyObject *ec_key_get_public_der(EC_KEY *key) {
+
+    unsigned char *src=NULL;
+    void *dst=NULL;
+    int src_len=0;
+    Py_ssize_t dst_len=0;
+    PyObject *pyo=NULL;
+    int ret=0;
+    
+    /* Convert to binary */
+    src_len = i2d_EC_PUBKEY( key, &src );
+    if (src_len < 0)
+    {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    /* Create a PyBuffer containing a copy of the binary,
+     * to simplify memory deallocation
+     */
+    pyo = PyBuffer_New( src_len );
+    ret = PyObject_AsWriteBuffer( pyo, &dst, &dst_len );
+    assert( src_len == dst_len );
+    if (ret < 0)
+    {
+        Py_DECREF(pyo);
+        OPENSSL_free(src);    
+        PyErr_SetString(_ec_err, "cannot get write buffer");
+        return NULL;
+    }
+    memcpy( dst, src, src_len );
+    OPENSSL_free(src);
+
+    return pyo;
+}
+%}
+
+%threadallow ec_key_read_pubkey;
+%inline %{
+EC_KEY *ec_key_read_pubkey(BIO *f) {
+    return PEM_read_bio_EC_PUBKEY(f, NULL, NULL, NULL);   
+}
+%}
+
+%threadallow ec_key_write_pubkey;
+%inline %{
+int ec_key_write_pubkey(EC_KEY *key, BIO *f) {
+    return PEM_write_bio_EC_PUBKEY(f, key );
+}
+%}
+
+%inline %{
+EC_KEY *ec_key_read_bio(BIO *f, PyObject *pyfunc) {
+    EC_KEY *ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_read_bio_ECPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+int ec_key_write_bio(EC_KEY *key, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_ECPrivateKey(f, key, cipher, NULL, 0,
+        passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+int ec_key_write_bio_no_cipher(EC_KEY *key, BIO *f, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_ECPrivateKey(f, key, NULL, NULL, 0, 
+                      passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+
+
+PyObject *ecdsa_sig_get_r(ECDSA_SIG *ecdsa_sig) {
+    return bn_to_mpi(ecdsa_sig->r);
+}
+
+PyObject *ecdsa_sig_get_s(ECDSA_SIG *ecdsa_sig) {
+    return bn_to_mpi(ecdsa_sig->s);
+}
+
+PyObject *ecdsa_sign(EC_KEY *key, PyObject *value) {
+    const void *vbuf;
+    int vlen;
+    PyObject *tuple;
+    ECDSA_SIG *sig; 
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(sig = ECDSA_do_sign(vbuf, vlen, key))) {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (!(tuple = PyTuple_New(2))) {
+        ECDSA_SIG_free(sig);
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tuple, 0, ecdsa_sig_get_r(sig));
+    PyTuple_SET_ITEM(tuple, 1, ecdsa_sig_get_s(sig));
+    ECDSA_SIG_free(sig);
+    return tuple;
+}
+
+int ecdsa_verify(EC_KEY *key, PyObject *value, PyObject *r, PyObject *s) {
+    const void *vbuf, *rbuf, *sbuf;
+    int vlen, rlen, slen;
+    ECDSA_SIG *sig;
+    int ret;
+
+    if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(r, &rbuf, &rlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(s, &sbuf, &slen) == -1))
+        return -1;
+
+    if (!(sig = ECDSA_SIG_new())) {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    if (!BN_mpi2bn((unsigned char *)rbuf, rlen, sig->r)) {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        ECDSA_SIG_free(sig);
+        return -1;
+    }
+    if (!BN_mpi2bn((unsigned char *)sbuf, slen, sig->s)) {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        ECDSA_SIG_free(sig);
+        return -1;
+    }
+    ret = ECDSA_do_verify(vbuf, vlen, sig, key);
+    ECDSA_SIG_free(sig);
+    if (ret == -1)
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+    return ret;
+}
+
+
+PyObject *ecdsa_sign_asn1(EC_KEY *key, PyObject *value) {
+    const void *vbuf;
+    int vlen;
+    void *sigbuf;
+    unsigned int siglen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(sigbuf = PyMem_Malloc(ECDSA_size(key)))) {
+        PyErr_SetString(PyExc_MemoryError, "ecdsa_sign_asn1");
+        return NULL;
+    }
+    if (!ECDSA_sign(0, vbuf, vlen, (unsigned char *)sigbuf, &siglen, key)) {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        PyMem_Free(sigbuf);
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize(sigbuf, siglen);
+    PyMem_Free(sigbuf);
+    return ret;
+}
+
+
+int ecdsa_verify_asn1(EC_KEY *key, PyObject *value, PyObject *sig) {
+    const void *vbuf; 
+    void *sbuf;
+    int vlen, slen, ret;
+
+    if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        || (m2_PyObject_AsReadBufferInt(sig, (const void **)&sbuf, &slen)
+        == -1))
+        return -1;
+
+    if ((ret = ECDSA_verify(0, vbuf, vlen, sbuf, slen, key)) == -1)
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+    return ret;
+}
+
+PyObject *ecdh_compute_key(EC_KEY *keypairA, EC_KEY *pubkeyB) {
+    int sharedkeylen;
+    void *sharedkey;
+    const EC_POINT *pkpointB;
+    PyObject *ret;
+    const EC_GROUP* groupA;
+
+    if ((pkpointB = EC_KEY_get0_public_key(pubkeyB)) == NULL)
+    {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    
+    groupA = EC_KEY_get0_group(keypairA);
+    sharedkeylen = (EC_GROUP_get_degree(groupA) + 7)/8;
+
+    if (!(sharedkey = PyMem_Malloc(sharedkeylen))) {
+        PyErr_SetString(PyExc_MemoryError, "ecdh_compute_key");
+        return NULL;
+    }
+    if ((sharedkeylen = ECDH_compute_key((unsigned char *)sharedkey, sharedkeylen, pkpointB, keypairA, NULL)) == -1) {
+        PyMem_Free(sharedkey);
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+
+    ret = PyString_FromStringAndSize((const char *)sharedkey, sharedkeylen);
+    PyMem_Free(sharedkey);
+    
+    return ret;
+}
+
+
+EC_KEY* ec_key_from_pubkey_der(PyObject *pubkey) {
+    const void *keypairbuf;
+    Py_ssize_t keypairbuflen;
+    const unsigned char *tempBuf;
+    EC_KEY *keypair;
+
+    if (PyObject_AsReadBuffer(pubkey, &keypairbuf, &keypairbuflen) == -1)
+    {
+        return NULL;
+    }
+
+    tempBuf = (const unsigned char *)keypairbuf;
+    if ((keypair = d2i_EC_PUBKEY( NULL, &tempBuf, keypairbuflen)) == 0)
+    {
+        PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    return keypair;
+}
+
+
+// According to [SEC2] the degree of the group is defined as EC key length
+int ec_key_keylen(EC_KEY *key) {
+    const EC_GROUP *group = EC_KEY_get0_group(key);
+    return EC_GROUP_get_degree(group);
+}
+
+int ec_key_type_check(EC_KEY *key) {
+    return 1;
+}
+%}
+#endif // if OpenSSL version with EC support
+
diff --git a/SWIG/_engine.i b/SWIG/_engine.i
new file mode 100644 (file)
index 0000000..b55720f
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: syntax=c sts=4 sw=4
+ *
+ * ENGINE functions from engine(3SSL).
+ * 
+ * Pavel Shramov
+ * IMEC MSU
+ */
+%{
+#include <openssl/engine.h>
+#include <openssl/ui.h>
+#include <stdio.h>
+%}
+
+%apply Pointer NONNULL { ENGINE * };
+%apply Pointer NONNULL { const ENGINE * };
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Functions to load different engines
+ */
+%rename(engine_load_builtin_engines) ENGINE_load_builtin_engines;
+extern void ENGINE_load_builtin_engines(void);
+
+%rename(engine_load_dynamic) ENGINE_load_dynamic;
+extern void ENGINE_load_dynamic(void);
+
+%rename(engine_load_openssl) ENGINE_load_openssl;
+extern void ENGINE_load_openssl(void);
+
+%rename(engine_cleanup) ENGINE_cleanup;
+extern void ENGINE_cleanup(void);
+
+/*
+ * Engine allocation functions
+ */
+%rename(engine_new) ENGINE_new;
+extern ENGINE * ENGINE_new();
+
+%rename(engine_by_id) ENGINE_by_id;
+extern ENGINE * ENGINE_by_id(const char *);
+
+%rename(engine_free) ENGINE_free;
+extern int ENGINE_free(ENGINE *);
+
+%rename(engine_init) ENGINE_init;
+extern int ENGINE_init(ENGINE *);
+
+%rename(engine_finish) ENGINE_finish;
+extern int ENGINE_finish(ENGINE *);
+
+/*
+ * Engine id/name functions
+ */
+%rename(engine_get_id) ENGINE_get_id;
+extern const char * ENGINE_get_id(const ENGINE *);
+
+%rename(engine_get_name) ENGINE_get_name;
+extern const char * ENGINE_get_name(const ENGINE *);
+
+/*
+ * Engine control functions
+ * Control argument may be NULL (e.g for LOAD command)
+ */
+%clear const char *;
+%rename(engine_ctrl_cmd_string) ENGINE_ctrl_cmd_string;
+extern int ENGINE_ctrl_cmd_string(ENGINE *e, const char *NONNULL, 
+                const char *arg, int cmd_optional);
+
+%apply Pointer NONNULL { const char * };
+
+/*
+ * UI methods. 
+ * XXX: UI_OpenSSL method is static and UI_destroy_method is not needed.
+ */
+%rename(ui_openssl) UI_OpenSSL;
+extern UI_METHOD * UI_OpenSSL();
+
+/*
+%rename(ui_destroy_method) UI_destroy_method;
+extern void UI_destroy_method(UI_METHOD *ui_method);
+ */
+
+%clear const char *;
+%inline %{
+
+/*
+ * Code from engine-pkcs11 1.4.0 in engine-pkcs11.c
+ *
+
+99  static char *get_pin(UI_METHOD * ui_method, void *callback_data, char *sc_pin,
+100                      int maxlen)
+101 {
+102         UI *ui;
+103         struct {
+104                 const void *password;
+105                 const char *prompt_info;
+106         } *mycb = callback_data;
+107 
+108         if (mycb->password) {
+109                 sc_pin = set_pin(mycb->password);
+110                 return sc_pin;
+111         }
+ *
+ * So callback_data need to be always provided and have fixed type.
+ * UI method still may be NULL.
+ *
+ * Following functions allocate and free callback data structure with 
+ * optional password set.
+ */
+
+typedef struct {
+    char * password;
+    char * prompt;
+} _cbd_t;
+
+void * engine_pkcs11_data_new(const char *pin) {
+    _cbd_t * cb = (_cbd_t *) PyMem_Malloc(sizeof(_cbd_t));
+    if (!cb) {
+        PyErr_SetString(PyExc_MemoryError, "engine_pkcs11_data_new");
+        return NULL;
+    }
+    cb->password = NULL;
+    if (pin) {
+        size_t size = strlen(pin);
+        cb->password = (char *) PyMem_Malloc(size + 1);
+        if (!cb->password) {
+            PyErr_SetString(PyExc_MemoryError, "engine_pkcs11_data_new");
+            PyMem_Free(cb);
+            return NULL;
+        }
+        memcpy(cb->password, pin, size + 1);
+    }
+    cb->prompt = NULL;
+    return cb;
+}
+
+void engine_pkcs11_data_free(void * vcb) {
+    _cbd_t * cb = (_cbd_t *) vcb;
+    if (!cb)
+        return;
+    if (cb->password)
+        PyMem_Free(cb->password);
+    PyMem_Free(cb);
+}
+
+%}
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Engine key/cert load functions.
+ * See above notice about callback_data.
+ */
+%rename(engine_load_private_key) ENGINE_load_private_key;
+extern EVP_PKEY *ENGINE_load_private_key(ENGINE *e, const char *key_id,
+                    UI_METHOD *ui_method, void *callback_data);
+%rename(engine_load_public_key) ENGINE_load_public_key;
+extern EVP_PKEY *ENGINE_load_public_key(ENGINE *e, const char *key_id,
+                    UI_METHOD *ui_method, void *callback_data);
+
+/*
+ * This function may be not implemented in engine.
+ * pkcs11 engine has this control.
+ */
+%inline %{
+static PyObject *_engine_err;
+
+void engine_init_error(PyObject *engine_err) {
+    Py_INCREF(engine_err);
+    _engine_err = engine_err;
+}
+
+X509 * engine_load_certificate(ENGINE *e, const char * slot) {
+    struct {
+        const char * slot;
+        X509 * cert;
+    } cbd;
+    cbd.slot = slot;
+    cbd.cert = NULL;
+    if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &cbd, NULL, 0)) {
+        PyErr_SetString(_engine_err, "cannot load certificate");
+        return NULL;
+    }
+    return cbd.cert;
+}
+%}
+
+/* These flags are used to control combinations of algorithm (methods)
+ * by bitwise "OR"ing. */
+%constant int ENGINE_METHOD_RSA = 0x0001;
+%constant int ENGINE_METHOD_DSA = 0x0002;
+%constant int ENGINE_METHOD_DH = 0x0004;
+%constant int ENGINE_METHOD_RAND = 0x0008;
+%constant int ENGINE_METHOD_ECDH = 0x0010;
+%constant int ENGINE_METHOD_ECDSA = 0x0020;
+%constant int ENGINE_METHOD_CIPHERS = 0x0040;
+%constant int ENGINE_METHOD_DIGESTS = 0x0080;
+%constant int ENGINE_METHOD_STORE = 0x0100;
+/* Obvious all-or-nothing cases. */
+%constant int ENGINE_METHOD_ALL = 0xFFFF;
+%constant int ENGINE_METHOD_NONE = 0x0000;
+
+%rename(engine_set_default) ENGINE_set_default;
+extern int ENGINE_set_default(ENGINE *e, unsigned int flags);
+
diff --git a/SWIG/_evp.i b/SWIG/_evp.i
new file mode 100644 (file)
index 0000000..0593eed
--- /dev/null
@@ -0,0 +1,596 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved.
+
+Portions Copyright (c) 2004-2007 Open Source Applications Foundation.
+Author: Heikki Toivonen
+
+Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+
+*/
+
+%include <openssl/opensslconf.h>
+
+%{
+#include <assert.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rsa.h>
+#include <openssl/opensslv.h>
+%}
+
+%apply Pointer NONNULL { EVP_MD_CTX * };
+%apply Pointer NONNULL { EVP_MD * };
+%apply Pointer NONNULL { EVP_PKEY * };
+%apply Pointer NONNULL { HMAC_CTX * };
+%apply Pointer NONNULL { EVP_CIPHER_CTX * };
+%apply Pointer NONNULL { EVP_CIPHER * };
+%apply Pointer NONNULL { RSA * };
+
+%rename(md5) EVP_md5;
+extern const EVP_MD *EVP_md5(void);
+%rename(sha1) EVP_sha1;
+extern const EVP_MD *EVP_sha1(void);
+%rename(ripemd160) EVP_ripemd160;
+extern const EVP_MD *EVP_ripemd160(void);
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%rename(sha224) EVP_sha224;
+extern const EVP_MD *EVP_sha224(void);
+%rename(sha256) EVP_sha256;
+extern const EVP_MD *EVP_sha256(void);
+%rename(sha384) EVP_sha384;
+extern const EVP_MD *EVP_sha384(void);
+%rename(sha512) EVP_sha512;
+extern const EVP_MD *EVP_sha512(void);
+#endif
+
+%rename(digest_init) EVP_DigestInit;
+extern int EVP_DigestInit(EVP_MD_CTX *, const EVP_MD *);
+
+%rename(des_ecb) EVP_des_ecb;
+extern const EVP_CIPHER *EVP_des_ecb(void);
+%rename(des_ede_ecb) EVP_des_ede;
+extern const EVP_CIPHER *EVP_des_ede(void);
+%rename(des_ede3_ecb) EVP_des_ede3;
+extern const EVP_CIPHER *EVP_des_ede3(void);
+%rename(des_cbc) EVP_des_cbc;
+extern const EVP_CIPHER *EVP_des_cbc(void);
+%rename(des_ede_cbc) EVP_des_ede_cbc;
+extern const EVP_CIPHER *EVP_des_ede_cbc(void);
+%rename(des_ede3_cbc) EVP_des_ede3_cbc;
+extern const EVP_CIPHER *EVP_des_ede3_cbc(void);
+%rename(des_cfb) EVP_des_cfb;
+extern const EVP_CIPHER *EVP_des_cfb(void);
+%rename(des_ede_cfb) EVP_des_ede_cfb;
+extern const EVP_CIPHER *EVP_des_ede_cfb(void);
+%rename(des_ede3_cfb) EVP_des_ede3_cfb;
+extern const EVP_CIPHER *EVP_des_ede3_cfb(void);
+%rename(des_ofb) EVP_des_ofb;
+extern const EVP_CIPHER *EVP_des_ofb(void);
+%rename(des_ede_ofb) EVP_des_ede_ofb;
+extern const EVP_CIPHER *EVP_des_ede_ofb(void);
+%rename(des_ede3_ofb) EVP_des_ede3_ofb;
+extern const EVP_CIPHER *EVP_des_ede3_ofb(void);
+%rename(bf_ecb) EVP_bf_ecb;
+extern const EVP_CIPHER *EVP_bf_ecb(void);
+%rename(bf_cbc) EVP_bf_cbc;
+extern const EVP_CIPHER *EVP_bf_cbc(void);
+%rename(bf_cfb) EVP_bf_cfb;
+extern const EVP_CIPHER *EVP_bf_cfb(void);
+%rename(bf_ofb) EVP_bf_ofb;
+extern const EVP_CIPHER *EVP_bf_ofb(void);
+/*
+%rename(idea_ecb) extern const EVP_CIPHER *EVP_idea_ecb(void);
+%rename(idea_cbc) extern const EVP_CIPHER *EVP_idea_cbc(void);
+%rename(idea_cfb) extern const EVP_CIPHER *EVP_idea_cfb(void);
+%rename(idea_ofb) extern const EVP_CIPHER *EVP_idea_ofb(void);
+*/
+%rename(cast5_ecb) EVP_cast5_ecb;
+extern const EVP_CIPHER *EVP_cast5_ecb(void);
+%rename(cast5_cbc) EVP_cast5_cbc;
+extern const EVP_CIPHER *EVP_cast5_cbc(void);
+%rename(cast5_cfb) EVP_cast5_cfb;
+extern const EVP_CIPHER *EVP_cast5_cfb(void);
+%rename(cast5_ofb) EVP_cast5_ofb;
+extern const EVP_CIPHER *EVP_cast5_ofb(void);
+/*
+%rename(rc5_ecb) extern const EVP_CIPHER *EVP_rc5_32_12_16_ecb(void);
+%rename(rc5_cbc) extern const EVP_CIPHER *EVP_rc5_32_12_16_cbc(void);
+%rename(rc5_cfb) extern const EVP_CIPHER *EVP_rc5_32_12_16_cfb(void);
+%rename(rc5_ofb) extern const EVP_CIPHER *EVP_rc5_32_12_16_ofb(void);
+*/
+%rename(rc4) EVP_rc4;
+extern const EVP_CIPHER *EVP_rc4(void);
+%rename(rc2_40_cbc) EVP_rc2_40_cbc;
+extern const EVP_CIPHER *EVP_rc2_40_cbc(void);
+%rename(aes_128_ecb) EVP_aes_128_ecb;
+extern const EVP_CIPHER *EVP_aes_128_ecb(void);
+%rename(aes_128_cbc) EVP_aes_128_cbc;
+extern const EVP_CIPHER *EVP_aes_128_cbc(void);
+%rename(aes_128_cfb) EVP_aes_128_cfb;
+extern const EVP_CIPHER *EVP_aes_128_cfb(void);
+%rename(aes_128_ofb) EVP_aes_128_ofb;
+extern const EVP_CIPHER *EVP_aes_128_ofb(void);
+%rename(aes_192_ecb) EVP_aes_192_ecb;
+extern const EVP_CIPHER *EVP_aes_192_ecb(void);
+%rename(aes_192_cbc) EVP_aes_192_cbc;
+extern const EVP_CIPHER *EVP_aes_192_cbc(void);
+%rename(aes_192_cfb) EVP_aes_192_cfb;
+extern const EVP_CIPHER *EVP_aes_192_cfb(void);
+%rename(aes_192_ofb) EVP_aes_192_ofb;
+extern const EVP_CIPHER *EVP_aes_192_ofb(void);
+%rename(aes_256_ecb) EVP_aes_256_ecb;
+extern const EVP_CIPHER *EVP_aes_256_ecb(void);
+%rename(aes_256_cbc) EVP_aes_256_cbc;
+extern const EVP_CIPHER *EVP_aes_256_cbc(void);
+%rename(aes_256_cfb) EVP_aes_256_cfb;
+extern const EVP_CIPHER *EVP_aes_256_cfb(void);
+%rename(aes_256_ofb) EVP_aes_256_ofb;
+extern const EVP_CIPHER *EVP_aes_256_ofb(void);
+
+%rename(cipher_set_padding) EVP_CIPHER_CTX_set_padding;
+extern int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int);
+
+%rename(pkey_new) EVP_PKEY_new;
+extern EVP_PKEY *EVP_PKEY_new(void);
+%rename(pkey_free) EVP_PKEY_free;
+extern void EVP_PKEY_free(EVP_PKEY *);
+%rename(pkey_assign) EVP_PKEY_assign;
+extern int EVP_PKEY_assign(EVP_PKEY *, int, char *);
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_EC)
+%rename(pkey_assign_ec) EVP_PKEY_assign_EC_KEY;
+extern int EVP_PKEY_assign_EC_KEY(EVP_PKEY *, EC_KEY *);
+#endif
+%rename(pkey_set1_rsa) EVP_PKEY_set1_RSA;
+extern int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *);
+%rename(pkey_get1_rsa) EVP_PKEY_get1_RSA;
+extern RSA* EVP_PKEY_get1_RSA(EVP_PKEY *);
+%rename(sign_init) EVP_SignInit;
+extern int EVP_SignInit(EVP_MD_CTX *, const EVP_MD *);
+%rename(verify_init) EVP_VerifyInit;
+extern int EVP_VerifyInit(EVP_MD_CTX *, const EVP_MD *);
+%rename(pkey_size) EVP_PKEY_size;
+extern int EVP_PKEY_size(EVP_PKEY *);
+
+%inline %{
+#define PKCS5_SALT_LEN  8
+
+static PyObject *_evp_err;
+
+void evp_init(PyObject *evp_err) {
+    Py_INCREF(evp_err);
+    _evp_err = evp_err;
+}
+
+PyObject *pkcs5_pbkdf2_hmac_sha1(PyObject *pass,
+                                 PyObject *salt,
+                                 int iter,
+                                 int keylen) {
+    unsigned char key[EVP_MAX_KEY_LENGTH];
+    unsigned char *saltbuf;
+    char *passbuf;
+    PyObject *ret;
+    int passlen, saltlen;
+
+    if (m2_PyObject_AsReadBufferInt(pass, (const void **)&passbuf,
+                                    &passlen) == -1)
+        return NULL;
+    if (m2_PyObject_AsReadBufferInt(salt, (const void **)&saltbuf,
+                                    &saltlen) == -1)
+        return NULL;
+
+    PKCS5_PBKDF2_HMAC_SHA1(passbuf, passlen, saltbuf, saltlen, iter,
+                           keylen, key);
+    ret = PyString_FromStringAndSize((char*)key, keylen);
+    OPENSSL_cleanse(key, keylen);
+    return ret;
+}
+
+EVP_MD_CTX *md_ctx_new(void) {
+    EVP_MD_CTX *ctx;
+
+    if (!(ctx = EVP_MD_CTX_create())) {
+        PyErr_SetString(PyExc_MemoryError, "md_ctx_new");
+    }
+    return ctx;
+}
+
+void md_ctx_free(EVP_MD_CTX *ctx) {
+    EVP_MD_CTX_destroy(ctx);
+}
+
+int digest_update(EVP_MD_CTX *ctx, PyObject *blob) {
+    const void *buf;
+    Py_ssize_t len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+        return -1;
+
+    return EVP_DigestUpdate(ctx, buf, len);
+}
+
+PyObject *digest_final(EVP_MD_CTX *ctx) {
+    void *blob;
+    int blen;
+    PyObject *ret;
+
+    if (!(blob = PyMem_Malloc(ctx->digest->md_size))) {
+        PyErr_SetString(PyExc_MemoryError, "digest_final");
+        return NULL;
+    }
+    if (!EVP_DigestFinal(ctx, blob, (unsigned int *)&blen)) {
+        PyMem_Free(blob);
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize(blob, blen);
+    PyMem_Free(blob);
+    return ret;
+}
+
+HMAC_CTX *hmac_ctx_new(void) {
+    HMAC_CTX *ctx;
+
+    if (!(ctx = (HMAC_CTX *)PyMem_Malloc(sizeof(HMAC_CTX)))) {
+        PyErr_SetString(PyExc_MemoryError, "hmac_ctx_new");
+        return NULL;
+    }
+    HMAC_CTX_init(ctx);
+    return ctx;
+}
+
+void hmac_ctx_free(HMAC_CTX *ctx) {
+    HMAC_CTX_cleanup(ctx);
+    PyMem_Free((void *)ctx);
+}
+
+PyObject *hmac_init(HMAC_CTX *ctx, PyObject *key, const EVP_MD *md) {
+    const void *kbuf;
+    int klen;
+
+    if (m2_PyObject_AsReadBufferInt(key, &kbuf, &klen) == -1)
+        return NULL;
+
+    HMAC_Init(ctx, kbuf, klen, md);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *hmac_update(HMAC_CTX *ctx, PyObject *blob) {
+    const void *buf;
+    Py_ssize_t len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+        return NULL;
+
+    HMAC_Update(ctx, buf, len);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *hmac_final(HMAC_CTX *ctx) {
+    void *blob;
+    int blen;
+    PyObject *ret;
+
+    if (!(blob = PyMem_Malloc(ctx->md->md_size))) {
+        PyErr_SetString(PyExc_MemoryError, "hmac_final");
+        return NULL;
+    }
+    HMAC_Final(ctx, blob, (unsigned int *)&blen);
+    ret = PyString_FromStringAndSize(blob, blen);
+    PyMem_Free(blob);
+    return ret;
+}
+
+PyObject *hmac(PyObject *key, PyObject *data, const EVP_MD *md) {
+    const void *kbuf, *dbuf;
+    void *blob;
+    int klen;
+    unsigned int blen;
+    Py_ssize_t dlen;
+    PyObject *ret;
+
+    if ((m2_PyObject_AsReadBufferInt(key, &kbuf, &klen) == -1)
+        || (PyObject_AsReadBuffer(data, &dbuf, &dlen) == -1))
+        return NULL;
+
+    if (!(blob = PyMem_Malloc(EVP_MAX_MD_SIZE))) {
+        PyErr_SetString(PyExc_MemoryError, "hmac");
+        return NULL;
+    }
+    HMAC(md, kbuf, klen, dbuf, dlen, blob, &blen);
+    blob = PyMem_Realloc(blob, blen);
+    ret = PyString_FromStringAndSize(blob, blen);
+    PyMem_Free(blob);
+    return ret;
+}
+
+EVP_CIPHER_CTX *cipher_ctx_new(void) {
+    EVP_CIPHER_CTX *ctx;
+
+    if (!(ctx = (EVP_CIPHER_CTX *)PyMem_Malloc(sizeof(EVP_CIPHER_CTX)))) {
+        PyErr_SetString(PyExc_MemoryError, "cipher_ctx_new");
+        return NULL;
+    }
+    EVP_CIPHER_CTX_init(ctx);
+    return ctx;
+}
+
+void cipher_ctx_free(EVP_CIPHER_CTX *ctx) {
+    EVP_CIPHER_CTX_cleanup(ctx);
+    PyMem_Free((void *)ctx);
+}
+
+PyObject *bytes_to_key(const EVP_CIPHER *cipher, EVP_MD *md, 
+                        PyObject *data, PyObject *salt,
+                        PyObject *iv, /* Not used */
+                        int iter) {
+    unsigned char key[EVP_MAX_KEY_LENGTH];
+    const void *dbuf, *sbuf;
+    int dlen, klen;
+    Py_ssize_t slen;
+    PyObject *ret;
+
+    if ((m2_PyObject_AsReadBufferInt(data, &dbuf, &dlen) == -1)
+        || (PyObject_AsReadBuffer(salt, &sbuf, &slen) == -1))
+        return NULL;
+
+    assert((slen == 8) || (slen == 0));
+    klen = EVP_BytesToKey(cipher, md, (unsigned char *)sbuf, 
+        (unsigned char *)dbuf, dlen, iter, 
+        key, NULL); /* Since we are not returning IV no need to derive it */
+    ret = PyString_FromStringAndSize((char*)key, klen);
+    return ret;
+}
+
+PyObject *cipher_init(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, 
+                        PyObject *key, PyObject *iv, int mode) {
+    const void *kbuf, *ibuf;
+    Py_ssize_t klen, ilen;
+
+    if ((PyObject_AsReadBuffer(key, &kbuf, &klen) == -1)
+        || (PyObject_AsReadBuffer(iv, &ibuf, &ilen) == -1))
+        return NULL;
+
+    if (!EVP_CipherInit(ctx, cipher, (unsigned char *)kbuf,
+                        (unsigned char *)ibuf, mode)) {
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *cipher_update(EVP_CIPHER_CTX *ctx, PyObject *blob) {
+    const void *buf;
+    int len, olen;
+    void *obuf;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1)
+        return NULL;
+
+    if (!(obuf = PyMem_Malloc(len + EVP_CIPHER_CTX_block_size(ctx) - 1))) {
+        PyErr_SetString(PyExc_MemoryError, "cipher_update");
+        return NULL;
+    }
+    if (!EVP_CipherUpdate(ctx, obuf, &olen, (unsigned char *)buf, len)) {
+        PyMem_Free(obuf);
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize(obuf, olen);
+    PyMem_Free(obuf);
+    return ret;
+}
+
+PyObject *cipher_final(EVP_CIPHER_CTX *ctx) {
+    void *obuf;
+    int olen;
+    PyObject *ret;
+
+    if (!(obuf = PyMem_Malloc(ctx->cipher->block_size))) {
+        PyErr_SetString(PyExc_MemoryError, "cipher_final");
+        return NULL;
+    }
+    if (!EVP_CipherFinal(ctx, (unsigned char *)obuf, &olen)) {
+        PyMem_Free(obuf);
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize(obuf, olen);
+    PyMem_Free(obuf);
+    return ret;
+}
+
+PyObject *sign_update(EVP_MD_CTX *ctx, PyObject *blob) {
+    const void *buf;
+    Py_ssize_t len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+        return NULL;
+
+    if (!EVP_SignUpdate(ctx, buf, len)) {
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *sign_final(EVP_MD_CTX *ctx, EVP_PKEY *pkey) {
+    PyObject *ret;
+    unsigned char *sigbuf;
+    unsigned int siglen = EVP_PKEY_size(pkey);
+
+    sigbuf = (unsigned char*)OPENSSL_malloc(siglen);
+    if (!sigbuf) {
+        PyErr_SetString(PyExc_MemoryError, "sign_final");
+        return NULL;
+    }
+
+    if (!EVP_SignFinal(ctx, sigbuf, &siglen, pkey)) {
+        OPENSSL_cleanse(sigbuf, siglen);
+        OPENSSL_free(sigbuf);
+        PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((char*)sigbuf, siglen);
+    OPENSSL_cleanse(sigbuf, siglen);
+    OPENSSL_free(sigbuf);
+    return ret;
+}
+
+int verify_update(EVP_MD_CTX *ctx, PyObject *blob) {
+    const void *buf;
+    Py_ssize_t len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+        return -1;
+
+    return EVP_VerifyUpdate(ctx, buf, len);
+}
+
+
+int verify_final(EVP_MD_CTX *ctx, PyObject *blob, EVP_PKEY *pkey) {
+    unsigned char *kbuf; 
+    int len;
+
+    if (m2_PyObject_AsReadBufferInt(blob, (const void **)&kbuf, &len) == -1)
+        return -1;
+
+    return EVP_VerifyFinal(ctx, kbuf, len, pkey);
+}
+%}
+
+%inline %{
+int pkey_write_pem_no_cipher(EVP_PKEY *pkey, BIO *f, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_PKCS8PrivateKey(f, pkey, NULL, NULL, 0,
+            passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+int pkey_write_pem(EVP_PKEY *pkey, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_PKCS8PrivateKey(f, pkey, cipher, NULL, 0,
+            passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+EVP_PKEY *pkey_read_pem(BIO *f, PyObject *pyfunc) {
+    EVP_PKEY *pk;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    pk = PEM_read_bio_PrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return pk;
+}
+
+int pkey_assign_rsa(EVP_PKEY *pkey, RSA *rsa) {
+    return EVP_PKEY_assign_RSA(pkey, rsa);
+}
+
+PyObject *pkey_as_der(EVP_PKEY *pkey) {
+    unsigned char * pp = NULL;
+    int len;
+    PyObject * der;
+    len = i2d_PUBKEY(pkey, &pp);
+    if (len < 0){
+        PyErr_SetString(PyExc_ValueError, "EVP_PKEY as DER failed");
+        return NULL; 
+    }
+    der = PyString_FromStringAndSize((char*)pp, len);
+    OPENSSL_free(pp);
+    return der;
+}
+
+PyObject *pkey_get_modulus(EVP_PKEY *pkey)
+{
+    RSA *rsa;
+    DSA *dsa;
+    BIO *bio;
+    BUF_MEM *bptr;
+    PyObject *ret;
+
+    switch (pkey->type) {
+        case EVP_PKEY_RSA:
+            rsa = EVP_PKEY_get1_RSA(pkey);
+
+            bio = BIO_new(BIO_s_mem());
+            if (!bio) {
+                RSA_free(rsa);
+                PyErr_SetString(PyExc_MemoryError, "pkey_get_modulus");
+                return NULL;
+            }
+            
+            if (!BN_print(bio, rsa->n)) {
+                PyErr_SetString(PyExc_RuntimeError, 
+                      ERR_error_string(ERR_get_error(), NULL));
+                BIO_free(bio);
+                RSA_free(rsa);
+                return NULL;
+            }
+            BIO_get_mem_ptr(bio, &bptr);
+            ret = PyString_FromStringAndSize(bptr->data, bptr->length);
+            BIO_set_close(bio, BIO_CLOSE);
+            BIO_free(bio);
+            RSA_free(rsa);
+
+            break;
+
+        case EVP_PKEY_DSA:
+            dsa = EVP_PKEY_get1_DSA(pkey);
+
+            bio = BIO_new(BIO_s_mem());
+            if (!bio) {
+                DSA_free(dsa);
+                PyErr_SetString(PyExc_MemoryError, "pkey_get_modulus");
+                return NULL;
+            }
+
+            if (!BN_print(bio, dsa->pub_key)) {
+                PyErr_SetString(PyExc_RuntimeError, 
+                      ERR_error_string(ERR_get_error(), NULL));
+                BIO_free(bio);
+                DSA_free(dsa);
+                return NULL;
+            }
+            BIO_get_mem_ptr(bio, &bptr);
+            ret = PyString_FromStringAndSize(bptr->data, bptr->length);
+            BIO_set_close(bio, BIO_CLOSE);
+            BIO_free(bio);
+            DSA_free(dsa);
+
+            break;
+            
+        default:
+            PyErr_SetString(PyExc_ValueError, "unsupported key type");
+            return NULL;
+    }
+    
+    return ret;
+}
+
+
+%}
+
diff --git a/SWIG/_lib.h b/SWIG/_lib.h
new file mode 100644 (file)
index 0000000..b53bc2a
--- /dev/null
@@ -0,0 +1,27 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _lib.h 593 2007-10-12 21:46:34Z heikki $ */
+
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
+typedef struct _blob {
+       unsigned char *data;
+       int len;
+} Blob;
+
+Blob *blob_new(int len, const char *errmsg);
+Blob *blob_copy(Blob *from, const char *errmsg);
+void blob_free(Blob *blob);
+
+static int m2_PyObject_AsReadBufferInt(PyObject *obj, const void **buffer,
+                                       int *buffer_len);
+static int m2_PyString_AsStringAndSizeInt(PyObject *obj, char **s, int *len);
+
+void gen_callback(int p, int n, void *arg);
+int passphrase_callback(char *buf, int num, int v, void *userdata);
+
+void lib_init(void);
+
diff --git a/SWIG/_lib.i b/SWIG/_lib.i
new file mode 100644 (file)
index 0000000..42dc180
--- /dev/null
@@ -0,0 +1,511 @@
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/* $Id: _lib.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <ceval.h>
+
+/* Blob interface. Deprecated. */
+
+Blob *blob_new(int len, const char *errmsg) {
+    
+    Blob *blob;
+    if (!(blob=(Blob *)PyMem_Malloc(sizeof(Blob)))){
+        PyErr_SetString(PyExc_MemoryError, errmsg);
+        return NULL;
+    }
+    if (!(blob->data=(unsigned char *)PyMem_Malloc(len))) {
+        PyMem_Free(blob);
+        PyErr_SetString(PyExc_MemoryError, errmsg);
+        return NULL;
+    }
+    blob->len=len;
+    return blob;
+}
+
+Blob *blob_copy(Blob *from, const char *errmsg) {
+    Blob *blob=blob_new(from->len, errmsg);
+    if (!blob) {
+        PyErr_SetString(PyExc_MemoryError, errmsg);
+        return NULL;
+    }
+    memcpy(blob->data, from->data, from->len);
+    return blob;
+}
+
+void blob_free(Blob *blob) {
+    PyMem_Free(blob->data);
+    PyMem_Free(blob);
+}
+
+
+/* Python helpers. */
+
+%}
+%ignore m2_PyObject_AsReadBufferInt;
+%ignore m2_PyString_AsStringAndSizeInt;
+%{
+static int
+m2_PyObject_AsReadBufferInt(PyObject *obj, const void **buffer,
+                int *buffer_len)
+{
+    int ret;
+    Py_ssize_t len;
+
+    ret = PyObject_AsReadBuffer(obj, buffer, &len);
+    if (ret)
+        return ret;
+    if (len > INT_MAX) {
+        PyErr_SetString(PyExc_ValueError, "object too large");
+        return -1;
+    }
+    *buffer_len = len;
+    return 0;
+}
+
+static int
+m2_PyString_AsStringAndSizeInt(PyObject *obj, char **s, int *len)
+{
+    int ret;
+    Py_ssize_t len2;
+
+    ret = PyString_AsStringAndSize(obj, s, &len2);
+    if (ret)
+       return ret;
+    if (len2 > INT_MAX) {
+       PyErr_SetString(PyExc_ValueError, "string too large");
+       return -1;
+    }
+    *len = len2;
+    return 0;
+}
+
+
+/* C callbacks invoked by OpenSSL; these in turn call back into 
+Python. */
+
+int ssl_verify_callback(int ok, X509_STORE_CTX *ctx) {
+    PyObject *argv, *ret;
+    PyObject *_x509_store_ctx_swigptr=0, *_x509_store_ctx_obj=0, *_x509_store_ctx_inst=0, *_klass=0;
+    PyObject *_x509=0, *_ssl_ctx=0;
+    SSL *ssl;
+    SSL_CTX *ssl_ctx;
+    X509 *x509;
+    int errnum, errdepth;
+    int cret;
+    int new_style_callback = 0, warning_raised_exception=0;
+    PyGILState_STATE gilstate;
+
+    ssl = (SSL *)X509_STORE_CTX_get_app_data(ctx);
+
+    gilstate = PyGILState_Ensure();
+
+    if (PyMethod_Check(ssl_verify_cb_func)) {
+        PyObject *func;
+        PyCodeObject *code;
+        func = PyMethod_Function(ssl_verify_cb_func);
+        code = (PyCodeObject *) PyFunction_GetCode(func);
+        if (code && code->co_argcount == 3) { /* XXX Python internals */
+            new_style_callback = 1;
+        }
+    } else if (PyFunction_Check(ssl_verify_cb_func)) {
+        PyCodeObject *code = (PyCodeObject *) PyFunction_GetCode(ssl_verify_cb_func);
+        if (code && code->co_argcount == 2) { /* XXX Python internals */
+            new_style_callback = 1;
+        }    
+    } else {
+        /* XXX There are lots of other callable types, but we will assume
+         * XXX that any other type of callable uses the new style callback,
+         * XXX although this is not entirely safe assumption.
+         */
+        new_style_callback = 1;
+    }
+    
+    if (new_style_callback) {
+        PyObject *x509mod = PyDict_GetItemString(PyImport_GetModuleDict(), "M2Crypto.X509");
+        _klass = PyObject_GetAttrString(x509mod, "X509_Store_Context");
+    
+        _x509_store_ctx_swigptr = SWIG_NewPointerObj((void *)ctx, SWIGTYPE_p_X509_STORE_CTX, 0);
+        _x509_store_ctx_obj = Py_BuildValue("(Oi)", _x509_store_ctx_swigptr, 0);
+        _x509_store_ctx_inst = PyInstance_New(_klass, _x509_store_ctx_obj, NULL);
+        argv = Py_BuildValue("(iO)", ok, _x509_store_ctx_inst);
+    } else {
+        if (PyErr_Warn(PyExc_DeprecationWarning, "Old style callback, use cb_func(ok, store) instead")) {
+            warning_raised_exception = 1;
+        }
+       
+        x509 = X509_STORE_CTX_get_current_cert(ctx);
+        errnum = X509_STORE_CTX_get_error(ctx);
+        errdepth = X509_STORE_CTX_get_error_depth(ctx);
+    
+        ssl = (SSL *)X509_STORE_CTX_get_app_data(ctx);
+        ssl_ctx = SSL_get_SSL_CTX(ssl);
+    
+        _x509 = SWIG_NewPointerObj((void *)x509, SWIGTYPE_p_X509, 0);
+        _ssl_ctx = SWIG_NewPointerObj((void *)ssl_ctx, SWIGTYPE_p_SSL_CTX, 0);
+        argv = Py_BuildValue("(OOiii)", _ssl_ctx, _x509, errnum, errdepth, ok);    
+    }
+
+    if (!warning_raised_exception) {
+        ret = PyEval_CallObject(ssl_verify_cb_func, argv);
+    } else {
+        ret = 0;
+    }
+
+    if (!ret) {
+        /* Got an exception in PyEval_CallObject(), let's fail verification
+         * to be safe.
+         */
+        cret = 0;   
+    } else {
+        cret = (int)PyInt_AsLong(ret);
+    }
+    Py_XDECREF(ret);
+    Py_XDECREF(argv);
+    if (new_style_callback) {
+        Py_XDECREF(_x509_store_ctx_inst);
+        Py_XDECREF(_x509_store_ctx_obj);
+        Py_XDECREF(_x509_store_ctx_swigptr);
+        Py_XDECREF(_klass);
+    } else {
+        Py_XDECREF(_x509);
+        Py_XDECREF(_ssl_ctx);
+    }
+
+    PyGILState_Release(gilstate);
+
+    return cret;
+}
+
+void ssl_info_callback(const SSL *s, int where, int ret) {
+    PyObject *argv, *retval, *_SSL;
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+
+    _SSL = SWIG_NewPointerObj((void *)s, SWIGTYPE_p_SSL, 0);
+    argv = Py_BuildValue("(iiO)", where, ret, _SSL);
+    
+    retval = PyEval_CallObject(ssl_info_cb_func, argv);
+
+    Py_XDECREF(retval);
+    Py_XDECREF(argv);
+    Py_XDECREF(_SSL);
+
+    PyGILState_Release(gilstate);
+}
+
+DH *ssl_set_tmp_dh_callback(SSL *ssl, int is_export, int keylength) {
+    PyObject *argv, *ret, *_ssl;
+    DH *dh;
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+
+    _ssl = SWIG_NewPointerObj((void *)ssl, SWIGTYPE_p_SSL, 0);
+    argv = Py_BuildValue("(Oii)", _ssl, is_export, keylength);
+
+    ret = PyEval_CallObject(ssl_set_tmp_dh_cb_func, argv);
+
+    if ((SWIG_ConvertPtr(ret, (void **)&dh, SWIGTYPE_p_DH, SWIG_POINTER_EXCEPTION | 0)) == -1)
+      dh = NULL;
+    Py_XDECREF(ret);
+    Py_XDECREF(argv);
+    Py_XDECREF(_ssl);
+
+    PyGILState_Release(gilstate);
+
+    return dh;
+}
+
+RSA *ssl_set_tmp_rsa_callback(SSL *ssl, int is_export, int keylength) {
+    PyObject *argv, *ret, *_ssl;
+    RSA *rsa;
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+
+    _ssl = SWIG_NewPointerObj((void *)ssl, SWIGTYPE_p_SSL, 0);
+    argv = Py_BuildValue("(Oii)", _ssl, is_export, keylength);
+
+    ret = PyEval_CallObject(ssl_set_tmp_rsa_cb_func, argv);
+
+    if ((SWIG_ConvertPtr(ret, (void **)&rsa, SWIGTYPE_p_RSA, SWIG_POINTER_EXCEPTION | 0)) == -1)
+      rsa = NULL;
+    Py_XDECREF(ret);
+    Py_XDECREF(argv);
+    Py_XDECREF(_ssl);
+
+    PyGILState_Release(gilstate);
+
+    return rsa;
+}
+
+void gen_callback(int p, int n, void *arg) {
+    PyObject *argv, *ret, *cbfunc;
+    PyGILState_STATE gilstate;
+    gilstate = PyGILState_Ensure();
+    cbfunc = (PyObject *)arg;
+    argv = Py_BuildValue("(ii)", p, n);
+    ret = PyEval_CallObject(cbfunc, argv);
+    Py_DECREF(argv);
+    Py_XDECREF(ret);
+    PyGILState_Release(gilstate);
+}
+
+int passphrase_callback(char *buf, int num, int v, void *arg) {
+    int i;
+    Py_ssize_t len;
+    char *str;
+    PyObject *argv, *ret, *cbfunc;
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+    cbfunc = (PyObject *)arg;
+    argv = Py_BuildValue("(i)", v);
+    ret = PyEval_CallObject(cbfunc, argv);
+    Py_DECREF(argv);
+    if (ret == NULL) {
+        PyGILState_Release(gilstate);
+        return -1;
+    }
+    if (!PyString_Check(ret)) {
+        Py_DECREF(ret);
+        PyGILState_Release(gilstate);
+        return -1;
+    }
+    if ((len = PyString_Size(ret)) > num)
+        len = num;
+    str = PyString_AsString(ret); 
+    for (i = 0; i < len; i++)
+        buf[i] = str[i];
+    Py_DECREF(ret);
+    PyGILState_Release(gilstate);
+    return len;
+}
+%}
+
+%inline %{
+void lib_init() {
+    SSLeay_add_all_algorithms();
+    ERR_load_ERR_strings();
+}
+
+/* Bignum routines that aren't not numerous enough to 
+warrant a separate file. */
+
+PyObject *bn_to_mpi(BIGNUM *bn) {
+    int len;
+    unsigned char *mpi;
+    PyObject *pyo;  
+
+    len = BN_bn2mpi(bn, NULL);
+    if (!(mpi=(unsigned char *)PyMem_Malloc(len))) {
+        PyErr_SetString(PyExc_RuntimeError, 
+            ERR_error_string(ERR_get_error(), NULL));
+        return NULL;
+    }
+    len=BN_bn2mpi(bn, mpi);
+    pyo=PyString_FromStringAndSize((const char *)mpi, len);
+    PyMem_Free(mpi);
+    return pyo;
+}
+
+BIGNUM *mpi_to_bn(PyObject *value) {
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    return BN_mpi2bn(vbuf, vlen, NULL);
+}
+
+PyObject *bn_to_bin(BIGNUM *bn) {
+    int len;
+    unsigned char *bin;
+    PyObject *pyo;  
+
+    len = BN_num_bytes(bn);
+    if (!(bin=(unsigned char *)PyMem_Malloc(len))) {
+      PyErr_SetString(PyExc_MemoryError, "bn_to_bin");
+      return NULL;
+    }
+    BN_bn2bin(bn, bin);
+    pyo=PyString_FromStringAndSize((const char *)bin, len);
+    PyMem_Free(bin);
+    return pyo;
+}
+
+BIGNUM *bin_to_bn(PyObject *value) {
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    return BN_bin2bn(vbuf, vlen, NULL);
+}
+
+PyObject *bn_to_hex(BIGNUM *bn) {
+    char *hex;
+    PyObject *pyo;  
+    Py_ssize_t len;
+
+    hex = BN_bn2hex(bn);
+    if (!hex) {
+        PyErr_SetString(PyExc_RuntimeError, 
+              ERR_error_string(ERR_get_error(), NULL));
+        OPENSSL_free(hex);
+        return NULL;    
+    }
+    len = strlen(hex);
+    pyo=PyString_FromStringAndSize(hex, len);
+    OPENSSL_free(hex);
+    return pyo;
+}
+
+BIGNUM *hex_to_bn(PyObject *value) {
+    const void *vbuf;
+    Py_ssize_t vlen;
+    BIGNUM *bn;
+
+    if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if ((bn=BN_new())==NULL) {
+        PyErr_SetString(PyExc_MemoryError, "hex_to_bn");
+        return NULL;
+    }
+    if (BN_hex2bn(&bn, (const char *)vbuf) <= 0) {
+        PyErr_SetString(PyExc_RuntimeError, 
+              ERR_error_string(ERR_get_error(), NULL));
+        BN_free(bn);
+        return NULL;
+    }
+    return bn;
+}
+
+BIGNUM *dec_to_bn(PyObject *value) {
+    const void *vbuf;
+    Py_ssize_t vlen;
+    BIGNUM *bn;
+
+    if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if ((bn=BN_new())==NULL) {
+      PyErr_SetString(PyExc_MemoryError, "dec_to_bn");
+      return NULL;
+    }
+    if ((BN_dec2bn(&bn, (const char *)vbuf) <= 0)) {
+      PyErr_SetString(PyExc_RuntimeError, 
+            ERR_error_string(ERR_get_error(), NULL));
+      BN_free(bn);
+      return NULL;
+    }
+    return bn;
+}
+%}
+
+
+/* Various useful typemaps. */
+
+%typemap(in) Blob * {
+    Py_ssize_t len;
+
+    if (!PyString_Check($input)) {
+        PyErr_SetString(PyExc_TypeError, "expected PyString");
+        return NULL;
+    }
+    len=PyString_Size($input);
+    if (len > INT_MAX) {
+        PyErr_SetString(PyExc_ValueError, "object too large");
+        return -1;
+    }
+    $1=(Blob *)PyMem_Malloc(sizeof(Blob));
+    if (!$1) {
+        PyErr_SetString(PyExc_MemoryError, "malloc Blob");
+        return NULL;
+    }
+    $1->data=(unsigned char *)PyString_AsString($input);
+    $1->len=len;
+}
+
+%typemap(out) Blob * {
+    if ($1==NULL) {
+        Py_INCREF(Py_None);
+        $result=Py_None;
+    } else {
+        $result=PyString_FromStringAndSize((const char *)$1->data, $1->len);
+        PyMem_Free($1->data);
+        PyMem_Free($1);
+    }
+}
+
+%typemap(in) FILE * {
+    if (!PyFile_Check($input)) {
+        PyErr_SetString(PyExc_TypeError, "expected PyFile");
+        return NULL;
+    }
+    $1=PyFile_AsFile($input);
+}
+
+%typemap(in) PyObject *pyfunc {
+    if (!PyCallable_Check($input)) {
+        PyErr_SetString(PyExc_TypeError, "expected PyCallable");
+        return NULL;
+    }
+    $1=$input;
+}
+
+%typemap(in) PyObject *pyblob {
+    if (!PyString_Check($input)) {
+        PyErr_SetString(PyExc_TypeError, "expected PyString");
+        return NULL;
+    }
+    $1=$input;
+}
+
+%typemap(in) PyObject * {
+    $1=$input;
+}
+
+%typemap(out) PyObject * {
+    $result=$1;
+}
+
+%typemap(out) int {
+    $result=PyInt_FromLong($1);
+    if (PyErr_Occurred()) SWIG_fail;
+}
+
+/* Pointer checks. */
+
+%apply Pointer NONNULL { Blob * };
+
+
+/* A bunch of "straight-thru" functions. */
+
+%rename(err_print_errors_fp) ERR_print_errors_fp;
+%threadallow ERR_print_errors_fp;
+extern void ERR_print_errors_fp(FILE *);
+%rename(err_print_errors) ERR_print_errors;
+%threadallow ERR_print_errors;
+extern void ERR_print_errors(BIO *);
+%rename(err_get_error) ERR_get_error;
+extern unsigned long ERR_get_error(void);
+%rename(err_peek_error) ERR_peek_error;
+extern unsigned long ERR_peek_error(void);
+%rename(err_lib_error_string) ERR_lib_error_string;
+extern const char *ERR_lib_error_string(unsigned long);
+%rename(err_func_error_string) ERR_func_error_string;
+extern const char *ERR_func_error_string(unsigned long);
+%rename(err_reason_error_string) ERR_reason_error_string;
+extern const char *ERR_reason_error_string(unsigned long);
diff --git a/SWIG/_m2crypto.def b/SWIG/_m2crypto.def
new file mode 100644 (file)
index 0000000..753db2c
--- /dev/null
@@ -0,0 +1,2 @@
+EXPORTS\r
+init__m2crypto
diff --git a/SWIG/_m2crypto.i b/SWIG/_m2crypto.i
new file mode 100644 (file)
index 0000000..3d779a1
--- /dev/null
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+ *
+ * Portions created by Open Source Applications Foundation (OSAF) are
+ * Copyright (C) 2004-2006 OSAF. All Rights Reserved.
+ *
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+ *
+ */
+
+%module(threads=1) _m2crypto
+/* We really don't need threadblock (PyGILState_Ensure() etc.) anywhere.
+   Disable threadallow as well, only enable it for operations likely to
+   block. */
+%nothreadblock;
+%nothreadallow;
+
+%{
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <_lib.h>
+
+#include "compile.h"
+
+static PyObject *ssl_verify_cb_func;
+static PyObject *ssl_info_cb_func;
+static PyObject *ssl_set_tmp_dh_cb_func;
+static PyObject *ssl_set_tmp_rsa_cb_func;
+%}
+
+%include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+#define CONST const
+#else
+#define CONST
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#define CONST098 const
+#else
+#define CONST098
+#endif
+
+/* Bring in STACK_OF macro definition */
+%include <openssl/safestack.h>
+
+/* Bring in LHASH_OF macro definition */
+/* XXX Can't include lhash.h where LHASH_OF is defined, because it includes
+   XXX stdio.h etc. which we fail to include. So we have to (re)define
+   XXX LHASH_OF here instead.
+%include <openssl/lhash.h>
+*/
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+#define LHASH_OF(type) struct lhash_st_##type
+#endif
+
+%include constraints.i
+%include _threads.i
+%include _lib.i
+%include _bio.i
+%include _bn.i
+%include _rand.i
+%include _evp.i
+%include _aes.i
+%include _rc4.i
+%include _dh.i
+%include _rsa.i
+%include _dsa.i
+%include _ssl.i
+%include _x509.i
+%include _asn1.i
+%include _pkcs7.i
+%include _util.i
+%include _ec.i
+%include _engine.i
+%include _objects.i
+
+#ifdef SWIG_VERSION
+%constant int encrypt = 1;
+%constant int decrypt = 0;
+#endif
+  
diff --git a/SWIG/_objects.i b/SWIG/_objects.i
new file mode 100644 (file)
index 0000000..40f5e51
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: syntax=c sts=4 sw=4
+ *
+ * ASN1_OBJECT manipulation functions from OBJ_obj2txt(3SSL).
+ * 
+ * Pavel Shramov
+ * IMEC MSU
+ */
+%{
+#include <openssl/objects.h>
+%}
+
+%apply Pointer NONNULL { ASN1_OBJECT * };
+%apply Pointer NONNULL { const char * };
+
+%rename(obj_nid2obj) OBJ_nid2obj;
+extern ASN1_OBJECT * OBJ_nid2obj(int n);
+%rename(obj_nid2ln)  OBJ_nid2ln;
+extern const char *  OBJ_nid2ln(int n);
+%rename(obj_nid2sn)  OBJ_nid2sn;
+extern const char *  OBJ_nid2sn(int n);
+
+%rename(obj_obj2nid) OBJ_obj2nid;
+extern int OBJ_obj2nid(const ASN1_OBJECT *o);
+
+%rename(obj_ln2nid) OBJ_ln2nid;
+extern int OBJ_ln2nid(const char *ln);
+%rename(obj_sn2nid) OBJ_sn2nid;
+extern int OBJ_sn2nid(const char *sn);
+
+%rename(obj_txt2nid) OBJ_txt2nid;
+extern int OBJ_txt2nid(const char *s);
+
+%rename(obj_txt2obj) OBJ_txt2obj;
+extern ASN1_OBJECT * OBJ_txt2obj(const char *s, int no_name);
+
+
+%rename(_obj_obj2txt) OBJ_obj2txt;
+extern int OBJ_obj2txt(char *, int, const ASN1_OBJECT *, int);
+
+
+%inline %{
+/*
+   From the manpage for OBJ_obt2txt ():
+   BUGS
+      OBJ_obj2txt() is awkward and messy to use: it doesn’t follow the
+      convention of other OpenSSL functions where the buffer can be set
+      to NULL to determine the amount of data that should be written.
+      Instead buf must point to a valid buffer and buf_len should be set
+      to a positive value. A buffer length of 80 should be more than
+      enough to handle any OID encountered in practice.
+
+   The first call to OBJ_obj2txt () therefore passes a non-NULL dummy
+   buffer. This wart is reportedly removed in OpenSSL 0.9.8b, although
+   the manpage has not been updated.
+
+   OBJ_obj2txt always prints \0 at the end. But the return value
+   is the number of "good" bytes written. So memory is allocated for
+   len + 1 bytes but only len bytes are marshalled to python.
+*/
+PyObject *obj_obj2txt(const ASN1_OBJECT *obj, int no_name)
+{
+    int len;
+    PyObject *ret;
+    char *buf;
+    char dummy[1];
+
+    len = OBJ_obj2txt(dummy, 1, obj, no_name);
+    if (len < 0) {
+        PyErr_SetString(PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    } else if (len == 0) {
+        /* XXX: For OpenSSL prior to 0.9.8b.
+
+          Changes between 0.9.8a and 0.9.8b  [04 May 2006]
+          ...
+          *) Several fixes and enhancements to the OID generation code. The old code
+             sometimes allowed invalid OIDs (1.X for X >= 40 for example), couldn't
+             handle numbers larger than ULONG_MAX, truncated printing and had a
+             non standard OBJ_obj2txt() behaviour.
+             [Steve Henson]
+        */
+
+        len = 80;
+    }
+
+    buf = PyMem_Malloc(len + 1);
+    len = OBJ_obj2txt(buf, len + 1, obj, no_name);
+    ret = PyString_FromStringAndSize(buf, len);
+    PyMem_Free(buf);
+
+    return ret;
+}
+%}
diff --git a/SWIG/_pkcs7.i b/SWIG/_pkcs7.i
new file mode 100644 (file)
index 0000000..174f40a
--- /dev/null
@@ -0,0 +1,241 @@
+/* Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _pkcs7.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/pkcs7.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { EVP_CIPHER * };
+%apply Pointer NONNULL { EVP_PKEY * };
+%apply Pointer NONNULL { PKCS7 * };
+%apply Pointer NONNULL { STACK_OF(X509) * };
+%apply Pointer NONNULL { X509 * };
+
+%rename(pkcs7_new) PKCS7_new;
+extern PKCS7 *PKCS7_new(void);
+%rename(pkcs7_free) PKCS7_free;
+extern void PKCS7_free(PKCS7 *);
+%rename(pkcs7_add_certificate) PKCS7_add_certificate;
+extern void PKCS7_add_certificate(PKCS7 *, X509 *);
+
+/* S/MIME operation */
+%constant int PKCS7_TEXT       = 0x1;
+%constant int PKCS7_NOCERTS    = 0x2;
+%constant int PKCS7_NOSIGS     = 0x4;
+%constant int PKCS7_NOCHAIN    = 0x8;
+%constant int PKCS7_NOINTERN   = 0x10;
+%constant int PKCS7_NOVERIFY   = 0x20;
+%constant int PKCS7_DETACHED   = 0x40;
+%constant int PKCS7_BINARY     = 0x80;
+%constant int PKCS7_NOATTR     = 0x100;
+
+%constant int PKCS7_SIGNED            = NID_pkcs7_signed;
+%constant int PKCS7_ENVELOPED         = NID_pkcs7_enveloped;
+%constant int PKCS7_SIGNED_ENVELOPED  = NID_pkcs7_signedAndEnveloped;
+%constant int PKCS7_DATA              = NID_pkcs7_data;
+
+%inline %{
+static PyObject *_pkcs7_err, *_smime_err;
+
+void pkcs7_init(PyObject *pkcs7_err) {
+    Py_INCREF(pkcs7_err);
+    _pkcs7_err = pkcs7_err;
+}
+
+void smime_init(PyObject *smime_err) {
+    Py_INCREF(smime_err);
+    _smime_err = smime_err;
+}
+%}
+
+%threadallow pkcs7_encrypt;
+%inline %{
+PKCS7 *pkcs7_encrypt(STACK_OF(X509) *stack, BIO *bio, EVP_CIPHER *cipher, int flags) {
+    return PKCS7_encrypt(stack, bio, cipher, flags);
+}
+
+PyObject *pkcs7_decrypt(PKCS7 *pkcs7, EVP_PKEY *pkey, X509 *cert, int flags) {
+    int outlen;
+    char *outbuf;
+    BIO *bio;
+    PyObject *ret; 
+
+    if (!(bio=BIO_new(BIO_s_mem()))) {
+        PyErr_SetString(PyExc_MemoryError, "pkcs7_decrypt");
+        return NULL;
+    }
+    if (!PKCS7_decrypt(pkcs7, pkey, cert, bio, flags)) {
+        PyErr_SetString(_pkcs7_err, ERR_reason_error_string(ERR_get_error()));
+        BIO_free(bio);
+        return NULL;
+    }
+    outlen = BIO_ctrl_pending(bio);
+    if (!(outbuf=(char *)PyMem_Malloc(outlen))) {
+        PyErr_SetString(PyExc_MemoryError, "pkcs7_decrypt");
+        BIO_free(bio);
+        return NULL;
+    }
+    BIO_read(bio, outbuf, outlen);
+    ret = PyString_FromStringAndSize(outbuf, outlen);
+    BIO_free(bio);
+    PyMem_Free(outbuf);
+    return ret;
+}
+%}
+
+%threadallow pkcs7_sign0;
+%inline %{
+PKCS7 *pkcs7_sign0(X509 *x509, EVP_PKEY *pkey, BIO *bio, int flags) {
+    return PKCS7_sign(x509, pkey, NULL, bio, flags);
+}
+%}
+
+%threadallow pkcs7_sign1;
+%inline %{
+PKCS7 *pkcs7_sign1(X509 *x509, EVP_PKEY *pkey, STACK_OF(X509) *stack, BIO *bio, int flags) {
+    return PKCS7_sign(x509, pkey, stack, bio, flags);
+}
+%}
+
+%inline %{
+PyObject *pkcs7_verify1(PKCS7 *pkcs7, STACK_OF(X509) *stack, X509_STORE *store, BIO *data, int flags) {
+    int res, outlen;
+    char *outbuf;
+    BIO *bio;
+    PyObject *ret; 
+
+    if (!(bio=BIO_new(BIO_s_mem()))) {
+        PyErr_SetString(PyExc_MemoryError, "pkcs7_verify1");
+        return NULL;
+    }
+    Py_BEGIN_ALLOW_THREADS
+    res = PKCS7_verify(pkcs7, stack, store, data, bio, flags);
+    Py_END_ALLOW_THREADS
+    if (!res) {
+        PyErr_SetString(_pkcs7_err, ERR_reason_error_string(ERR_get_error()));
+        BIO_free(bio);
+        return NULL;
+    }
+    outlen = BIO_ctrl_pending(bio);
+    if (!(outbuf=(char *)PyMem_Malloc(outlen))) {
+        PyErr_SetString(PyExc_MemoryError, "pkcs7_verify1");
+        BIO_free(bio);
+        return NULL;
+    }
+    BIO_read(bio, outbuf, outlen);
+    ret = PyString_FromStringAndSize(outbuf, outlen);
+    BIO_free(bio);
+    PyMem_Free(outbuf);
+    return ret;
+}
+
+PyObject *pkcs7_verify0(PKCS7 *pkcs7, STACK_OF(X509) *stack, X509_STORE *store, int flags) {
+    return pkcs7_verify1(pkcs7, stack, store, NULL, flags);
+}
+%}
+
+%threadallow smime_write_pkcs7_multi;
+%inline %{
+int smime_write_pkcs7_multi(BIO *bio, PKCS7 *pkcs7, BIO *data, int flags) {
+    return SMIME_write_PKCS7(bio, pkcs7, data, flags | PKCS7_DETACHED);
+}
+%}
+
+%threadallow smime_write_pkcs7;
+%inline %{
+int smime_write_pkcs7(BIO *bio, PKCS7 *pkcs7, int flags) {
+    return SMIME_write_PKCS7(bio, pkcs7, NULL, flags);
+}
+
+PyObject *smime_read_pkcs7(BIO *bio) {
+    BIO *bcont = NULL;
+    PKCS7 *p7;
+    PyObject *tuple, *_p7, *_BIO;
+
+    if (BIO_method_type(bio) == BIO_TYPE_MEM) {
+        /* OpenSSL FAQ explains that this is needed for mem BIO to return EOF,
+         * like file BIO does. Might need to do this for more mem BIOs but
+         * not sure if that is safe, so starting with just this single place.
+         */
+        BIO_set_mem_eof_return(bio, 0);
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    p7=SMIME_read_PKCS7(bio, &bcont);
+    Py_END_ALLOW_THREADS
+    if (!p7) {
+        PyErr_SetString(_smime_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (!(tuple=PyTuple_New(2))) {
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+        return NULL;
+    }
+    _p7 = SWIG_NewPointerObj((void *)p7, SWIGTYPE_p_PKCS7, 0);
+    PyTuple_SET_ITEM(tuple, 0, _p7);
+    if (!bcont) {
+        Py_INCREF(Py_None);
+        PyTuple_SET_ITEM(tuple, 1, Py_None);
+    } else {
+        _BIO = SWIG_NewPointerObj((void *)bcont, SWIGTYPE_p_BIO, 0);
+        PyTuple_SET_ITEM(tuple, 1, _BIO);
+    }
+    return tuple;
+}
+%}
+
+%threadallow pkcs7_read_bio;
+%inline %{
+PKCS7 *pkcs7_read_bio(BIO *bio) {
+    return PEM_read_bio_PKCS7(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow pkcs7_read_bio_der;
+%inline %{
+PKCS7 *pkcs7_read_bio_der(BIO *bio) {
+    return d2i_PKCS7_bio(bio, NULL);
+}
+%}
+
+%threadallow pkcs7_write_bio;
+%inline %{
+int pkcs7_write_bio(PKCS7 *pkcs7, BIO* bio) {
+    return PEM_write_bio_PKCS7(bio, pkcs7);
+}
+%}
+
+%threadallow pkcs7_write_bio_der;
+%inline %{
+int pkcs7_write_bio_der(PKCS7 *pkcs7, BIO *bio) {
+    return i2d_PKCS7_bio(bio, pkcs7);
+}
+
+int pkcs7_type_nid(PKCS7 *pkcs7) {
+    return OBJ_obj2nid(pkcs7->type);
+}
+
+const char *pkcs7_type_sn(PKCS7 *pkcs7) {
+    return OBJ_nid2sn(OBJ_obj2nid(pkcs7->type));
+}
+%}
+
+%threadallow smime_crlf_copy;
+%inline %{
+int smime_crlf_copy(BIO *in, BIO *out) {
+    return SMIME_crlf_copy(in, out, PKCS7_TEXT);
+}
+
+/* return STACK_OF(X509)* */     
+STACK_OF(X509) *pkcs7_get0_signers(PKCS7 *p7, STACK_OF(X509) *certs, int flags) {     
+    return PKCS7_get0_signers(p7, certs, flags);      
+}
+
+%}
+
diff --git a/SWIG/_rand.i b/SWIG/_rand.i
new file mode 100644 (file)
index 0000000..2d4f144
--- /dev/null
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _rand.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%module _rand
+
+%rename(rand_load_file) RAND_load_file;
+extern int RAND_load_file(const char *, long);
+%rename(rand_save_file) RAND_write_file;
+extern int RAND_write_file(const char *);
+%rename(rand_poll) RAND_poll;
+extern int RAND_poll(void);
+%rename(rand_status) RAND_status;
+extern int RAND_status(void);
+%rename(rand_cleanup) RAND_cleanup;
+extern void RAND_cleanup(void);
+
+%inline %{
+static PyObject *_rand_err;
+
+void rand_init(PyObject *rand_err) {
+    Py_INCREF(rand_err);
+    _rand_err = rand_err;
+}
+
+PyObject *rand_seed(PyObject *seed) {
+    const void *buf;
+    int len;
+
+    if (m2_PyObject_AsReadBufferInt(seed, &buf, &len) == -1)
+        return NULL;
+
+    RAND_seed(buf, len);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rand_add(PyObject *blob, double entropy) {
+    const void *buf;
+    int len;
+
+    if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1)
+        return NULL;
+
+    RAND_add(buf, len, entropy);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rand_bytes(int n) {
+    void *blob;
+    PyObject *obj;
+    
+    if (!(blob = PyMem_Malloc(n))) {
+        PyErr_SetString(PyExc_MemoryError, "rand_bytes");
+        return NULL;
+    }
+    if (RAND_bytes(blob, n)) {
+        obj = PyString_FromStringAndSize(blob, n);
+        PyMem_Free(blob);
+        return obj;
+    } else {
+        PyMem_Free(blob);
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+PyObject *rand_pseudo_bytes(int n) {
+    int ret;
+    unsigned char *blob;
+    PyObject *tuple;
+    
+    if (!(blob=(unsigned char *)PyMem_Malloc(n))) {
+        PyErr_SetString(PyExc_MemoryError, "rand_pseudo_bytes");
+        return NULL;
+    }
+    if (!(tuple=PyTuple_New(2))) {
+        PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+        PyMem_Free(blob);
+        return NULL;
+    }
+    ret = RAND_pseudo_bytes(blob, n);
+    if (ret == -1) {
+        PyMem_Free(blob);
+        Py_DECREF(tuple);
+        Py_INCREF(Py_None);
+        return Py_None;
+    } else {
+        PyTuple_SET_ITEM(tuple, 0, PyString_FromStringAndSize((char*)blob, n));
+        PyMem_Free(blob);
+        PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong((long)ret));
+        return tuple;
+    }
+}
+
+void rand_screen(void) {
+#ifdef __WINDOWS__
+    RAND_screen();
+#endif
+}
+
+int rand_win32_event(unsigned int imsg, int wparam, long lparam) {
+#ifdef __WINDOWS__
+    return RAND_event(imsg, wparam, lparam);
+#else
+    return 0;
+#endif
+}
+%}
+
+/* 
+2004-04-05, ngps: Still missing:
+  RAND_egd
+  RAND_egd_bytes
+  RAND_query_egd_bytes
+  RAND_file_name
+*/
+
+
diff --git a/SWIG/_rc4.i b/SWIG/_rc4.i
new file mode 100644 (file)
index 0000000..3259997
--- /dev/null
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _rc4.i 522 2007-05-08 22:21:51Z heikki $ */
+
+%{
+#include <openssl/rc4.h>
+%}
+
+%apply Pointer NONNULL { RC4_KEY * };
+
+%inline %{
+RC4_KEY *rc4_new(void) {
+    RC4_KEY *key;
+    
+    if (!(key = (RC4_KEY *)PyMem_Malloc(sizeof(RC4_KEY))))
+        PyErr_SetString(PyExc_MemoryError, "rc4_new");
+    return key;
+}   
+
+void rc4_free(RC4_KEY *key) {
+    PyMem_Free((void *)key);
+}
+
+PyObject *rc4_set_key(RC4_KEY *key, PyObject *value) {
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    RC4_set_key(key, vlen, vbuf);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rc4_update(RC4_KEY *key, PyObject *in) {
+    PyObject *ret;
+    const void *buf;
+    Py_ssize_t len;
+    void *out;
+
+    if (PyObject_AsReadBuffer(in, &buf, &len) == -1)
+        return NULL;
+
+    if (!(out = PyMem_Malloc(len))) {
+        PyErr_SetString(PyExc_MemoryError, "expected a string object");
+        return NULL;
+    }
+    RC4(key, len, buf, out);
+    ret = PyString_FromStringAndSize(out, len);
+    PyMem_Free(out);
+    return ret;
+}
+
+int rc4_type_check(RC4_KEY *key) {
+    return 1;
+}
+%}
diff --git a/SWIG/_rsa.i b/SWIG/_rsa.i
new file mode 100644 (file)
index 0000000..537e802
--- /dev/null
@@ -0,0 +1,452 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved. */
+/* $Id: _rsa.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/opensslv.h>
+%}
+
+%apply Pointer NONNULL { RSA * };
+%apply Pointer NONNULL { PyObject *pyfunc };
+
+%rename(rsa_new) RSA_new;
+extern RSA *RSA_new(void);
+%rename(rsa_free) RSA_free;
+extern void RSA_free(RSA *);
+%rename(rsa_size) RSA_size;
+extern int RSA_size(const RSA *);
+%rename(rsa_check_key) RSA_check_key;
+extern int RSA_check_key(const RSA *);
+
+%constant int no_padding        = RSA_NO_PADDING;
+%constant int pkcs1_padding     = RSA_PKCS1_PADDING;
+%constant int sslv23_padding    = RSA_SSLV23_PADDING;
+%constant int pkcs1_oaep_padding = RSA_PKCS1_OAEP_PADDING;
+
+%constant int NID_sha1 = NID_sha1;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%constant int NID_sha224 = NID_sha224;
+%constant int NID_sha256 = NID_sha256;
+%constant int NID_sha384 = NID_sha384;
+%constant int NID_sha512 = NID_sha512;
+#endif
+
+%constant int NID_md5 = NID_md5;
+
+%constant int NID_ripemd160 = NID_ripemd160;
+
+%inline %{
+static PyObject *_rsa_err;
+
+void rsa_init(PyObject *rsa_err) {
+    Py_INCREF(rsa_err);
+    _rsa_err = rsa_err;
+}
+%}
+
+%inline %{
+RSA *rsa_read_key(BIO *f, PyObject *pyfunc) {
+    RSA *rsa;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    rsa = PEM_read_bio_RSAPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return rsa;
+}
+%}
+
+%inline %{
+int rsa_write_key(RSA *rsa, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_RSAPrivateKey(f, rsa, cipher, NULL, 0,
+        passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%inline %{
+int rsa_write_key_no_cipher(RSA *rsa, BIO *f, PyObject *pyfunc) {
+    int ret;
+
+    Py_INCREF(pyfunc);
+    Py_BEGIN_ALLOW_THREADS
+    ret = PEM_write_bio_RSAPrivateKey(f, rsa, NULL, NULL, 0, 
+                      passphrase_callback, (void *)pyfunc);
+    Py_END_ALLOW_THREADS
+    Py_DECREF(pyfunc);
+    return ret;
+}
+%}
+
+%threadallow rsa_read_pub_key;
+%inline %{
+RSA *rsa_read_pub_key(BIO *f) {
+    return PEM_read_bio_RSA_PUBKEY(f, NULL, NULL, NULL);   
+}
+%}
+
+%threadallow rsa_write_pub_key;
+%inline %{
+int rsa_write_pub_key(RSA *rsa, BIO *f) {
+    return PEM_write_bio_RSA_PUBKEY(f, rsa);
+}
+
+PyObject *rsa_get_e(RSA *rsa) {
+    if (!rsa->e) {
+        PyErr_SetString(_rsa_err, "'e' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(rsa->e);
+}
+
+PyObject *rsa_get_n(RSA *rsa) {
+    if (!rsa->n) {
+        PyErr_SetString(_rsa_err, "'n' is unset");
+        return NULL;
+    }
+    return bn_to_mpi(rsa->n);
+}
+
+PyObject *rsa_set_e(RSA *rsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (rsa->e)
+        BN_free(rsa->e);
+    rsa->e = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rsa_set_n(RSA *rsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (rsa->n)
+        BN_free(rsa->n);
+    rsa->n = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rsa_set_e_bin(RSA *rsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_bin2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (rsa->e)
+        BN_free(rsa->e);
+    rsa->e = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rsa_set_n_bin(RSA *rsa, PyObject *value) {
+    BIGNUM *bn;
+    const void *vbuf;
+    int vlen;
+
+    if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+        return NULL;
+
+    if (!(bn = BN_bin2bn((unsigned char *)vbuf, vlen, NULL))) {
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    if (rsa->n)
+        BN_free(rsa->n);
+    rsa->n = bn;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject *rsa_private_encrypt(RSA *rsa, PyObject *from, int padding) {
+    const void *fbuf;
+    void *tbuf;
+    int flen, tlen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+        return NULL;
+
+    if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+        PyErr_SetString(PyExc_MemoryError, "rsa_private_encrypt");
+        return NULL;
+    }
+    tlen = RSA_private_encrypt(flen, (unsigned char *)fbuf, 
+        (unsigned char *)tbuf, rsa, padding);
+    if (tlen == -1) {
+        PyMem_Free(tbuf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+    PyMem_Free(tbuf);
+    return ret;
+}
+
+PyObject *rsa_public_decrypt(RSA *rsa, PyObject *from, int padding) {
+    const void *fbuf;
+    void *tbuf;
+    int flen, tlen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+        return NULL;
+
+    if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+        PyErr_SetString(PyExc_MemoryError, "rsa_public_decrypt");
+        return NULL;
+    }
+    tlen = RSA_public_decrypt(flen, (unsigned char *)fbuf, 
+        (unsigned char *)tbuf, rsa, padding);
+    if (tlen == -1) {
+        PyMem_Free(tbuf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+    PyMem_Free(tbuf);
+    return ret;
+}
+
+PyObject *rsa_public_encrypt(RSA *rsa, PyObject *from, int padding) {
+    const void *fbuf;
+    void *tbuf;
+    int flen, tlen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+        return NULL;
+
+    if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+        PyErr_SetString(PyExc_MemoryError, "rsa_public_encrypt");
+        return NULL;
+    }
+    tlen = RSA_public_encrypt(flen, (unsigned char *)fbuf, 
+        (unsigned char *)tbuf, rsa, padding);
+    if (tlen == -1) {
+        PyMem_Free(tbuf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+    PyMem_Free(tbuf);
+    return ret;
+}
+
+PyObject *rsa_private_decrypt(RSA *rsa, PyObject *from, int padding) {
+    const void *fbuf;
+    void *tbuf;
+    int flen, tlen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+        return NULL;
+
+    if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+        PyErr_SetString(PyExc_MemoryError, "rsa_private_decrypt");
+        return NULL;
+    }
+    tlen = RSA_private_decrypt(flen, (unsigned char *)fbuf, 
+        (unsigned char *)tbuf, rsa, padding);
+    if (tlen == -1) {
+        PyMem_Free(tbuf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+    PyMem_Free(tbuf);
+    return ret;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090708fL
+PyObject *rsa_padding_add_pkcs1_pss(RSA *rsa, PyObject *digest, EVP_MD *hash, int salt_length) {
+    const void *dbuf;
+    unsigned char *tbuf;
+    int dlen, result, tlen;
+    PyObject *ret;
+
+    if (m2_PyObject_AsReadBufferInt(digest, &dbuf, &dlen) == -1)
+        return NULL;
+
+    tlen = RSA_size(rsa); 
+
+    if (!(tbuf = OPENSSL_malloc(tlen))) {
+        PyErr_SetString(PyExc_MemoryError, "rsa_padding_add_pkcs1_pss");
+        return NULL;
+    }
+    result = RSA_padding_add_PKCS1_PSS(
+        rsa,
+        tbuf,
+        (unsigned char *)dbuf,
+        hash,
+        salt_length);
+
+    if (result == -1) {
+        OPENSSL_cleanse(tbuf, tlen);
+        OPENSSL_free(tbuf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+    OPENSSL_cleanse(tbuf, tlen);
+    OPENSSL_free(tbuf);
+    return ret;
+}
+
+int rsa_verify_pkcs1_pss(RSA *rsa, PyObject *digest, PyObject *signature, EVP_MD *hash, int salt_length) {
+    const void *dbuf;
+    const void *sbuf;
+    int dlen, slen, ret;
+
+    if (m2_PyObject_AsReadBufferInt(digest, &dbuf, &dlen) == -1) {
+        return 0;
+    }
+
+    if (m2_PyObject_AsReadBufferInt(signature, &sbuf, &slen) == -1) {
+        return 0;
+    }
+
+    ret = RSA_verify_PKCS1_PSS(
+        rsa,
+        (unsigned char *)dbuf,
+        hash,
+        (unsigned char *)sbuf,
+        salt_length);
+
+    return ret;
+}
+#endif
+
+PyObject *rsa_sign(RSA *rsa, PyObject *py_digest_string, int method_type) {
+    int digest_len = 0;
+    int buf_len = 0;
+    int ret = 0;
+    unsigned int real_buf_len = 0;
+    char *digest_string = NULL;
+    unsigned char * sign_buf = NULL;
+    PyObject *signature; 
+    
+    ret = m2_PyString_AsStringAndSizeInt(py_digest_string, &digest_string,
+                                         &digest_len); 
+    if (ret == -1) {
+        /* PyString_AsStringAndSize raises the correct exceptions. */
+        return NULL;
+    }
+    
+    buf_len = RSA_size(rsa);
+    sign_buf = (unsigned char *)PyMem_Malloc(buf_len);
+    ret = RSA_sign(method_type, (const unsigned char *)digest_string, digest_len, 
+                   sign_buf, &real_buf_len, rsa);
+   
+    if (!ret) {
+        PyMem_Free(sign_buf);
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    signature =  PyString_FromStringAndSize((const char*) sign_buf, buf_len);
+    PyMem_Free(sign_buf);
+    return signature;
+}     
+int rsa_verify(RSA *rsa, PyObject *py_verify_string, PyObject* py_sign_string, int method_type){
+    int ret = 0;
+    char * sign_string = NULL;
+    char * verify_string = NULL;
+    int verify_len = 0;
+    int sign_len = 0;
+
+    ret = m2_PyString_AsStringAndSizeInt(py_verify_string, &verify_string,
+                                         &verify_len);
+    if (ret == -1) {
+        /* PyString_AsStringAndSize raises the correct exceptions. */
+        return 0;
+    }
+    ret = m2_PyString_AsStringAndSizeInt(py_sign_string, &sign_string,
+                                         &sign_len);
+    if (ret == -1) {
+        return 0;
+    }
+
+    ret = RSA_verify(method_type, (unsigned char *) verify_string,
+                     verify_len, (unsigned char *) sign_string,
+                     sign_len, rsa);
+    if (!ret) {
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+    } 
+    return ret;
+}
+
+void genrsa_callback(int p, int n, void *arg) {
+    PyObject *argv, *ret, *cbfunc;
+
+    cbfunc = (PyObject *)arg; 
+    argv = Py_BuildValue("(ii)", p, n);
+    ret = PyEval_CallObject(cbfunc, argv);
+    PyErr_Clear();
+    Py_DECREF(argv);
+    Py_XDECREF(ret);
+}
+
+RSA *rsa_generate_key(int bits, unsigned long e, PyObject *pyfunc) {
+    RSA *rsa;
+
+    Py_INCREF(pyfunc);
+    rsa = RSA_generate_key(bits, e, genrsa_callback, (void *)pyfunc);
+    Py_DECREF(pyfunc);
+    if (!rsa) 
+        PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+    return rsa;
+}
+
+int rsa_type_check(RSA *rsa) {
+    return 1;
+}
+
+int rsa_check_pub_key(RSA *rsa) {
+    return (rsa->e) && (rsa->n);
+}
+%}
+
+%threadallow rsa_write_key_der;
+%inline %{
+int rsa_write_key_der(RSA *rsa, BIO *bio) {
+    return i2d_RSAPrivateKey_bio(bio, rsa);
+}
+%}
+
diff --git a/SWIG/_ssl.i b/SWIG/_ssl.i
new file mode 100644 (file)
index 0000000..2373ff2
--- /dev/null
@@ -0,0 +1,739 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+**
+** Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+**
+*/
+/* $Id: _ssl.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <pythread.h>
+#include <openssl/bio.h>
+#include <openssl/dh.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+%}
+
+%apply Pointer NONNULL { SSL_CTX * };
+%apply Pointer NONNULL { SSL * };
+%apply Pointer NONNULL { SSL_CIPHER * };
+%apply Pointer NONNULL { STACK_OF(SSL_CIPHER) * };
+%apply Pointer NONNULL { STACK_OF(X509) * };
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { DH * };
+%apply Pointer NONNULL { RSA * };
+%apply Pointer NONNULL { EVP_PKEY *};
+%apply Pointer NONNULL { PyObject *pyfunc };
+
+%rename(ssl_get_ciphers) SSL_get_ciphers;
+extern STACK_OF(SSL_CIPHER) *SSL_get_ciphers(const SSL *ssl);
+
+%rename(ssl_get_version) SSL_get_version;
+extern const char *SSL_get_version(CONST SSL *);
+%rename(ssl_get_error) SSL_get_error;
+extern int SSL_get_error(CONST SSL *, int);
+%rename(ssl_get_state) SSL_state_string;
+extern const char *SSL_state_string(const SSL *);
+%rename(ssl_get_state_v) SSL_state_string_long;
+extern const char *SSL_state_string_long(const SSL *);
+%rename(ssl_get_alert_type) SSL_alert_type_string;
+extern const char *SSL_alert_type_string(int);
+%rename(ssl_get_alert_type_v) SSL_alert_type_string_long;
+extern const char *SSL_alert_type_string_long(int);
+%rename(ssl_get_alert_desc) SSL_alert_desc_string;
+extern const char *SSL_alert_desc_string(int);
+%rename(ssl_get_alert_desc_v) SSL_alert_desc_string_long;
+extern const char *SSL_alert_desc_string_long(int);
+
+%rename(sslv2_method) SSLv2_method;
+extern SSL_METHOD *SSLv2_method(void);
+%rename(sslv3_method) SSLv3_method;
+extern SSL_METHOD *SSLv3_method(void);
+%rename(sslv23_method) SSLv23_method;
+extern SSL_METHOD *SSLv23_method(void);
+%rename(tlsv1_method) TLSv1_method;
+extern SSL_METHOD *TLSv1_method(void);
+
+%rename(ssl_ctx_new) SSL_CTX_new;
+extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
+%rename(ssl_ctx_free) SSL_CTX_free;
+extern void SSL_CTX_free(SSL_CTX *);
+%rename(ssl_ctx_set_verify_depth) SSL_CTX_set_verify_depth;
+extern void SSL_CTX_set_verify_depth(SSL_CTX *, int);
+%rename(ssl_ctx_get_verify_depth) SSL_CTX_get_verify_depth;
+extern int SSL_CTX_get_verify_depth(CONST SSL_CTX *);
+%rename(ssl_ctx_get_verify_mode) SSL_CTX_get_verify_mode;
+extern int SSL_CTX_get_verify_mode(CONST SSL_CTX *);
+%rename(ssl_ctx_set_cipher_list) SSL_CTX_set_cipher_list;
+extern int SSL_CTX_set_cipher_list(SSL_CTX *, const char *);
+%rename(ssl_ctx_add_session) SSL_CTX_add_session;
+extern int SSL_CTX_add_session(SSL_CTX *, SSL_SESSION *);
+%rename(ssl_ctx_remove_session) SSL_CTX_remove_session;
+extern int SSL_CTX_remove_session(SSL_CTX *, SSL_SESSION *);
+%rename(ssl_ctx_set_session_timeout) SSL_CTX_set_timeout;
+extern long SSL_CTX_set_timeout(SSL_CTX *, long);
+%rename(ssl_ctx_get_session_timeout) SSL_CTX_get_timeout;
+extern long SSL_CTX_get_timeout(CONST SSL_CTX *);
+%rename(ssl_ctx_get_cert_store) SSL_CTX_get_cert_store;
+extern X509_STORE *SSL_CTX_get_cert_store(CONST SSL_CTX *);
+
+%rename(bio_new_ssl) BIO_new_ssl;
+extern BIO *BIO_new_ssl(SSL_CTX *, int);
+
+%rename(ssl_new) SSL_new;
+extern SSL *SSL_new(SSL_CTX *);
+%rename(ssl_free) SSL_free;
+%threadallow SSL_free;
+extern void SSL_free(SSL *);
+%rename(ssl_dup) SSL_dup;
+extern SSL *SSL_dup(SSL *);
+%rename(ssl_set_bio) SSL_set_bio;
+extern void SSL_set_bio(SSL *, BIO *, BIO *);
+%rename(ssl_set_accept_state) SSL_set_accept_state;
+extern void SSL_set_accept_state(SSL *);
+%rename(ssl_set_connect_state) SSL_set_connect_state;
+extern void SSL_set_connect_state(SSL *);
+%rename(ssl_get_shutdown) SSL_get_shutdown;
+extern int SSL_get_shutdown(CONST SSL *);
+%rename(ssl_set_shutdown) SSL_set_shutdown;
+extern void SSL_set_shutdown(SSL *, int);
+%rename(ssl_shutdown) SSL_shutdown;
+%threadallow SSL_shutdown;
+extern int SSL_shutdown(SSL *);
+%rename(ssl_clear) SSL_clear;
+extern int SSL_clear(SSL *);
+%rename(ssl_do_handshake) SSL_do_handshake;
+%threadallow SSL_do_handshake;
+extern int SSL_do_handshake(SSL *);
+%rename(ssl_renegotiate) SSL_renegotiate;
+%threadallow SSL_renegotiate;
+extern int SSL_renegotiate(SSL *);
+%rename(ssl_pending) SSL_pending;
+extern int SSL_pending(CONST SSL *);
+
+%rename(ssl_get_peer_cert) SSL_get_peer_certificate;
+extern X509 *SSL_get_peer_certificate(CONST SSL *);
+%rename(ssl_get_current_cipher) SSL_get_current_cipher;
+extern SSL_CIPHER *SSL_get_current_cipher(CONST SSL *);
+%rename(ssl_get_verify_mode) SSL_get_verify_mode;
+extern int SSL_get_verify_mode(CONST SSL *);
+%rename(ssl_get_verify_depth) SSL_get_verify_depth;
+extern int SSL_get_verify_depth(CONST SSL *);
+%rename(ssl_get_verify_result) SSL_get_verify_result;
+extern long SSL_get_verify_result(CONST SSL *);
+%rename(ssl_get_ssl_ctx) SSL_get_SSL_CTX;
+extern SSL_CTX *SSL_get_SSL_CTX(CONST SSL *);
+%rename(ssl_get_default_session_timeout) SSL_get_default_timeout;
+extern long SSL_get_default_timeout(CONST SSL *);
+
+%rename(ssl_set_cipher_list) SSL_set_cipher_list;
+extern int SSL_set_cipher_list(SSL *, const char *);
+%rename(ssl_get_cipher_list) SSL_get_cipher_list;
+extern const char *SSL_get_cipher_list(CONST SSL *, int);
+
+%rename(ssl_cipher_get_name) SSL_CIPHER_get_name;
+extern const char *SSL_CIPHER_get_name(CONST SSL_CIPHER *);
+%rename(ssl_cipher_get_version) SSL_CIPHER_get_version;
+extern char *SSL_CIPHER_get_version(CONST SSL_CIPHER *);
+
+%rename(ssl_get_session) SSL_get_session;
+extern SSL_SESSION *SSL_get_session(CONST SSL *);
+%rename(ssl_get1_session) SSL_get1_session;
+extern SSL_SESSION *SSL_get1_session(SSL *);
+%rename(ssl_set_session) SSL_set_session;
+extern int SSL_set_session(SSL *, SSL_SESSION *);
+%rename(ssl_session_free) SSL_SESSION_free;
+extern void SSL_SESSION_free(SSL_SESSION *);
+%rename(ssl_session_print) SSL_SESSION_print;
+%threadallow SSL_SESSION_print;
+extern int SSL_SESSION_print(BIO *, CONST SSL_SESSION *);
+%rename(ssl_session_set_timeout) SSL_SESSION_set_timeout;
+extern long SSL_SESSION_set_timeout(SSL_SESSION *, long);
+%rename(ssl_session_get_timeout) SSL_SESSION_get_timeout;
+extern long SSL_SESSION_get_timeout(CONST SSL_SESSION *);
+
+%constant int ssl_error_none              = SSL_ERROR_NONE;
+%constant int ssl_error_ssl               = SSL_ERROR_SSL;
+%constant int ssl_error_want_read         = SSL_ERROR_WANT_READ;
+%constant int ssl_error_want_write        = SSL_ERROR_WANT_WRITE;
+%constant int ssl_error_want_x509_lookup  = SSL_ERROR_WANT_X509_LOOKUP;
+%constant int ssl_error_syscall           = SSL_ERROR_SYSCALL;
+%constant int ssl_error_zero_return       = SSL_ERROR_ZERO_RETURN;
+%constant int ssl_error_want_connect      = SSL_ERROR_WANT_CONNECT;
+
+%constant int SSL_VERIFY_NONE                 = 0x00;
+%constant int SSL_VERIFY_PEER                 = 0x01;
+%constant int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02;
+%constant int SSL_VERIFY_CLIENT_ONCE          = 0x04;
+
+%constant int SSL_ST_CONNECT                  = 0x1000;
+%constant int SSL_ST_ACCEPT                   = 0x2000;
+%constant int SSL_ST_MASK                     = 0x0FFF;
+%constant int SSL_ST_INIT                     = (SSL_ST_CONNECT|SSL_ST_ACCEPT);
+%constant int SSL_ST_BEFORE                   = 0x4000;
+%constant int SSL_ST_OK                       = 0x03;
+%constant int SSL_ST_RENEGOTIATE              = (0x04|SSL_ST_INIT);
+
+%constant int SSL_CB_LOOP                     = 0x01;
+%constant int SSL_CB_EXIT                     = 0x02;
+%constant int SSL_CB_READ                     = 0x04;
+%constant int SSL_CB_WRITE                    = 0x08;
+%constant int SSL_CB_ALERT                    = 0x4000; /* used in callback */
+%constant int SSL_CB_READ_ALERT               = (SSL_CB_ALERT|SSL_CB_READ);
+%constant int SSL_CB_WRITE_ALERT              = (SSL_CB_ALERT|SSL_CB_WRITE);
+%constant int SSL_CB_ACCEPT_LOOP              = (SSL_ST_ACCEPT|SSL_CB_LOOP);
+%constant int SSL_CB_ACCEPT_EXIT              = (SSL_ST_ACCEPT|SSL_CB_EXIT);
+%constant int SSL_CB_CONNECT_LOOP             = (SSL_ST_CONNECT|SSL_CB_LOOP);
+%constant int SSL_CB_CONNECT_EXIT             = (SSL_ST_CONNECT|SSL_CB_EXIT);
+%constant int SSL_CB_HANDSHAKE_START          = 0x10;
+%constant int SSL_CB_HANDSHAKE_DONE           = 0x20;
+
+%constant int SSL_SENT_SHUTDOWN              = 1;
+%constant int SSL_RECEIVED_SHUTDOWN          = 2;
+
+%constant int SSL_SESS_CACHE_OFF            = 0x000;
+%constant int SSL_SESS_CACHE_CLIENT         = 0x001;
+%constant int SSL_SESS_CACHE_SERVER         = 0x002;
+%constant int SSL_SESS_CACHE_BOTH           = (SSL_SESS_CACHE_CLIENT|SSL_SESS_CACHE_SERVER);
+
+%constant int SSL_OP_ALL                  = 0x00000FFFL;
+
+%constant int SSL_OP_NO_SSLv2             = 0x01000000L;
+%constant int SSL_OP_NO_SSLv3             = 0x02000000L;
+%constant int SSL_OP_NO_TLSv1             = 0x04000000L;
+%constant int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = 0x00000800L;
+
+%constant int SSL_MODE_ENABLE_PARTIAL_WRITE = SSL_MODE_ENABLE_PARTIAL_WRITE;
+%constant int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = SSL_MODE_ENABLE_PARTIAL_WRITE;
+%constant int SSL_MODE_AUTO_RETRY           = SSL_MODE_AUTO_RETRY;
+
+%inline %{
+static PyObject *_ssl_err;
+
+void ssl_init(PyObject *ssl_err) {
+    SSL_library_init();
+    SSL_load_error_strings();
+    Py_INCREF(ssl_err);
+    _ssl_err = ssl_err;
+}
+
+void ssl_ctx_passphrase_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+    SSL_CTX_set_default_passwd_cb(ctx, passphrase_callback);
+    SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)pyfunc);
+    Py_INCREF(pyfunc);
+}
+
+int ssl_ctx_use_x509(SSL_CTX *ctx, X509 *x) {
+    int i;
+    
+    if (!(i = SSL_CTX_use_certificate(ctx, x))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+
+}
+
+int ssl_ctx_use_cert(SSL_CTX *ctx, char *file) {
+    int i;
+    
+    if (!(i = SSL_CTX_use_certificate_file(ctx, file, SSL_FILETYPE_PEM))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+}
+
+int ssl_ctx_use_cert_chain(SSL_CTX *ctx, char *file) {
+    int i;
+
+    if (!(i = SSL_CTX_use_certificate_chain_file(ctx, file))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+}
+
+
+int ssl_ctx_use_privkey(SSL_CTX *ctx, char *file) {
+    int i;
+    
+    if (!(i = SSL_CTX_use_PrivateKey_file(ctx, file, SSL_FILETYPE_PEM))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+}
+
+int ssl_ctx_use_rsa_privkey(SSL_CTX *ctx, RSA *rsakey) {
+    int i;
+
+    if (!(i = SSL_CTX_use_RSAPrivateKey(ctx, rsakey))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+}
+
+int ssl_ctx_use_pkey_privkey(SSL_CTX *ctx, EVP_PKEY *pkey) {
+    int i;
+
+    if (!(i = SSL_CTX_use_PrivateKey(ctx, pkey))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return i;
+}
+
+
+int ssl_ctx_check_privkey(SSL_CTX *ctx) {
+    int ret;
+    
+    if (!(ret = SSL_CTX_check_private_key(ctx))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return ret;
+}
+
+void ssl_ctx_set_client_CA_list_from_file(SSL_CTX *ctx, const char *ca_file) {
+    SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(ca_file));
+}
+
+void ssl_ctx_set_verify_default(SSL_CTX *ctx, int mode) {
+    SSL_CTX_set_verify(ctx, mode, NULL);
+}
+
+void ssl_ctx_set_verify(SSL_CTX *ctx, int mode, PyObject *pyfunc) {
+    Py_XDECREF(ssl_verify_cb_func);
+    Py_INCREF(pyfunc);
+    ssl_verify_cb_func = pyfunc;
+    SSL_CTX_set_verify(ctx, mode, ssl_verify_callback);
+}
+
+int ssl_ctx_set_session_id_context(SSL_CTX *ctx, PyObject *sid_ctx) {
+    const void *buf;
+    int len;
+
+    if (m2_PyObject_AsReadBufferInt(sid_ctx, &buf, &len) == -1)
+        return -1;
+
+    return SSL_CTX_set_session_id_context(ctx, buf, len);
+}
+
+void ssl_ctx_set_info_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+    Py_XDECREF(ssl_info_cb_func);
+    Py_INCREF(pyfunc);
+    ssl_info_cb_func = pyfunc;
+    SSL_CTX_set_info_callback(ctx, ssl_info_callback);
+}
+
+long ssl_ctx_set_tmp_dh(SSL_CTX *ctx, DH* dh) {
+    return SSL_CTX_set_tmp_dh(ctx, dh);
+}
+
+void ssl_ctx_set_tmp_dh_callback(SSL_CTX *ctx,  PyObject *pyfunc) {
+    Py_XDECREF(ssl_set_tmp_dh_cb_func);
+    Py_INCREF(pyfunc);
+    ssl_set_tmp_dh_cb_func = pyfunc;
+    SSL_CTX_set_tmp_dh_callback(ctx, ssl_set_tmp_dh_callback);
+}
+
+long ssl_ctx_set_tmp_rsa(SSL_CTX *ctx, RSA* rsa) {
+    return SSL_CTX_set_tmp_rsa(ctx, rsa);
+}
+
+void ssl_ctx_set_tmp_rsa_callback(SSL_CTX *ctx,  PyObject *pyfunc) {
+    Py_XDECREF(ssl_set_tmp_rsa_cb_func);
+    Py_INCREF(pyfunc);
+    ssl_set_tmp_rsa_cb_func = pyfunc;
+    SSL_CTX_set_tmp_rsa_callback(ctx, ssl_set_tmp_rsa_callback);
+}
+
+int ssl_ctx_load_verify_locations(SSL_CTX *ctx, const char *cafile, const char *capath) {
+    return SSL_CTX_load_verify_locations(ctx, cafile, capath);
+}
+
+/* SSL_CTX_set_options is a macro. */
+long ssl_ctx_set_options(SSL_CTX *ctx, long op) {
+    return SSL_CTX_set_options(ctx, op);
+}
+
+int bio_set_ssl(BIO *bio, SSL *ssl, int flag) {
+    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+    return BIO_ctrl(bio, BIO_C_SET_SSL, flag, (char *)ssl);
+}
+
+long ssl_set_mode(SSL *ssl, long mode) {
+    return SSL_set_mode(ssl, mode);
+}
+
+long ssl_get_mode(SSL *ssl) {
+    return SSL_get_mode(ssl);
+}
+
+void ssl_set_client_CA_list_from_file(SSL *ssl, const char *ca_file) {
+    SSL_set_client_CA_list(ssl, SSL_load_client_CA_file(ca_file));
+}
+
+void ssl_set_client_CA_list_from_context(SSL *ssl, SSL_CTX *ctx) {
+    SSL_set_client_CA_list(ssl, SSL_CTX_get_client_CA_list(ctx));
+}
+
+int ssl_set_session_id_context(SSL *ssl, PyObject *sid_ctx) {
+    const void *buf;
+    int len;
+
+    if (m2_PyObject_AsReadBufferInt(sid_ctx, &buf, &len) == -1)
+        return -1;
+
+    return SSL_set_session_id_context(ssl, buf, len);
+}
+
+int ssl_set_fd(SSL *ssl, int fd) {
+    int ret;
+    
+    if (!(ret = SSL_set_fd(ssl, fd))) {
+        PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+        return -1;
+    }
+    return ret;
+}
+
+PyObject *ssl_accept(SSL *ssl) {
+    PyObject *obj = NULL;
+    int r, err;
+
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_accept(ssl);
+    Py_END_ALLOW_THREADS
+
+
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            obj = PyInt_FromLong((long)1);
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+            obj = PyInt_FromLong((long)0);
+            break;
+        case SSL_ERROR_SSL:
+            PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            obj = NULL;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+            obj = NULL;
+            break;
+    }
+
+
+    return obj;
+}
+
+PyObject *ssl_connect(SSL *ssl) {
+    PyObject *obj = NULL;
+    int r, err;
+
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_connect(ssl);
+    Py_END_ALLOW_THREADS
+
+    
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            obj = PyInt_FromLong((long)1);
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+            obj = PyInt_FromLong((long)0);
+            break;
+        case SSL_ERROR_SSL:
+            PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            obj = NULL;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+            obj = NULL;
+            break;
+    }
+    
+    
+    return obj;
+}
+
+void ssl_set_shutdown1(SSL *ssl, int mode) {
+    SSL_set_shutdown(ssl, mode);
+}
+
+PyObject *ssl_read(SSL *ssl, int num) {
+    PyObject *obj = NULL;
+    void *buf;
+    int r, err;
+
+    if (!(buf = PyMem_Malloc(num))) {
+        PyErr_SetString(PyExc_MemoryError, "ssl_read");
+        return NULL;
+    }
+
+
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_read(ssl, buf, num);
+    Py_END_ALLOW_THREADS
+
+
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            buf = PyMem_Realloc(buf, r);
+            obj = PyString_FromStringAndSize(buf, r);
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            Py_INCREF(Py_None);
+            obj = Py_None;
+            break;
+        case SSL_ERROR_SSL:
+            PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            obj = NULL;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+            obj = NULL;
+            break;
+    }
+    PyMem_Free(buf);
+
+
+    return obj;
+}
+
+PyObject *ssl_read_nbio(SSL *ssl, int num) {
+    PyObject *obj = NULL;
+    void *buf;
+    int r, err;
+
+
+    if (!(buf = PyMem_Malloc(num))) {
+        PyErr_SetString(PyExc_MemoryError, "ssl_read");
+        return NULL;
+    }
+    
+    
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_read(ssl, buf, num);
+    Py_END_ALLOW_THREADS
+    
+    
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            buf = PyMem_Realloc(buf, r);
+            obj = PyString_FromStringAndSize(buf, r);
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            Py_INCREF(Py_None);
+            obj = Py_None;
+            break;
+        case SSL_ERROR_SSL:
+            PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            obj = NULL;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+            obj = NULL;
+            break;
+    }
+    PyMem_Free(buf);
+    
+    
+    return obj;
+}
+
+int ssl_write(SSL *ssl, PyObject *blob) {
+    const void *buf;
+    int len, r, err, ret;
+
+
+    if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) {
+        return -1;
+    }
+
+    
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_write(ssl, buf, len);
+    Py_END_ALLOW_THREADS
+
+
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            ret = r;
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            ret = -1;
+            break;
+        case SSL_ERROR_SSL:
+            PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            ret = -1;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+        default:
+            ret = -1;
+    }
+    
+    
+    return ret;
+}
+
+int ssl_write_nbio(SSL *ssl, PyObject *blob) {
+    const void *buf;
+    int len, r, err, ret;
+
+
+    if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) {
+        return -1;
+    }
+
+    
+    Py_BEGIN_ALLOW_THREADS
+    r = SSL_write(ssl, buf, len);
+    Py_END_ALLOW_THREADS
+    
+    
+    switch (SSL_get_error(ssl, r)) {
+        case SSL_ERROR_NONE:
+        case SSL_ERROR_ZERO_RETURN:
+            ret = r;
+            break;
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            ret = -1;
+            break;
+        case SSL_ERROR_SSL:
+            ret = -1;
+            break;
+        case SSL_ERROR_SYSCALL:
+            err = ERR_get_error();
+            if (err)
+                PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+            else if (r == 0)
+                PyErr_SetString(_ssl_err, "unexpected eof");
+            else if (r == -1)
+                PyErr_SetFromErrno(_ssl_err);
+        default:
+            ret = -1;
+    }
+    
+    
+    return ret;
+}
+
+int ssl_cipher_get_bits(SSL_CIPHER *c) {
+    return SSL_CIPHER_get_bits(c, NULL);
+}
+
+int sk_ssl_cipher_num(STACK_OF(SSL_CIPHER) *stack) {
+    return sk_SSL_CIPHER_num(stack);
+}
+
+SSL_CIPHER *sk_ssl_cipher_value(STACK_OF(SSL_CIPHER) *stack, int idx) {
+    return sk_SSL_CIPHER_value(stack, idx);
+}
+
+STACK_OF(X509) *ssl_get_peer_cert_chain(SSL *ssl) {
+    return SSL_get_peer_cert_chain(ssl);
+}
+
+int sk_x509_num(STACK_OF(X509) *stack) {
+    return sk_X509_num(stack);
+}
+
+X509 *sk_x509_value(STACK_OF(X509) *stack, int idx) {
+    return sk_X509_value(stack, idx);
+}
+%}
+
+%threadallow i2d_ssl_session;
+%inline %{
+void i2d_ssl_session(BIO *bio, SSL_SESSION *sess) {
+    i2d_SSL_SESSION_bio(bio, sess);
+}
+%}
+
+%threadallow ssl_session_read_pem;
+%inline %{
+SSL_SESSION *ssl_session_read_pem(BIO *bio) {
+    return PEM_read_bio_SSL_SESSION(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow ssl_session_write_pem;
+%inline %{
+int ssl_session_write_pem(SSL_SESSION *sess, BIO *bio) {
+    return PEM_write_bio_SSL_SESSION(bio, sess);
+}
+
+int ssl_ctx_set_session_cache_mode(SSL_CTX *ctx, int mode)
+{
+    return SSL_CTX_set_session_cache_mode(ctx, mode);
+}
+
+int ssl_ctx_get_session_cache_mode(SSL_CTX *ctx)
+{
+    return SSL_CTX_get_session_cache_mode(ctx);
+}
+
+static long ssl_ctx_set_cache_size(SSL_CTX *ctx, long arg)
+{
+  return SSL_CTX_sess_set_cache_size(ctx, arg);
+}
+
+int ssl_is_init_finished(SSL *ssl)
+{
+  return SSL_is_init_finished(ssl);
+}
+%}
+
diff --git a/SWIG/_threads.i b/SWIG/_threads.i
new file mode 100644 (file)
index 0000000..bb625df
--- /dev/null
@@ -0,0 +1,66 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _threads.i 690 2009-07-22 08:32:43Z heikki $ */
+
+%{
+#include <pythread.h>
+#include <openssl/crypto.h>
+
+#ifdef THREADING
+static PyThread_type_lock lock_cs[CRYPTO_NUM_LOCKS];
+static long lock_count[CRYPTO_NUM_LOCKS];
+static int thread_mode = 0;
+#endif
+
+void threading_locking_callback(int mode, int type, const char *file, int line) {
+#ifdef THREADING
+        if (mode & CRYPTO_LOCK) {
+                PyThread_acquire_lock(lock_cs[type], WAIT_LOCK);
+                lock_count[type]++;
+        } else {
+                PyThread_release_lock(lock_cs[type]);
+                lock_count[type]--;
+        }
+#endif
+}
+
+unsigned long threading_id_callback(void) {
+#ifdef THREADING
+    return (unsigned long)PyThread_get_thread_ident();
+#else
+    return (unsigned long)0;
+#endif
+}
+%}
+
+%inline %{
+void threading_init(void) {
+#ifdef THREADING
+    int i;
+    if (!thread_mode) {
+        for (i=0; i<CRYPTO_NUM_LOCKS; i++) {
+            lock_count[i]=0;
+            lock_cs[i]=PyThread_allocate_lock();
+        }
+        CRYPTO_set_id_callback(threading_id_callback);
+        CRYPTO_set_locking_callback(threading_locking_callback);
+    }
+    thread_mode = 1;
+#endif
+}
+
+void threading_cleanup(void) {
+#ifdef THREADING
+    int i;
+    if (thread_mode) {
+        CRYPTO_set_locking_callback(NULL);
+        for (i=0; i<CRYPTO_NUM_LOCKS; i++) {
+            lock_count[i]=0;
+            PyThread_release_lock(lock_cs[i]);
+            PyThread_free_lock(lock_cs[i]);
+        }
+    }
+    thread_mode = 0;
+#endif
+}
+%}
+
diff --git a/SWIG/_util.i b/SWIG/_util.i
new file mode 100644 (file)
index 0000000..522b8c8
--- /dev/null
@@ -0,0 +1,57 @@
+/* Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _util.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <openssl/x509v3.h>
+%}
+
+%inline %{
+static PyObject *_util_err;
+
+void util_init(PyObject *util_err) {
+    Py_INCREF(util_err);
+    _util_err = util_err;
+}
+    
+PyObject *util_hex_to_string(PyObject *blob) {
+    PyObject *obj;
+    const void *buf;
+    char *ret;
+    Py_ssize_t len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+        return NULL;
+
+    ret = hex_to_string((unsigned char *)buf, len);
+    if (!ret) {
+        PyErr_SetString(_util_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    obj = PyString_FromString(ret);
+    OPENSSL_free(ret);
+    return obj;
+}
+
+PyObject *util_string_to_hex(PyObject *blob) {
+    PyObject *obj;
+    const void *buf;
+    unsigned char *ret;
+    Py_ssize_t len0;
+    long len;
+
+    if (PyObject_AsReadBuffer(blob, &buf, &len0) == -1)
+        return NULL;
+
+    len = len0;
+    ret = string_to_hex((char *)buf, &len);
+    if (ret == NULL) {
+        PyErr_SetString(_util_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    obj = PyString_FromStringAndSize((char*)ret, len);
+    OPENSSL_free(ret);
+    return obj;
+}
+%}
diff --git a/SWIG/_x509.i b/SWIG/_x509.i
new file mode 100644 (file)
index 0000000..0471f68
--- /dev/null
@@ -0,0 +1,674 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.  */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+**
+** Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+**
+*/
+/* $Id: _x509.i 721 2010-02-13 06:30:33Z heikki $   */
+
+%{
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { X509 * };
+%apply Pointer NONNULL { X509_CRL * };
+%apply Pointer NONNULL { X509_REQ * };
+%apply Pointer NONNULL { X509_NAME * };
+%apply Pointer NONNULL { X509_NAME_ENTRY * };
+%apply Pointer NONNULL { EVP_PKEY * };
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%rename(x509_check_ca) X509_check_ca;
+extern int X509_check_ca(X509 *);
+#endif
+
+%rename(x509_new) X509_new;
+extern X509 *X509_new( void );
+%rename(x509_dup) X509_dup;
+extern X509 *X509_dup(X509 *);
+%rename(x509_free) X509_free;
+extern void X509_free(X509 *);
+%rename(x509_crl_free) X509_CRL_free;
+extern void X509_CRL_free(X509_CRL *);
+%rename(x509_crl_new) X509_CRL_new;
+extern X509_CRL * X509_CRL_new();
+
+%rename(x509_print) X509_print;
+%threadallow X509_print;
+extern int X509_print(BIO *, X509 *);
+%rename(x509_crl_print) X509_CRL_print;
+%threadallow X509_CRL_print;
+extern int X509_CRL_print(BIO *, X509_CRL *);
+
+%rename(x509_get_serial_number) X509_get_serialNumber;
+extern ASN1_INTEGER *X509_get_serialNumber(X509 *);
+%rename(x509_set_serial_number) X509_set_serialNumber;
+extern int X509_set_serialNumber(X509 *, ASN1_INTEGER *);
+%rename(x509_get_pubkey) X509_get_pubkey;
+extern EVP_PKEY *X509_get_pubkey(X509 *);
+%rename(x509_set_pubkey) X509_set_pubkey;
+extern int X509_set_pubkey(X509 *, EVP_PKEY *);
+%rename(x509_get_issuer_name) X509_get_issuer_name;
+extern X509_NAME *X509_get_issuer_name(X509 *);
+%rename(x509_set_issuer_name) X509_set_issuer_name;
+extern int X509_set_issuer_name(X509 *, X509_NAME *);
+%rename(x509_get_subject_name) X509_get_subject_name;
+extern X509_NAME *X509_get_subject_name(X509 *);
+%rename(x509_set_subject_name) X509_set_subject_name;
+extern int X509_set_subject_name(X509 *, X509_NAME *);
+%rename(x509_cmp_current_time) X509_cmp_current_time;
+extern int X509_cmp_current_time(ASN1_UTCTIME *);
+
+                            
+/* From x509.h */
+/* standard trust ids */
+%constant int X509_TRUST_DEFAULT      = -1;
+%constant int X509_TRUST_COMPAT       = 1;
+%constant int X509_TRUST_SSL_CLIENT   = 2;
+%constant int X509_TRUST_SSL_SERVER   = 3;
+%constant int X509_TRUST_EMAIL        = 4;
+%constant int X509_TRUST_OBJECT_SIGN  = 5;
+%constant int X509_TRUST_OCSP_SIGN    = 6;
+%constant int X509_TRUST_OCSP_REQUEST = 7;
+
+/* trust_flags values */
+%constant int X509_TRUST_DYNAMIC      = 1;
+%constant int X509_TRUST_DYNAMIC_NAME = 2;
+
+/* check_trust return codes */
+%constant int X509_TRUST_TRUSTED      = 1;
+%constant int X509_TRUST_REJECTED     = 2;
+%constant int X509_TRUST_UNTRUSTED    = 3;
+
+/* From x509v3.h */
+%constant int X509_PURPOSE_SSL_CLIENT         = 1;
+%constant int X509_PURPOSE_SSL_SERVER         = 2;
+%constant int X509_PURPOSE_NS_SSL_SERVER      = 3;
+%constant int X509_PURPOSE_SMIME_SIGN         = 4;
+%constant int X509_PURPOSE_SMIME_ENCRYPT      = 5;
+%constant int X509_PURPOSE_CRL_SIGN           = 6;
+%constant int X509_PURPOSE_ANY                = 7;
+%constant int X509_PURPOSE_OCSP_HELPER        = 8;
+
+%rename(x509_check_purpose) X509_check_purpose;
+extern int X509_check_purpose(X509 *, int, int);
+%rename(x509_check_trust) X509_check_trust;
+extern int X509_check_trust(X509 *, int, int);
+
+%rename(x509_write_pem) PEM_write_bio_X509;
+%threadallow PEM_write_bio_X509;
+extern int PEM_write_bio_X509(BIO *, X509 *);
+%rename(x509_write_pem_file) PEM_write_X509;
+extern int PEM_write_X509(FILE *, X509 *);
+
+%rename(x509_verify) X509_verify;
+extern int X509_verify(X509 *a, EVP_PKEY *r);
+%rename(x509_get_verify_error) X509_verify_cert_error_string;
+extern const char *X509_verify_cert_error_string(long);
+
+%constant long X509V3_EXT_UNKNOWN_MASK         = (0xfL << 16);
+%constant long X509V3_EXT_DEFAULT              = 0;
+%constant long X509V3_EXT_ERROR_UNKNOWN        = (1L << 16);
+%constant long X509V3_EXT_PARSE_UNKNOWN        = (2L << 16);
+%constant long X509V3_EXT_DUMP_UNKNOWN         = (3L << 16);
+
+%rename(x509_add_ext) X509_add_ext;
+extern int X509_add_ext(X509 *, X509_EXTENSION *, int);
+%rename(x509_get_ext_count) X509_get_ext_count;
+extern int X509_get_ext_count(X509 *);
+%rename(x509_get_ext) X509_get_ext;
+extern X509_EXTENSION *X509_get_ext(X509 *, int);
+%rename(x509_ext_print) X509V3_EXT_print;
+%threadallow X509V3_EXT_print;
+extern int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int);
+
+%rename(x509_name_new) X509_NAME_new;
+extern X509_NAME *X509_NAME_new( void );
+%rename(x509_name_free) X509_NAME_free;
+extern void X509_NAME_free(X509_NAME *);
+%rename(x509_name_print) X509_NAME_print;
+%threadallow X509_NAME_print;
+extern int X509_NAME_print(BIO *, X509_NAME *, int);
+%rename(x509_name_get_entry) X509_NAME_get_entry;
+extern X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *, int);
+%rename(x509_name_entry_count) X509_NAME_entry_count;
+extern int X509_NAME_entry_count(X509_NAME *);
+%rename(x509_name_delete_entry) X509_NAME_delete_entry;
+extern X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int);
+%rename(x509_name_add_entry) X509_NAME_add_entry;
+extern int X509_NAME_add_entry(X509_NAME *, X509_NAME_ENTRY *, int, int);
+%rename(x509_name_add_entry_by_obj) X509_NAME_add_entry_by_OBJ;
+extern int X509_NAME_add_entry_by_OBJ(X509_NAME *, ASN1_OBJECT *, int, unsigned char *, int, int, int );
+%rename(x509_name_add_entry_by_nid) X509_NAME_add_entry_by_NID;
+extern int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, unsigned char *, int, int, int );
+%rename(x509_name_print_ex) X509_NAME_print_ex;
+%threadallow X509_NAME_print_ex;
+extern int X509_NAME_print_ex(BIO *, X509_NAME *, int, unsigned long);
+%rename(x509_name_print_ex_fp) X509_NAME_print_ex_fp;
+extern int X509_NAME_print_ex_fp(FILE *, X509_NAME *, int, unsigned long);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+%rename(x509_name_hash) X509_NAME_hash_old;
+extern unsigned long X509_NAME_hash_old(X509_NAME *);
+#else
+%rename(x509_name_hash) X509_NAME_hash;
+extern unsigned long X509_NAME_hash(X509_NAME *);
+#endif
+
+%rename(x509_name_get_index_by_nid) X509_NAME_get_index_by_NID;
+extern int X509_NAME_get_index_by_NID(X509_NAME *, int, int);
+
+%rename(x509_name_entry_new) X509_NAME_ENTRY_new;
+extern X509_NAME_ENTRY *X509_NAME_ENTRY_new( void );
+%rename(x509_name_entry_free) X509_NAME_ENTRY_free;
+extern void X509_NAME_ENTRY_free( X509_NAME_ENTRY *);
+/*XXX This is probably bogus:*/
+%rename(x509_name_entry_create_by_nid) X509_NAME_ENTRY_create_by_NID;
+extern X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_NID( X509_NAME_ENTRY **, int, int, unsigned char *, int);
+%rename(x509_name_entry_set_object) X509_NAME_ENTRY_set_object;
+extern int X509_NAME_ENTRY_set_object( X509_NAME_ENTRY *, ASN1_OBJECT *);
+%rename(x509_name_entry_get_object) X509_NAME_ENTRY_get_object;
+extern ASN1_OBJECT *X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *);
+%rename(x509_name_entry_get_data) X509_NAME_ENTRY_get_data;
+extern ASN1_STRING *X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *);
+
+%typemap(in) (CONST unsigned char *, int) { 
+    if (PyString_Check($input)) {
+        Py_ssize_t len;
+
+        $1 = (unsigned char *)PyString_AsString($input); 
+        len = PyString_Size($input);
+        if (len > INT_MAX) {
+            PyErr_SetString(PyExc_ValueError, "object too large");
+            return NULL;
+        }
+        $2 = len;
+    } else {
+        PyErr_SetString(PyExc_TypeError, "expected string");
+        return NULL;
+    }
+}
+%rename(x509_name_entry_set_data) X509_NAME_ENTRY_set_data;
+extern int X509_NAME_ENTRY_set_data(X509_NAME_ENTRY *, int, CONST unsigned char *, int);
+%typemap(in) (CONST unsigned char *, int);
+
+%rename(x509_req_new) X509_REQ_new;
+extern X509_REQ * X509_REQ_new();
+%rename(x509_req_free) X509_REQ_free;
+extern void X509_REQ_free(X509_REQ *);
+%rename(x509_req_print) X509_REQ_print;
+%threadallow X509_REQ_print;
+extern int X509_REQ_print(BIO *, X509_REQ *);
+
+%rename(x509_req_get_pubkey) X509_REQ_get_pubkey;
+extern EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *);
+%rename(x509_req_set_pubkey) X509_REQ_set_pubkey;
+extern int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *);
+%rename(x509_req_set_subject_name) X509_REQ_set_subject_name;
+extern int X509_REQ_set_subject_name(X509_REQ *, X509_NAME *);
+
+%rename(x509_req_verify) X509_REQ_verify;
+extern int X509_REQ_verify(X509_REQ *, EVP_PKEY *);
+%rename(x509_req_sign) X509_REQ_sign;
+extern int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *);
+
+%rename(i2d_x509_bio) i2d_X509_bio;
+%threadallow i2d_X509_bio;
+extern int i2d_X509_bio(BIO *, X509 *);
+%rename(i2d_x509_req_bio) i2d_X509_REQ_bio;
+%threadallow i2d_X509_REQ_bio;
+extern int i2d_X509_REQ_bio(BIO *, X509_REQ *);
+
+%rename(x509_store_new) X509_STORE_new;
+extern X509_STORE *X509_STORE_new(void);
+%rename(x509_store_free) X509_STORE_free;
+extern void X509_STORE_free(X509_STORE *);
+%rename(x509_store_add_cert) X509_STORE_add_cert;
+extern int X509_STORE_add_cert(X509_STORE *, X509 *);
+
+%rename(x509_store_ctx_get_current_cert) X509_STORE_CTX_get_current_cert;
+extern X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *);
+%rename(x509_store_ctx_get_error) X509_STORE_CTX_get_error;
+extern int X509_STORE_CTX_get_error(X509_STORE_CTX *);
+%rename(x509_store_ctx_get_error_depth) X509_STORE_CTX_get_error_depth;
+extern int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *);
+%rename(x509_store_ctx_free) X509_STORE_CTX_free;
+extern void X509_STORE_CTX_free(X509_STORE_CTX *);
+%rename(x509_store_ctx_get1_chain) X509_STORE_CTX_get1_chain;
+extern STACK_OF(X509) *X509_STORE_CTX_get1_chain(X509_STORE_CTX *);
+
+%rename(x509_extension_get_critical) X509_EXTENSION_get_critical;
+extern int X509_EXTENSION_get_critical(X509_EXTENSION *);
+%rename(x509_extension_set_critical) X509_EXTENSION_set_critical;
+extern int X509_EXTENSION_set_critical(X509_EXTENSION *, int);
+
+
+%constant int NID_commonName                  = 13;
+%constant int NID_countryName                 = 14;
+%constant int NID_localityName                = 15;
+%constant int NID_stateOrProvinceName         = 16;
+%constant int NID_organizationName            = 17;
+%constant int NID_organizationalUnitName      = 18;
+%constant int NID_serialNumber                = 105;
+%constant int NID_surname                     = 100;
+%constant int NID_givenName                   = 99;
+%constant int NID_pkcs9_emailAddress          = 48;
+
+/* Cribbed from x509_vfy.h. */
+%constant int        X509_V_OK                                      = 0;
+%constant int        X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT           = 2;
+%constant int        X509_V_ERR_UNABLE_TO_GET_CRL                   = 3;
+%constant int        X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE    = 4;
+%constant int        X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE     = 5;
+%constant int        X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY  = 6;
+%constant int        X509_V_ERR_CERT_SIGNATURE_FAILURE              = 7;
+%constant int        X509_V_ERR_CRL_SIGNATURE_FAILURE               = 8;
+%constant int        X509_V_ERR_CERT_NOT_YET_VALID                  = 9;
+%constant int        X509_V_ERR_CERT_HAS_EXPIRED                    = 10;
+%constant int        X509_V_ERR_CRL_NOT_YET_VALID                   = 11;
+%constant int        X509_V_ERR_CRL_HAS_EXPIRED                     = 12;
+%constant int        X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD      = 13;
+%constant int        X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD       = 14;
+%constant int        X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD      = 15;
+%constant int        X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD      = 16;
+%constant int        X509_V_ERR_OUT_OF_MEM                          = 17;
+%constant int        X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT         = 18;
+%constant int        X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN           = 19;
+%constant int        X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY   = 20;
+%constant int        X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE     = 21;
+%constant int        X509_V_ERR_CERT_CHAIN_TOO_LONG                 = 22;
+%constant int        X509_V_ERR_CERT_REVOKED                        = 23;
+%constant int        X509_V_ERR_INVALID_CA                          = 24;
+%constant int        X509_V_ERR_PATH_LENGTH_EXCEEDED                = 25;
+%constant int        X509_V_ERR_INVALID_PURPOSE                     = 26;
+%constant int        X509_V_ERR_CERT_UNTRUSTED                      = 27;
+%constant int        X509_V_ERR_CERT_REJECTED                       = 28;
+%constant int        X509_V_ERR_APPLICATION_VERIFICATION            = 50;
+
+/* x509.h */
+%constant int XN_FLAG_COMPAT = 0;
+%constant int XN_FLAG_SEP_COMMA_PLUS = (1 << 16);
+%constant int XN_FLAG_SEP_CPLUS_SPC = (2 << 16);
+%constant int XN_FLAG_SEP_MULTILINE = (4 << 16);
+%constant int XN_FLAG_DN_REV = (1 << 20);
+%constant int XN_FLAG_FN_LN = (1 << 21);
+%constant int XN_FLAG_SPC_EQ = (1 << 23);
+%constant int XN_FLAG_DUMP_UNKNOWN_FIELDS = (1 << 24);
+%constant int XN_FLAG_FN_ALIGN = (1 << 25);
+%constant int XN_FLAG_ONELINE =(ASN1_STRFLGS_RFC2253 | \
+            ASN1_STRFLGS_ESC_QUOTE | \
+            XN_FLAG_SEP_CPLUS_SPC | \
+            XN_FLAG_SPC_EQ);
+%constant int XN_FLAG_MULTILINE = (ASN1_STRFLGS_ESC_CTRL | \
+            ASN1_STRFLGS_ESC_MSB | \
+            XN_FLAG_SEP_MULTILINE | \
+            XN_FLAG_SPC_EQ | \
+            XN_FLAG_FN_LN | \
+            XN_FLAG_FN_ALIGN);
+%constant int XN_FLAG_RFC2253 = (ASN1_STRFLGS_RFC2253 | \
+            XN_FLAG_SEP_COMMA_PLUS | \
+            XN_FLAG_DN_REV | \
+            XN_FLAG_DUMP_UNKNOWN_FIELDS);
+
+/* Cribbed from rsa.h. */
+%constant int RSA_3                           = 0x3L;
+%constant int RSA_F4                          = 0x10001L;
+
+%inline %{
+static PyObject *_x509_err;
+
+void x509_init(PyObject *x509_err) {
+    Py_INCREF(x509_err);
+    _x509_err = x509_err;
+}
+%}
+
+%threadallow x509_read_pem;
+%inline %{
+X509 *x509_read_pem(BIO *bio) {
+    return PEM_read_bio_X509(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow d2i_x509;
+%inline %{
+X509 *d2i_x509(BIO *bio) {
+    return d2i_X509_bio(bio, NULL);
+}
+%}
+
+%threadallow d2i_x509_req;
+%inline %{
+X509_REQ *d2i_x509_req(BIO *bio) {
+    return d2i_X509_REQ_bio(bio, NULL);
+}
+
+PyObject *i2d_x509(X509 *x)
+{
+    int len;
+    PyObject *ret = NULL;
+    unsigned char *buf = NULL;
+    len = i2d_X509(x, &buf);
+    if (len < 0) {
+        PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+    }
+    else {     
+        ret = PyString_FromStringAndSize((char*)buf, len);
+        OPENSSL_free(buf);
+    }
+    return ret;
+}
+%}
+
+%threadallow x509_req_read_pem;
+%inline %{
+X509_REQ *x509_req_read_pem(BIO *bio) {
+    return PEM_read_bio_X509_REQ(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow x509_req_write_pem;
+%inline %{
+int x509_req_write_pem(BIO *bio, X509_REQ *x) {
+    return PEM_write_bio_X509_REQ(bio, x);
+}
+%}
+
+%threadallow x509_crl_read_pem;
+%inline %{
+X509_CRL *x509_crl_read_pem(BIO *bio) {
+    return PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL);
+}
+
+/* X509_set_version() is a macro. */
+int x509_set_version(X509 *x, long version) {
+    return X509_set_version(x, version);
+}
+
+/* X509_get_version() is a macro. */
+long x509_get_version(X509 *x) {
+    return X509_get_version(x);
+}
+
+/* X509_set_notBefore() is a macro. */
+int x509_set_not_before(X509 *x, ASN1_UTCTIME *tm) {
+    return X509_set_notBefore(x, tm);
+}
+
+/* X509_get_notBefore() is a macro. */
+ASN1_UTCTIME *x509_get_not_before(X509 *x) {
+    return X509_get_notBefore(x);
+}
+
+/* X509_set_notAfter() is a macro. */
+int x509_set_not_after(X509 *x, ASN1_UTCTIME *tm) {
+    return X509_set_notAfter(x, tm);
+}
+
+/* X509_get_notAfter() is a macro. */
+ASN1_UTCTIME *x509_get_not_after(X509 *x) {
+    return X509_get_notAfter(x);
+}
+
+int x509_sign(X509 *x, EVP_PKEY *pkey, EVP_MD *md) {
+    return X509_sign(x, pkey, md);
+}
+
+/* XXX The first parameter is really ASN1_TIME, does it matter? */
+ASN1_TIME *x509_gmtime_adj(ASN1_UTCTIME *s, long adj) {
+    return X509_gmtime_adj(s, adj);
+}
+
+PyObject *x509_name_by_nid(X509_NAME *name, int nid) {
+    void *buf;
+    int len, xlen;
+    PyObject *ret;
+
+    if ((len = X509_NAME_get_text_by_NID(name, nid, NULL, 0)) == -1) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    len++;
+    if (!(buf = PyMem_Malloc(len))) {
+        PyErr_SetString(PyExc_MemoryError, "x509_name_by_nid");
+        return NULL;
+    }
+    xlen = X509_NAME_get_text_by_NID(name, nid, buf, len);
+    ret = PyString_FromStringAndSize(buf, xlen);
+    PyMem_Free(buf);
+    return ret;
+}
+
+int x509_name_set_by_nid(X509_NAME *name, int nid, PyObject *obj) {
+    return X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, (unsigned char *)PyString_AsString(obj), -1, -1, 0);
+}
+
+/* x509_name_add_entry_by_txt */
+int x509_name_add_entry_by_txt(X509_NAME *name, char *field, int type, char *bytes, int len, int loc, int set) {
+    return X509_NAME_add_entry_by_txt(name, field, type, (unsigned char *)bytes, len, loc, set);
+}
+
+PyObject *x509_name_get_der(X509_NAME *name)
+{
+    i2d_X509_NAME(name, 0);
+    return PyString_FromStringAndSize(name->bytes->data, name->bytes->length);
+}
+
+/* sk_X509_new_null() is a macro returning "STACK_OF(X509) *". */
+STACK_OF(X509) *sk_x509_new_null(void) {
+    return sk_X509_new_null();
+}
+
+/* sk_X509_free() is a macro. */
+void sk_x509_free(STACK_OF(X509) *stack) {
+    sk_X509_free(stack);
+}
+
+/* sk_X509_push() is a macro. */
+int sk_x509_push(STACK_OF(X509) *stack, X509 *x509) {
+    return sk_X509_push(stack, x509);
+}
+
+/* sk_X509_pop() is a macro. */
+X509 *sk_x509_pop(STACK_OF(X509) *stack) {
+    return sk_X509_pop(stack);
+}
+
+int x509_store_load_locations(X509_STORE *store, const char *file) {
+    return X509_STORE_load_locations(store, file, NULL);
+}
+
+int x509_type_check(X509 *x509) {
+    return 1;
+}
+
+int x509_name_type_check(X509_NAME *name) {
+    return 1;
+}
+
+X509_NAME *x509_req_get_subject_name(X509_REQ *x) {
+    return X509_REQ_get_subject_name(x);
+}
+
+long x509_req_get_version(X509_REQ *x) {
+    return X509_REQ_get_version(x);
+}
+
+int x509_req_set_version(X509_REQ *x, long version) {
+    return X509_REQ_set_version(x, version);
+}
+
+int x509_req_add_extensions(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts) {
+    return X509_REQ_add_extensions(req, exts);
+}
+
+X509_NAME_ENTRY *x509_name_entry_create_by_txt(X509_NAME_ENTRY **ne, char *field, int type, char *bytes, int len) {
+    return X509_NAME_ENTRY_create_by_txt( ne, field, type, (unsigned char *)bytes, len);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+LHASH_OF(CONF_VALUE) 
+#else
+LHASH
+#endif
+*x509v3_lhash() { 
+    return lh_new(NULL, NULL); /* Should probably be lh_CONF_VALUE_new but won't compile. */
+}
+
+X509V3_CTX *
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+x509v3_set_conf_lhash(LHASH_OF(CONF_VALUE) * lhash) {
+#else
+x509v3_set_conf_lhash(LHASH                * lhash) {
+#endif
+      X509V3_CTX * ctx;
+      if (!(ctx=(X509V3_CTX *)PyMem_Malloc(sizeof(X509V3_CTX)))) {
+          PyErr_SetString(PyExc_MemoryError, "x509v3_set_conf_lhash");
+          return NULL;
+      }
+      X509V3_set_conf_lhash(ctx, lhash);        
+      return ctx;
+}
+
+X509_EXTENSION *
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+x509v3_ext_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, char *name, char *value) {
+#else
+x509v3_ext_conf(LHASH                *conf, X509V3_CTX *ctx, char *name, char *value) {
+#endif
+      X509_EXTENSION * ext = NULL;
+      ext = X509V3_EXT_conf(conf, ctx, name, value); 
+      PyMem_Free(ctx); 
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+      lh_CONF_VALUE_free(conf);
+#else
+      lh_free(conf);
+#endif
+      return ext;
+}
+
+/* X509_EXTENSION_free() might be a macro, didn't find definition. */
+void x509_extension_free(X509_EXTENSION *ext) {
+    X509_EXTENSION_free(ext);
+}
+
+PyObject *x509_extension_get_name(X509_EXTENSION *ext) {
+    PyObject * ext_name;
+    const char * ext_name_str; 
+    ext_name_str = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
+    if (!ext_name_str) {
+        PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+        return NULL;
+    }
+    ext_name = PyString_FromStringAndSize(ext_name_str, strlen(ext_name_str));
+    return ext_name;
+}
+
+/* sk_X509_EXTENSION_new_null is a macro. */
+STACK_OF(X509_EXTENSION) *sk_x509_extension_new_null(void) {
+    return sk_X509_EXTENSION_new_null();
+}
+
+/* sk_X509_EXTENSION_free() is a macro. */
+void sk_x509_extension_free(STACK_OF(X509_EXTENSION) *stack) {
+    sk_X509_EXTENSION_free(stack);
+}
+
+/* sk_X509_EXTENSION_push() is a macro. */
+int sk_x509_extension_push(STACK_OF(X509_EXTENSION) *stack, X509_EXTENSION *x509_ext) {
+    return sk_X509_EXTENSION_push(stack, x509_ext);
+}
+
+/* sk_X509_EXTENSION_pop() is a macro. */
+X509_EXTENSION *sk_x509_extension_pop(STACK_OF(X509_EXTENSION) *stack) {
+    return sk_X509_EXTENSION_pop(stack);
+}
+
+/* sk_X509_EXTENSION_num() is a macro. */
+int sk_x509_extension_num(STACK_OF(X509_EXTENSION) *stack) {
+    return sk_X509_EXTENSION_num(stack);
+}
+
+/* sk_X509_EXTENSION_value() is a macro. */
+X509_EXTENSION *sk_x509_extension_value(STACK_OF(X509_EXTENSION) *stack, int i) {
+    return sk_X509_EXTENSION_value(stack, i);
+}
+
+/* X509_STORE_CTX_get_app_data is a macro. */
+void *x509_store_ctx_get_app_data(X509_STORE_CTX *ctx) {
+  return X509_STORE_CTX_get_app_data(ctx);
+}
+
+/*#defines for i2d and d2i types, which are typed differently
+in openssl-0.9.8 than they are in openssl-0.9.7. This will
+be picked up by the C preprocessor, not the SWIG preprocessor.
+Used in the wrapping of ASN1_seq_unpack and ASN1_seq_pack functions.
+*/
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL 
+#define D2ITYPE d2i_of_void *
+#define I2DTYPE i2d_of_void *
+#else
+#define D2ITYPE char *(*)()
+#define I2DTYPE int (*)()
+#endif   
+
+STACK_OF(X509) *
+make_stack_from_der_sequence(PyObject * pyEncodedString){
+    STACK_OF(X509) *certs;
+    Py_ssize_t encoded_string_len;
+    char *encoded_string;
+
+    encoded_string_len = PyString_Size(pyEncodedString);
+    if (encoded_string_len > INT_MAX) {
+        PyErr_SetString(PyExc_ValueError, "object too large");
+        return NULL;
+    }
+    encoded_string = PyString_AsString(pyEncodedString);
+    if (!encoded_string) {
+        return NULL;
+    }
+
+    certs = ASN1_seq_unpack_X509((unsigned char *)encoded_string, encoded_string_len, d2i_X509, X509_free ); 
+    if (!certs) {
+       PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+       return NULL;
+    }
+    return certs;
+}
+
+PyObject *
+get_der_encoding_stack(STACK_OF(X509) *stack){
+    PyObject * encodedString;
+    
+    unsigned char * encoding;
+    int len; 
+    
+    encoding = ASN1_seq_pack_X509(stack, i2d_X509, NULL, &len); 
+    if (!encoding) {
+       PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+       return NULL;
+    }
+    encodedString = PyString_FromStringAndSize((const char *)encoding, len);
+    OPENSSL_free(encoding);
+    return encodedString; 
+}
+
+%}
+
+/* Free malloc'ed return value for x509_name_oneline */
+%typemap(ret) char * {
+    if ($1 != NULL)
+        OPENSSL_free($1); 
+}
+%inline %{
+char *x509_name_oneline(X509_NAME *x) {
+    return X509_NAME_oneline(x, NULL, 0);
+}
+%}
+%typemap(ret) char *;
diff --git a/contrib/README b/contrib/README
new file mode 100644 (file)
index 0000000..26483f1
--- /dev/null
@@ -0,0 +1,6 @@
+This directory contains contributions by users of M2Crypto. Some of these
+may get folded into the main distribution in time.
+
+Thanks guys!
+
+
diff --git a/contrib/SimpleX509create.README b/contrib/SimpleX509create.README
new file mode 100644 (file)
index 0000000..a08db85
--- /dev/null
@@ -0,0 +1,3 @@
+Contributed by Peter Teniz <peter.teniz@inverisa.net> as a demonstration of
+PKI functionality, also contributed by him.
+
diff --git a/contrib/SimpleX509create.py b/contrib/SimpleX509create.py
new file mode 100644 (file)
index 0000000..7f5fc67
--- /dev/null
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+#
+#vim: ts=4 sw=4 nowrap
+#
+
+"""PKI demo by Peter Teniz <peter.teniz@inverisa.net>"""
+
+import M2Crypto
+
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC  = MBSTRING_FLAG | 1
+MBSTRING_BMP  = MBSTRING_FLAG | 2
+
+
+class Cert:
+       def __init__ ( self ):
+               self.RsaKey = { 'KeyLength'       : 1024,
+                                               'PubExponent'     : 0x10001,            # -> 65537
+                                               'keygen_callback' : self.callback 
+                                         }
+
+               self.KeyPair         = None
+               self.PKey            = None
+
+               self.X509Request     = None 
+               self.X509Certificate = None
+
+       def callback ( self, *args ):
+               return 'p'
+
+
+
+       def CreatePKey ( self ):
+               self.KeyPair = M2Crypto.RSA.gen_key( self.RsaKey['KeyLength'], self.RsaKey['PubExponent'], self.RsaKey['keygen_callback'] )
+               #PubKey = M2Crypto.RSA.new_pub_key( self.KeyPair.pub () )
+
+               self.KeyPair.save_key( 'KeyPair.pem', cipher='des_ede3_cbc', callback=self.callback )
+               
+               self.PKey = M2Crypto.EVP.PKey ( md='sha1')
+               self.PKey.assign_rsa ( self.KeyPair )
+
+
+       def CreateX509Request ( self ):
+               #
+               # X509 REQUEST
+               #
+
+               self.X509Request = M2Crypto.X509.Request ()
+
+               #
+               # subject
+               #
+
+               X509Name = M2Crypto.X509.X509_Name ()
+
+               X509Name.add_entry_by_txt ( field='C',            type=MBSTRING_ASC, entry='austria',               len=-1, loc=-1, set=0 )    # country name
+               X509Name.add_entry_by_txt ( field='SP',           type=MBSTRING_ASC, entry='kernten',               len=-1, loc=-1, set=0 )    # state of province name
+               X509Name.add_entry_by_txt ( field='L',            type=MBSTRING_ASC, entry='stgallen',              len=-1, loc=-1, set=0 )    # locality name
+               X509Name.add_entry_by_txt ( field='O',            type=MBSTRING_ASC, entry='labor',                 len=-1, loc=-1, set=0 )    # organization name
+               X509Name.add_entry_by_txt ( field='OU',           type=MBSTRING_ASC, entry='it-department',         len=-1, loc=-1, set=0 )    # organizational unit name
+               X509Name.add_entry_by_txt ( field='CN',           type=MBSTRING_ASC, entry='Certificate client',    len=-1, loc=-1, set=0 )    # common name
+               X509Name.add_entry_by_txt ( field='Email',        type=MBSTRING_ASC, entry='user@localhost',        len=-1, loc=-1, set=0 )    # pkcs9 email address
+               X509Name.add_entry_by_txt ( field='emailAddress', type=MBSTRING_ASC, entry='user@localhost',        len=-1, loc=-1, set=0 )    # pkcs9 email address     
+
+               self.X509Request.set_subject_name( X509Name )
+
+               #
+               # publickey
+               #
+
+               self.X509Request.set_pubkey ( pkey=self.PKey )
+               self.X509Request.sign ( pkey=self.PKey, md='sha1' )
+               #print X509Request.as_text ()
+
+
+
+
+
+
+       def CreateX509Certificate ( self ):
+               #
+               # X509 CERTIFICATE
+               #
+
+               self.X509Certificate =  M2Crypto.X509.X509 ()
+
+               #
+               # version
+               #
+
+               self.X509Certificate.set_version ( 0 )
+
+               #
+               # time notBefore
+               #
+
+               ASN1 = M2Crypto.ASN1.ASN1_UTCTIME ()
+               ASN1.set_time ( 500 )
+               self.X509Certificate.set_not_before( ASN1 )
+
+               #
+               # time notAfter
+               #
+
+               ASN1 = M2Crypto.ASN1.ASN1_UTCTIME ()
+               ASN1.set_time ( 500 )
+               self.X509Certificate.set_not_after( ASN1 )
+
+               #
+               # public key
+               #
+
+               self.X509Certificate.set_pubkey ( pkey=self.PKey )
+               
+               #
+               # subject
+               #
+
+               X509Name = self.X509Request.get_subject ()
+
+               #print X509Name.entry_count ()
+               #print X509Name.as_text ()
+
+               self.X509Certificate.set_subject_name( X509Name )
+
+               #
+               # issuer
+               #
+
+               X509Name = M2Crypto.X509.X509_Name ( M2Crypto.m2.x509_name_new () )
+
+               X509Name.add_entry_by_txt ( field='C',            type=MBSTRING_ASC, entry='germany',               len=-1, loc=-1, set=0 )    # country name
+               X509Name.add_entry_by_txt ( field='SP',           type=MBSTRING_ASC, entry='bavaria',               len=-1, loc=-1, set=0 )    # state of province name
+               X509Name.add_entry_by_txt ( field='L',            type=MBSTRING_ASC, entry='munich',                len=-1, loc=-1, set=0 )    # locality name
+               X509Name.add_entry_by_txt ( field='O',            type=MBSTRING_ASC, entry='sbs',                   len=-1, loc=-1, set=0 )    # organization name
+               X509Name.add_entry_by_txt ( field='OU',           type=MBSTRING_ASC, entry='it-department',         len=-1, loc=-1, set=0 )    # organizational unit name
+               X509Name.add_entry_by_txt ( field='CN',           type=MBSTRING_ASC, entry='Certificate Authority', len=-1, loc=-1, set=0 )    # common name
+               X509Name.add_entry_by_txt ( field='Email',        type=MBSTRING_ASC, entry='admin@localhost',       len=-1, loc=-1, set=0 )    # pkcs9 email address
+               X509Name.add_entry_by_txt ( field='emailAddress', type=MBSTRING_ASC, entry='admin@localhost',       len=-1, loc=-1, set=0 )    # pkcs9 email address     
+
+               #print X509Name.entry_count ()
+               #print X509Name.as_text ()
+
+               self.X509Certificate.set_issuer_name( X509Name )
+
+               #
+               # signing
+               #
+
+               self.X509Certificate.sign( pkey=self.PKey, md='sha1' )
+               print self.X509Certificate.as_text ()
+
+
+
+
+
+if __name__ == '__main__':
+       run = Cert ()
+       run.CreatePKey ()
+       run.CreateX509Request ()
+       run.CreateX509Certificate ()
diff --git a/contrib/dave.README b/contrib/dave.README
new file mode 100644 (file)
index 0000000..1a3339a
--- /dev/null
@@ -0,0 +1,64 @@
+From dave@pythonapocrypha.com  Wed Dec 11 07:57:55 2002
+Date: Tue, 10 Dec 2002 15:05:26 -0800 (PST)
+From: Dave Brueck <dave@pythonapocrypha.com>
+To: ngps@netmemetic.com
+Subject: M2Crypto problem with asynch sockets
+
+Hi and thanks for M2Crypto - great stuff!
+
+I wrote an asynchronous socket layer and decided to use M2Crypto to add 
+SSL support to it. Unfortunately, I've found a small problem in 
+_m2crypto_wrap.c - hopefully I'm just not understanding something.
+
+The ssl_connect, ssl_read_nbio, etc. calls don't differentiate between 
+SSL_ERROR_WANT_WRITE and SSL_ERROR_WANT_READ when a non-blocking call 
+couldn't finish. But without this information, I don't know whether the 
+socket needs to do more reading or more writing before a subsequent 
+attempt will work without blocking. The demo applications (e.g. 
+echod-async.py) don't seem to care about this but they get around it by 
+simply trying the operation over and over again, which I can't do for 
+performance reasons.
+
+Am I missing something? I thought about just calling SSL_get_error when 
+the above calls return None (indicating WANT_READ or WANT_WRITE), but by 
+then the error has already been removed from the SSL error queue.
+
+Any help or suggestions would be appreciated. I'd be happy to submit a 
+patch fixing those calls, but by not returning None they would break 
+existing code.
+
+Thanks again for M2Crypto though!
+
+-Dave
+
+
+From dave@pythonapocrypha.com  Fri Dec 13 00:46:39 2002
+Date: Thu, 12 Dec 2002 09:52:08 -0800 (PST)
+From: Dave Brueck <dave@pythonapocrypha.com>
+To: ngps@netmemetic.com
+Subject: Re: M2Crypto problem with asynch sockets
+
+Hello again,
+
+Here is a patch to M2Crypto's _ssl.i that illustrates the fix I had in 
+mind in my previous message. You might not want to use it as is since it 
+changes the error semantics of the affected functions (they now raise an 
+exception that contains the SSL_WANT_READ or SSL_WANT_WRITE flag instead 
+of returning None or whatever), but if you tell me how you'd like it 
+instead then I'd be happy to fix the patch and send it back to you.
+
+Just to refresh your memory, this patch fixes the problem where a 
+non-blocking call to accept/connect/etc results in an SSL_NEED_READ/WRITE; 
+currently there's no way for the caller to know _which_ of the two 
+occurred and so it must try again once the socket has become readable OR 
+writeable, instead of waiting specifically for one or the other. For many 
+people this won't matter because their performance requirements are low 
+enough that trying the ssl_accept/etc call again prematurely won't hurt 
+too much, but for servers with lots of connections or high throughput it's 
+much more critical to wait until you know the SSL call has a better chance 
+of success.
+
+Thanks!
+-Dave Brueck
+
+
diff --git a/contrib/dave.patch b/contrib/dave.patch
new file mode 100644 (file)
index 0000000..6e41073
--- /dev/null
@@ -0,0 +1,180 @@
+--- _ssl.i.orig        Tue Sep 18 06:06:34 2001
++++ _ssl.i     Thu Dec 12 08:57:39 2002
+@@ -238,9 +238,18 @@
+     return ret;
+ }
++void SetWantRWError(int which) {
++    PyObject *o = Py_BuildValue("(is)", which, 
++                                
+ERR_reason_error_string(ERR_get_error()));
++    if (o != NULL) {
++        PyErr_SetObject(_ssl_err, o);
++        Py_DECREF(o);
++    }
++}
++
+ PyObject *ssl_accept(SSL *ssl) {
+     PyObject *obj;
+-    int r, err;
++    int r, err, which;
+     PyThreadState *_save;
+     if (thread_mode) {
+@@ -252,14 +261,15 @@
+         _save = (PyThreadState *)SSL_get_app_data(ssl);
+         PyEval_RestoreThread(_save);
+     }
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             obj = PyInt_FromLong((long)1);
+             break;
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
+-            obj = PyInt_FromLong((long)0);
++            SetWantRWError(which);
++            obj = NULL;
+             break;
+         case SSL_ERROR_SSL:
+             PyErr_SetString(_ssl_err, 
+ERR_reason_error_string(ERR_get_error()));
+@@ -281,7 +291,7 @@
+ PyObject *ssl_connect(SSL *ssl) {
+     PyObject *obj;
+-    int r, err;
++    int r, err, which;
+     PyThreadState *_save;
+     if (thread_mode) {
+@@ -293,14 +303,15 @@
+         _save = (PyThreadState *)SSL_get_app_data(ssl);
+         PyEval_RestoreThread(_save);
+     }
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             obj = PyInt_FromLong((long)1);
+             break;
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
+-            obj = PyInt_FromLong((long)0);
++            SetWantRWError(which);
++            obj = NULL;
+             break;
+         case SSL_ERROR_SSL:
+             PyErr_SetString(_ssl_err, 
+ERR_reason_error_string(ERR_get_error()));
+@@ -327,7 +338,7 @@
+ PyObject *ssl_read(SSL *ssl, int num) {
+     PyObject *obj;
+     void *buf;
+-    int r, err;
++    int r, err, which;
+     PyThreadState *_save;
+     if (!(buf = PyMem_Malloc(num))) {
+@@ -343,7 +354,7 @@
+         _save = (PyThreadState *)SSL_get_app_data(ssl);
+         PyEval_RestoreThread(_save);
+     }
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             buf = PyMem_Realloc(buf, r);
+@@ -352,8 +363,8 @@
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
+         case SSL_ERROR_WANT_X509_LOOKUP:
+-            Py_INCREF(Py_None);
+-            obj = Py_None;
++            SetWantRWError(which);
++            obj = NULL;
+             break;
+         case SSL_ERROR_SSL:
+             PyErr_SetString(_ssl_err, 
+ERR_reason_error_string(ERR_get_error()));
+@@ -377,14 +388,14 @@
+ PyObject *ssl_read_nbio(SSL *ssl, int num) {
+     PyObject *obj;
+     void *buf;
+-    int r, err;
++    int r, err, which;
+     if (!(buf = PyMem_Malloc(num))) {
+         PyErr_SetString(PyExc_MemoryError, "ssl_read");
+         return NULL;
+     }
+     r = SSL_read(ssl, buf, num);
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             buf = PyMem_Realloc(buf, r);
+@@ -392,6 +403,9 @@
+             break;
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
++            SetWantRWError(which);
++            obj = NULL;
++            break;
+         case SSL_ERROR_WANT_X509_LOOKUP:
+             Py_INCREF(Py_None);
+             obj = Py_None;
+@@ -417,7 +431,7 @@
+ int ssl_write(SSL *ssl, PyObject *blob) {
+     const void *buf;
+-    int len, r, err;
++    int len, r, err, which;
+     PyThreadState *_save;
+ #if PYTHON_API_VERSION >= 1009
+@@ -440,12 +454,14 @@
+         _save = (PyThreadState *)SSL_get_app_data(ssl);
+         PyEval_RestoreThread(_save);
+     }
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             return r;
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
++            SetWantRWError(which);
++            return -1;
+         case SSL_ERROR_WANT_X509_LOOKUP:
+             return -1;
+         case SSL_ERROR_SSL:
+@@ -466,7 +482,7 @@
+ int ssl_write_nbio(SSL *ssl, PyObject *blob) {
+     const void *buf;
+-    int len, r, err;
++    int len, r, err, which;
+ #if PYTHON_API_VERSION >= 1009
+     if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+@@ -480,12 +496,14 @@
+     buf = (const void *)PyString_AsString(blob);
+ #endif
+     r = SSL_write(ssl, buf, len);
+-    switch (SSL_get_error(ssl, r)) {
++    switch ((which = SSL_get_error(ssl, r))) {
+         case SSL_ERROR_NONE:
+         case SSL_ERROR_ZERO_RETURN:
+             return r;
+         case SSL_ERROR_WANT_WRITE:
+         case SSL_ERROR_WANT_READ:
++            SetWantRWError(which);
++            return -1;
+         case SSL_ERROR_WANT_X509_LOOKUP:
+             return -1;
+         case SSL_ERROR_SSL:
+
+
+
diff --git a/contrib/dispatcher.README b/contrib/dispatcher.README
new file mode 100644 (file)
index 0000000..3049a5e
--- /dev/null
@@ -0,0 +1,30 @@
+Date: Thu, 31 May 2001 17:11:45 +0400 (MSD)
+From: Ilya Etingof <ilya@glas.net>
+To: ngps@post1.com
+Cc: Ilya Etingof <ilya@glas.net>
+Subject: Another kind of non-blocking SSL dispatcher
+
+--1922505501-409592217-991314705=:1995
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+Hi,
+
+Thanks for writing M2Crypto!
+
+I've been trying to use the ssl_dispatcher.py though I felt like the
+bundled version is not absolutely non-blocking. Precisely, it looks
+like the Connection.connect() method does not handle the case when
+socket.connect() returns the WOULDBLOCK error. Another suspicious thing 
+is that there seems to be no SSL "want read" and "want write" error
+return codes of SSL read and write functions.
+
+The attached [quick and dirty] code hopefully fixes these two
+problems. Please, let me know if I'm missing some important clue about all
+this.
+
+Thanks,
+ilya
+
+--1922505501-409592217-991314705=:1995
+
diff --git a/contrib/dispatcher.py b/contrib/dispatcher.py
new file mode 100644 (file)
index 0000000..6e7302d
--- /dev/null
@@ -0,0 +1,191 @@
+#!/usr/local/bin/python -O\r
+"""\r
+   Implements a [hopefully] non-blocking SSL dispatcher on top of\r
+   M2Crypto package.\r
+   \r
+   Written by Ilya Etingof <ilya@glas.net>, 05/2001\r
+"""\r
+import asyncore, socket\r
+\r
+# M2Crypto\r
+from M2Crypto import SSL\r
+\r
+class _nb_connection (SSL.Connection):\r
+    """Functional equivalent of SSL.Connection class. Facilitates\r
+       possibly delayed socket.connect() and socket.accept()\r
+       termination.\r
+    """\r
+    def __init__ (self, ctx, sock):\r
+        SSL.Connection.__init__ (self, ctx, sock)\r
+        \r
+    def connect(self, addr):\r
+        self._setup_ssl(addr)\r
+        return self._check_ssl_return(SSL.m2.ssl_connect(self.ssl))\r
+\r
+    def accept(self, addr):\r
+        self._setup_ssl(addr)\r
+        self.accept_ssl()\r
+                \r
+class dispatcher(asyncore.dispatcher_with_send):\r
+    """A non-blocking SSL dispatcher that mimics the \r
+       asyncode.dispatcher API.\r
+    """\r
+    def __init__ (self, cert, key, sock=None, serving=None):\r
+        asyncore.dispatcher_with_send.__init__ (self)\r
+        \r
+        self.__serving = serving\r
+\r
+        # XXX\r
+        if sock:\r
+            if self.__serving:\r
+                self.set_socket(sock)\r
+        else:\r
+            self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\r
+            \r
+        self.ctx = SSL.Context('sslv23')\r
+        self.ctx.set_verify(SSL.verify_none, 10)\r
+        self.ctx.load_cert(cert, key)\r
+        self.ctx.set_info_callback()\r
+\r
+        self.ssl = _nb_connection(self.ctx, self.socket)\r
+        \r
+        self.__output = ''\r
+        self.__want_write = 1\r
+\r
+    #\r
+    # The following are asyncore overloaded methods\r
+    #\r
+    \r
+    def handle_connect (self):\r
+        """Initiate SSL connection negotiation\r
+        """\r
+        if self.__serving:\r
+            self.ssl.accept (self.addr)\r
+\r
+            self.peer = self.ssl.get_peer_cert()\r
+        \r
+            self.handle_ssl_accept()\r
+\r
+        else:\r
+            self.ssl.connect (self.addr)\r
+\r
+            self.handle_ssl_connect()\r
+\r
+    def handle_read(self):\r
+        """Read user and/or SSL protocol data from SSL connection\r
+        """\r
+        ret = self.ssl._read_nbio()\r
+\r
+        if ret:\r
+            self.handle_ssl_read(ret)\r
+        else:\r
+            # Assume write is wanted\r
+            self.__want_write = 1\r
+\r
+    def handle_write(self):\r
+        """Write pending user and/or SSL protocol data down to SSL\r
+           connection\r
+        """\r
+        self.__want_write = 0\r
+\r
+        ret = self.ssl._write_nbio(self.__output)\r
+        \r
+        if ret < 0:\r
+            try:\r
+                err = SSL.m2.ssl_get_error(self.ssl.ssl, ret)\r
+\r
+            except SSL.SSLError:\r
+                return\r
+            \r
+            if err == SSL.m2.ssl_error_want_write:\r
+                self.__want_write = 1\r
+        else:\r
+            self.__output = self.__output[ret:]\r
+\r
+    def writable (self):\r
+        """Indicate that write is desired if here're some\r
+           user and/or SSL protocol data.\r
+        """\r
+        if self.__output or self.__want_write:\r
+            return 1\r
+\r
+        return self.ssl_writable()\r
+\r
+    def handle_close (self):\r
+        """Shutdown SSL connection.\r
+        """\r
+        self.ssl = None\r
+        \r
+        self.ctx = None\r
+        self.close ()\r
+\r
+        self.handle_ssl_close()\r
+\r
+    def handle_error (self, *info):\r
+        """A trap for asyncore errors\r
+        """\r
+        self.handle_ssl_error(info)\r
+\r
+    #\r
+    # The following are ssl.dispatcher API\r
+    #\r
+    \r
+    def ssl_connect(self, server):\r
+        """Initiate SSL connection\r
+        """\r
+        self.connect(server)\r
+    \r
+    def ssl_write(self, data):\r
+        """Write data to SSL connection\r
+        """\r
+        self.__output = self.__output + data\r
+\r
+    def ssl_close(self):\r
+        """Close SSL connection\r
+        """\r
+        self.handle_close()\r
+        \r
+    def handle_ssl_connect(self):\r
+        """Invoked on SSL connection establishment (whilst\r
+           in client mode)\r
+        """\r
+        print 'Unhandled handle_ssl_connect()'\r
+\r
+    def handle_ssl_accept(self):\r
+        """Invoked on SSL connection establishment (whilst\r
+           in server mode)\r
+        """\r
+        print 'Unhandled handle_ssl_accept()'\r
+        \r
+    def handle_ssl_read(self, data):\r
+        """Invoked on new data arrival to SSL connection\r
+        """\r
+        print 'Unhandled handle_ssl_read event'\r
+\r
+    def handle_ssl_close(self):\r
+        """Invoked on SSL connection termination\r
+        """\r
+        pass\r
+\r
+    def ssl_writable(self):\r
+        """Invoked prior to every select() call\r
+        """\r
+        return 0\r
+    \r
+if __name__=='__main__':\r
+    """Give it a test run\r
+    """\r
+    class client(dispatcher):\r
+        """SSL client class\r
+        """\r
+        def __init__ (self, cert, key):\r
+            dispatcher.__init__(self, cert, key)\r
+            \r
+        def handle_ssl_read(self, data):\r
+            print data            \r
+            self.ssl_write('test write')\r
+\r
+    ssl = client('test.cert', 'test.key')\r
+    ssl.ssl_connect(('localhost', 7777))\r
+    \r
+    asyncore.loop()\r
diff --git a/contrib/isaac.README b/contrib/isaac.README
new file mode 100644 (file)
index 0000000..b47932e
--- /dev/null
@@ -0,0 +1,18 @@
+This is Isaac Salzberg's application of Mihai Ibanescu's patch
+(available on SF) that allows HTTPS tunneling through an
+authenticating proxy.
+
+This one's a double whammy: it works with IIS through the 
+authenticating proxy, whereas the one on SF, which uses Python's
+built-in SSL, doesn't.
+
+This code is not folded into the main distribution because:
+
+1. Apparently Mihai is still working on it.
+2. Mihai uses Python's built-in SSL. Isaac patched it to use
+   M2Crypto.SSL. The stuff is essentially #ifdef'ed code.
+3. I don't have an authenticating proxy nor an IIS server to test
+   against, so I can't clean up the code. Volunteers welcome. ;-)
+
+Thanks Isaac.
+
diff --git a/contrib/isaac.httpslib.py b/contrib/isaac.httpslib.py
new file mode 100644 (file)
index 0000000..ae9d23d
--- /dev/null
@@ -0,0 +1,269 @@
+"""M2Crypto support for Python 1.5.2 and Python 2.x's httplib. 
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import string, sys
+from httplib import *
+import SSL
+
+if sys.version[0] == '2':
+    
+    if sys.version[:3] in ['2.1', '2.2']:
+        # In 2.1 and above, httplib exports "HTTP" only.
+        from httplib import HTTPConnection, HTTPS_PORT
+        # ISS Added:
+        from httplib import HTTPResponse,FakeSocket
+
+    class HTTPSConnection(HTTPConnection):
+    
+        """
+        This class allows communication via SSL using M2Crypto.
+        """
+    
+        default_port = HTTPS_PORT
+    
+        def __init__(self, host, port=None, **ssl):
+            keys = ssl.keys()
+            try: 
+                keys.remove('key_file')
+            except ValueError:
+                pass
+            try:
+                keys.remove('cert_file')
+            except ValueError:
+                pass
+            try:
+                keys.remove('ssl_context')
+            except ValueError:
+                pass
+            if keys:
+                raise IllegalKeywordArgument()
+            try:
+                self.ssl_ctx = ssl['ssl_context']
+                assert isinstance(self.ssl_ctx, SSL.Context)
+            except KeyError:
+                self.ssl_ctx = SSL.Context('sslv23')
+            HTTPConnection.__init__(self, host, port)
+    
+        def connect(self):
+            self.sock = SSL.Connection(self.ssl_ctx)
+            self.sock.connect((self.host, self.port))
+    
+        def close(self):
+            # This kludges around line 545 of httplib.py,
+            # which closes the connection in this object;
+            # the connection remains open in the response
+            # object.
+            #
+            # M2Crypto doesn't close-here-keep-open-there,
+            # so, in effect, we don't close until the whole 
+            # business is over and gc kicks in.
+            #
+            # Long-running callers beware leakage.
+            #
+            # 05-Jan-2002: This module works with Python 2.2,
+            # but I've not investigated if the above conditions
+            # remain.
+            pass
+
+
+    class HTTPS(HTTP):
+        
+        _connection_class = HTTPSConnection
+    
+        def __init__(self, host='', port=None, **ssl):
+            HTTP.__init__(self, host, port)
+            try:
+                self.ssl_ctx = ssl['ssl_context']
+            except KeyError:
+                self.ssl_ctx = SSL.Context('sslv23')
+
+
+elif sys.version[:3] == '1.5':
+
+    class HTTPS(HTTP):
+    
+        def __init__(self, ssl_context, host='', port=None):
+            assert isinstance(ssl_context, SSL.Context)
+            self.debuglevel=0
+            self.file=None
+            self.ssl_ctx=ssl_context
+            if host:
+                self.connect(host, port)
+    
+        def connect(self, host, port=None):
+            # Cribbed from httplib.HTTP.
+            if not port:
+                i = string.find(host, ':')
+                if i >= 0:
+                    host, port = host[:i], host[i+1:]
+                    try: port = string.atoi(port)
+                    except string.atoi_error:
+                        raise socket.error, "nonnumeric port"
+            if not port: port = HTTPS_PORT
+            self.sock = SSL.Connection(self.ssl_ctx)
+            if self.debuglevel > 0: print 'connect:', (host, port)
+            self.sock.connect((host, port))
+
+# ISS Added.
+# From here, starts the proxy patch
+class HTTPProxyConnection(HTTPConnection):
+    """
+    This class provides HTTP access through (authenticated) proxies.
+    
+    Example:
+    If the HTTP proxy address is proxy.your.org:8080, an authenticated proxy
+    (one which requires a username/password combination in order to serve
+    requests), one can fetch HTTP documents from 'www.webserver.net', port 81:
+
+    conn = HTTPProxyConnection('proxy.your.org:8080', 'www.webserver.net',
+        port=81, username='username', password='password')
+    conn.connect()
+    conn.request("HEAD", "/index.html", headers={'X-Custom-Header-1' : 'Value-1'})
+    resp = conn.getresponse()
+    ...
+
+    """
+    def __init__(self, proxy, host, port=None, username=None, password=None):
+        # The connection goes through the proxy
+        HTTPConnection.__init__(self, proxy)
+        # save the proxy connection settings
+        self.__proxy, self.__proxy_port = self.host, self.port
+        # self.host and self.port will point to the real host
+        self._set_hostport(host, port)
+        # save the host and port
+        self._host, self._port = self.host, self.port
+        # Authenticated proxies support
+        self.__username = username
+        self.__password = password
+
+    def connect(self):
+        """Connect to the host and port specified in __init__ (through a
+        proxy)."""
+        # We are connecting to the proxy, so use the proxy settings
+        self._set_hostport(self.__proxy, self.__proxy_port)
+        HTTPConnection.connect(self)
+        # Restore the real host and port
+        self._set_hostport(self._host, self._port)
+
+    def putrequest(self, method, url):
+        """Send a request to the server.
+
+        `method' specifies an HTTP request method, e.g. 'GET'.
+        `url' specifies the object being requested, e.g. '/index.html'.
+        """
+        # The URL has to include the real host
+        hostname = self._host
+        if self._port != self.default_port:
+            hostname = hostname + ':' + str(self._port)
+        newurl = "http://%s%s" % (hostname, url)
+        # Piggyback on the parent class
+        HTTPConnection.putrequest(self, method, newurl)
+        # Add proxy-specific headers
+        self._add_auth_proxy_header()
+        
+    def _add_auth_proxy_header(self):
+        """Adds an HTTP header for authenticated proxies
+        """
+        if not self.__username:
+            # No username, so assume not an authenticated proxy
+            return
+        # Authenticated proxy
+        import base64
+        userpass = "%s:%s" % (self.__username, self.__password)
+        enc_userpass = string.strip(base64.encodestring(userpass))
+        self.putheader("Proxy-Authorization", "Basic %s" % enc_userpass)
+
+class HTTPSProxyResponse(HTTPResponse):
+    """
+    Replacement class for HTTPResponse
+    Proxy responses (made through SSL) have to keep the connection open 
+    after the initial request, since the connection is tunneled to the SSL
+    host with the CONNECT method.
+    """
+    def begin(self):
+        HTTPResponse.begin(self)
+        self.will_close = 0
+
+class HTTPSProxyConnection(HTTPProxyConnection):
+    """This class provides HTTP access through (authenticated) proxies.
+    
+    Example:
+    If the HTTP proxy address is proxy.your.org:8080, an authenticated proxy
+    (one which requires a username/password combination in order to serve
+    requests), one can fetch HTTP documents from 'www.webserver.net', port 81:
+
+    conn = HTTPProxyConnection('proxy.your.org:8080', 'www.webserver.net',
+        port=81, username='username', password='password')
+    conn.connect()
+    conn.request("HEAD", "/index.html", headers={'X-Custom-Header-1' : 'Value-1'})
+    resp = conn.getresponse()
+    ...
+
+    To avoid dealing with multiple inheritance, this class only inherits from
+    HTTPProxyConnection.
+    """
+    default_port = HTTPSConnection.default_port
+
+    def __init__(self, proxy, host, port=None, username=None, password=None, **x509):
+        for key in x509.keys():
+           if key not in ['cert_file', 'key_file','ssl_context']:
+                raise IllegalKeywordArgument()
+        self.key_file = x509.get('key_file')
+        self.cert_file = x509.get('cert_file')
+        #ISS Added
+        self.ssl_ctx = x509.get('ssl_context')
+        # Piggybacking on HTTPProxyConnection
+        HTTPProxyConnection.__init__(self, proxy, host, port, username, password)
+
+    def connect(self):
+        """Connect (using SSL) to the host and port specified in __init__ 
+        (through a proxy)."""
+        import socket
+        # Set the connection with the proxy
+        HTTPProxyConnection.connect(self)
+        # Use the stock HTTPConnection putrequest 
+        host = "%s:%s" % (self._host, self._port)
+        HTTPConnection.putrequest(self, "CONNECT", host)
+        # Add proxy-specific stuff
+        self._add_auth_proxy_header()
+        # And send the request
+        HTTPConnection.endheaders(self)
+        # Save the response class
+        response_class = self.response_class
+        # And replace the response class with our own one, which does not
+        # close the connection
+        self.response_class = HTTPSProxyResponse
+        response = HTTPConnection.getresponse(self)
+        # Restore the response class
+        self.response_class = response_class
+        # Close the response object manually
+        response.close()
+        if response.status != 200:
+            # Close the connection manually
+            self.close()
+            # XXX Find the appropriate error code
+            raise socket.error(1001, response.status, response.value)
+
+        # NgPS: I haven't read the code recently, but I think it is
+        # reasonable to assume that self.sock is a connected TCP socket at
+        # this point.
+
+        # Use the real stuff. ;-)
+        if self.ssl_ctx and isinstance(self.ssl_ctx, SSL.Context):
+           self.sock =  SSL.Connection(self.ssl_ctx)
+           self.sock.connect((self.host, self.port))
+        else:
+           # Fake the socket
+           ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
+           self.sock = FakeSocket(self.sock, ssl)
+        if self.debuglevel > 0: print 'socket type:', self.sock
+
+    def putrequest(self, method, url):
+        """Send a request to the server.
+
+        `method' specifies an HTTP request method, e.g. 'GET'.
+        `url' specifies the object being requested, e.g. '/index.html'.
+        """
+        # bypass the parent class's putrequest: use the grandparent's one :-)
+        return HTTPConnection.putrequest(self, method, url)
diff --git a/contrib/m2crypto.spec b/contrib/m2crypto.spec
new file mode 100644 (file)
index 0000000..f80cddb
--- /dev/null
@@ -0,0 +1,46 @@
+%define name    m2crypto
+%define version 0.06
+%define snap    snap5
+%define release %{snap}.1
+%define prefix  %{_prefix}
+
+Summary:      Python crypto library
+Name:         %{name}
+Version:      %{version}
+Release:      %{release}
+Copyright:    tummy.com, ltd.
+Group:        Applications/Crypto
+Source:       %{name}-%{version}-%{snap}.zip
+Packager:     Sean Reifschneider <jafo-rpms@tummy.com>
+BuildRoot:    /var/tmp/%{name}-root
+Requires:     openssl >= 0.9.6a
+Patch0:       m2crypto-makefile.patch
+BuildPrereq:  openssl-devel >= 0.9.6a
+BuildPrereq:  swig >= 1.1p5
+
+%description
+M2Crypto makes available to the Python programmer the following:
+
+   RSA, DH, DSA, HMACs, message digests, symmetric ciphers.
+   SSL functionality to implement clients and servers.
+   HTTPS extensions to Python's httplib, urllib, and the eff-bot's xmlrpclib.
+   S/MIME v2.
+
+%prep
+%setup -n %{name}-%{version}-%{snap}
+%patch0 -p1
+%build
+( cd swig; make -f Makefile.py1 )
+
+%install
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+mkdir -p "$RPM_BUILD_ROOT"/usr/lib/python1.5/site-packages
+cp -a M2Crypto "$RPM_BUILD_ROOT"/usr/lib/python1.5/site-packages
+
+%clean
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+
+%files
+%defattr(755,root,root)
+%doc BUGS CHANGES INSTALL LICENCE README STORIES doc demo tests patches
+/usr/lib/python1.5/site-packages
diff --git a/contrib/smimeplus.README b/contrib/smimeplus.README
new file mode 100644 (file)
index 0000000..2125b9f
--- /dev/null
@@ -0,0 +1,3 @@
+Contributed by Bernard Yue: A high level smime interface. "It works
+for Python 2.2 and above, and requires the email module from Python
+2.2.3 to work on Python 2.1.
diff --git a/contrib/smimeplus.py b/contrib/smimeplus.py
new file mode 100644 (file)
index 0000000..5a9c22d
--- /dev/null
@@ -0,0 +1,187 @@
+import sys, os, tempfile
+import UserDict
+from email import Message
+import M2Crypto
+
+if not (sys.version_info[0] >= 2 and sys.version_info[1] >= 2):
+    class object:
+        pass
+    True=1
+    False=0
+
+
+class smimeplus(object):
+    def __init__(self, cert, privkey, passphrase, cacert, randfile=None):
+        self.cipher = 'des_ede3_cbc'   # XXX make it configable??
+        self.setsender(cert, privkey, passphrase)
+        self.setcacert(cacert)
+        self.randfile = randfile
+        self.__loadrand()
+        
+    def __passcallback(self, v):
+        """private key passphrase callback function"""
+        return self.passphrase
+
+    def __loadrand(self):
+        """Load random number file"""
+        if self.randfile:
+            M2Crypto.Rand.load_file(self.randfile, -1)
+
+    def __saverand(self):
+        """Save random number file"""
+        if self.randfile:
+            M2Crypto.Rand.save_file(self.randfile)
+
+    def __gettext(self, msg):
+        """Return a string representation of 'msg'"""
+        _data = ''
+        if isinstance(msg, Message.Message):
+            for _p in msg.walk():
+                _data = _data + _p.as_string()
+        else:
+            _data = str(msg)
+        return _data
+
+    def __pack(self, msg):
+        """Convert 'msg' to string and put it into an memory buffer for 
+           openssl operation"""
+        return M2Crypto.BIO.MemoryBuffer(self.__gettext(msg))
+
+    def setsender(self, cert=None, privkey=None, passphrase=None):
+        if cert:
+            self.cert = cert
+        if privkey:
+            self.key  = privkey
+        if passphrase:
+            self.passphrase  = passphrase
+
+    def setcacert(self, cacert):
+        self.cacert = cacert
+
+    def sign(self, msg):
+        """Sign a message"""
+        _sender = M2Crypto.SMIME.SMIME()
+        _sender.load_key_bio(self.__pack(self.key), self.__pack(self.cert),
+                callback=self.__passcallback)
+
+        _signed = _sender.sign(self.__pack(msg))
+
+        _out = self.__pack(None)
+        _sender.write(_out, _signed, self.__pack(msg))
+        return _out.read()
+
+    def verify(self, smsg, scert):
+        """Verify to see if 'smsg' was signed by 'scert', and scert was
+           issued by cacert of this object.  Return message signed if success, 
+           None otherwise"""
+        # Load signer's cert.
+        _x509 = M2Crypto.X509.load_cert_bio(self.__pack(scert))
+        _stack = M2Crypto.X509.X509_Stack()
+        _stack.push(_x509)
+
+        # Load CA cert.
+        _tmpfile = persistdata(self.cacert)
+        _store = M2Crypto.X509.X509_Store()
+        _store.load_info(_tmpfile)
+        os.remove(_tmpfile)
+
+        # prepare SMIME object
+        _sender = M2Crypto.SMIME.SMIME()
+        _sender.set_x509_stack(_stack)
+        _sender.set_x509_store(_store)
+    
+        # Load signed message, verify it, and return result
+        _p7, _data = M2Crypto.SMIME.smime_load_pkcs7_bio(self.__pack(smsg))
+        try:
+            return _sender.verify(_p7, flags=M2Crypto.SMIME.PKCS7_SIGNED)
+        except M2Crypto.SMIME.SMIME_Error, _msg:
+            return None
+
+    def encrypt(self, rcert, msg):
+        # Instantiate an SMIME object.
+        _sender = M2Crypto.SMIME.SMIME()
+    
+        # Load target cert to encrypt to.
+        _x509 = M2Crypto.X509.load_cert_bio(self.__pack(rcert))
+        _stack = M2Crypto.X509.X509_Stack()
+        _stack.push(_x509)
+        _sender.set_x509_stack(_stack)
+    
+        _sender.set_cipher(M2Crypto.SMIME.Cipher(self.cipher))
+    
+        # Encrypt the buffer.
+        _buf = self.__pack(self.__gettext(msg))
+        _p7 = _sender.encrypt(_buf)
+        
+        # Output p7 in mail-friendly format.
+        _out = self.__pack('')
+        _sender.write(_out, _p7)
+    
+        # Save the PRNG's state.
+        self.__saverand()
+
+        return _out.read()
+
+    def decrypt(self, emsg):
+        """decrypt 'msg'.  Return decrypt message if success, None
+           otherwise"""
+        # Load private key and cert.
+        _sender = M2Crypto.SMIME.SMIME()
+        _sender.load_key_bio(self.__pack(self.key), self.__pack(self.cert),
+                callback=self.__passcallback)
+    
+        # Load the encrypted data.
+        _p7, _data = M2Crypto.SMIME.smime_load_pkcs7_bio(self.__pack(emsg))
+    
+        # Decrypt p7.
+        try:
+            return _sender.decrypt(_p7)
+        except M2Crypto.SMIME.SMIME_Error, _msg:
+            return None
+
+    def addHeader(self, rcert, content, subject=''):
+        """Add To, From, Subject Header to 'content'"""
+        _scert = M2Crypto.X509.load_cert_bio(self.__pack(self.cert))
+        _scertsubj = X509_Subject(str(_scert.get_subject()))
+        _rcert = M2Crypto.X509.load_cert_bio(self.__pack(rcert))
+        _rcertsubj = X509_Subject(str(_rcert.get_subject()))
+
+        _out = 'From: "%(CN)s" <%(emailAddress)s>\n' % _scertsubj
+        _out = _out + 'To: "%(CN)s" <%(emailAddress)s>\n' % _rcertsubj
+        _out = _out + 'Subject: %s\n' % subject
+        _out = _out + content
+
+        return _out
+
+
+class X509_Subject(UserDict.UserDict):
+    # This class needed to be rewritten or merge with X509_Name
+    def __init__(self, substr):
+        UserDict.UserDict.__init__(self)
+        try:
+            _data = substr.strip().split('/')
+        except AttributeError, _msg:
+            pass
+        else:
+            for _i in _data:
+                try:
+                    _k, _v = _i.split('=')
+                    self[_k] = _v
+                except ValueError, _msg:
+                    pass
+
+
+def persistdata(data, file=None, isbinary=False):
+    if not file:
+        file = tempfile.mktemp()
+    if isbinary:
+        _flag = 'wb'
+    else:
+        _flag = 'w'
+
+    _fh = open(file, _flag)
+    _fh.write(data)
+    _fh.close()
+    return file
+
+
diff --git a/demo/CipherSaber/CipherSaber.py b/demo/CipherSaber/CipherSaber.py
new file mode 100644 (file)
index 0000000..81bd4c6
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+"""CipherSaber, http://ciphersaber.gurus.com.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# XXX getopt handling has bugs.
+
+from M2Crypto import RC4, Rand
+import getopt, getpass, sys
+
+class argerr(Exception): pass
+
+cmd = -1
+inf = sys.stdin
+outf = sys.stdout
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'dei:o:')
+for opt in optlist:
+    if '-d' in opt:
+        cmd = cmd + 1
+    elif '-e' in opt:
+        cmd = cmd + 2
+    elif '-i' in opt:
+        i = opt[1]
+        if i == '-':
+            inf = sys.stdin
+        else:
+            inf = open(i, 'rb')
+    elif '-o' in opt:
+        o = opt[1]
+        if o == '-':
+            outf = sys.stdout
+        else:
+            outf = open(o, 'wb')
+if cmd < 0:
+    raise argerr, "either -d or -e"
+if cmd > 1:
+    raise argerr, "either -d or -e, not both"
+
+if cmd == 0:
+    iv = inf.read(10)
+    pp = getpass.getpass('Enter decryption passphrase: ')
+else:
+    iv = Rand.rand_bytes(10)
+    outf.write(iv)
+    pp = getpass.getpass('Enter encryption passphrase: ')
+    pp2 = getpass.getpass('Enter passphrase again: ')
+    if pp != pp2:
+        raise SystemExit, 'passphrase mismatch, I\'m outta here...'
+
+ci = RC4.RC4(pp + iv)
+del pp, iv
+
+while 1:
+    buf = inf.read()
+    if not buf: 
+        break
+    outf.write(ci.update(buf))
+outf.write(ci.final())
+
+
diff --git a/demo/CipherSaber/cstest1.cs1 b/demo/CipherSaber/cstest1.cs1
new file mode 100644 (file)
index 0000000..a9794b6
--- /dev/null
@@ -0,0 +1 @@
+om\v«óªg\19\ 3\150í¶wÊtà\b\9dÐç¸\85CV»\14Hã|Ûïçó¨OO_³ý
\ No newline at end of file
diff --git a/demo/Zope/ZServer/HTTPS_Server.py b/demo/Zope/ZServer/HTTPS_Server.py
new file mode 100644 (file)
index 0000000..06a2668
--- /dev/null
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2000-2004, Ng Pheng Siong. All Rights Reserved.
+# This file is derived from Zope's ZServer/HTTPServer.py.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""
+Medusa HTTPS server for Zope
+
+changes from Medusa's http_server:
+
+    Request Threads -- Requests are processed by threads from a thread
+    pool.
+    
+    Output Handling -- Output is pushed directly into the producer
+    fifo by the request-handling thread. The HTTP server does not do
+    any post-processing such as chunking.
+
+    Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+    which mutiple requests come in on the same channel, before
+    responses are sent back. When requests are pipelined, the client
+    doesn't wait for the response before sending another request. The
+    server must ensure that responses are sent back in the same order
+    as requests are received.
+
+
+changes from Zope's HTTP server:
+
+    Well, this is a *HTTPS* server :)
+
+    X.509 certificate-based authentication -- When this is in force,
+    zhttps_handler, a subclass of zhttp_handler, is installed.  The
+    https server is configured to request an X.509 certificate from
+    the client. When the request reaches zhttps_handler, it sets
+    REMOTE_USER to the client's subject distinguished name (DN) from
+    the certificate. Zope's REMOTE_USER machinery takes care of the
+    rest, e.g., in conjunction with the RemoteUserFolder product.
+    
+""" 
+
+import sys, time, types
+
+from PubCore import handle
+from medusa import asyncore
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION
+from HTTPServer import zhttp_handler
+from zLOG import register_subsystem
+
+from M2Crypto import SSL, version
+from medusa.https_server import https_server, https_channel
+from medusa.asyncore import dispatcher
+
+
+ZSERVER_SSL_VERSION=version
+
+register_subsystem('ZServer HTTPS_Server')
+
+
+class zhttps0_handler(zhttp_handler):
+    "zhttps0 handler - sets SSL request headers a la mod_ssl"
+
+    def __init__ (self, module, uri_base=None, env=None):
+        zhttp_handler.__init__(self, module, uri_base, env)
+
+    def get_environment(self, request):
+        env = zhttp_handler.get_environment(self, request)
+        env['SSL_CIPHER'] = request.channel.get_cipher()
+        return env
+
+
+class zhttps_handler(zhttps0_handler):
+    "zhttps handler - sets REMOTE_USER to user's X.509 certificate Subject DN"
+
+    def __init__ (self, module, uri_base=None, env=None):
+        zhttps0_handler.__init__(self, module, uri_base, env)
+
+    def get_environment(self, request):
+        env = zhttps0_handler.get_environment(self, request)
+        peer = request.channel.get_peer_cert()
+        if peer is not None:
+            env['REMOTE_USER'] = str(peer.get_subject())
+        return env
+
+
+class zhttps_channel(https_channel):
+    "https channel"
+
+    closed=0
+    zombie_timeout=100*60 # 100 minutes
+    
+    def __init__(self, server, conn, addr):
+        https_channel.__init__(self, server, conn, addr)
+        self.queue=[]
+        self.working=0
+        self.peer_found=0
+    
+    def push(self, producer, send=1):
+        # this is thread-safe when send is false
+        # note, that strings are not wrapped in 
+        # producers by default
+        if self.closed:
+            return
+        self.producer_fifo.push(producer)
+        if send: self.initiate_send()
+        
+    push_with_producer=push
+
+    def work(self):
+        "try to handle a request"
+        if not self.working:
+            if self.queue:
+                self.working=1
+                try: module_name, request, response=self.queue.pop(0)
+                except: return
+                handle(module_name, request, response)
+        
+    def close(self):
+        self.closed=1
+        while self.queue:
+            self.queue.pop()
+        if self.current_request is not None:
+            self.current_request.channel=None # break circ refs
+            self.current_request=None
+        while self.producer_fifo:
+            p=self.producer_fifo.first()
+            if p is not None and type(p) != types.StringType:
+                p.more() # free up resources held by producer
+            self.producer_fifo.pop()
+        self.del_channel()
+        #self.socket.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+        self.socket.close()
+
+    def done(self):
+        "Called when a publishing request is finished"
+        self.working=0
+        self.work()
+
+    def kill_zombies(self):
+        now = int (time.time())
+        for channel in asyncore.socket_map.values():
+            if channel.__class__ == self.__class__:
+                if (now - channel.creation_time) > channel.zombie_timeout:
+                    channel.close()
+
+
+class zhttps_server(https_server):    
+    "https server"
+    
+    SERVER_IDENT='ZServerSSL/%s' % (ZSERVER_SSL_VERSION,)
+    
+    channel_class = zhttps_channel
+    shutup = 0
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        self.shutup = 1
+        https_server.__init__(self, ip, port, ssl_ctx, resolver, logger_object)
+        self.ssl_ctx = ssl_ctx
+        self.shutup = 0        
+        self.log_info('(%s) HTTPS server started at %s\n'
+                      '\tHostname: %s\n\tPort: %d' % (
+                        self.SERVER_IDENT,
+                        time.ctime(time.time()),
+                        self.server_name,
+                        self.server_port
+                        ))
+        
+    def log_info(self, message, type='info'):
+        if self.shutup: return
+        dispatcher.log_info(self, message, type)
+
+    def readable(self):
+        return self.accepting and \
+                len(asyncore.socket_map) < CONNECTION_LIMIT
+
+    def listen(self, num):
+        # override asyncore limits for nt's listen queue size
+        self.accepting = 1
+        return self.socket.listen (num)
+
diff --git a/demo/Zope/ZServer/__init__.py b/demo/Zope/ZServer/__init__.py
new file mode 100644 (file)
index 0000000..69fe9b1
--- /dev/null
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+import sys, os
+
+# HACKERY to get around asyncore issues. This ought to go away! We're
+# currently using the Python 2.2 asyncore bundled with Zope to override
+# brokenness in the Python 2.1 version. We need to do some funny business
+# to make this work, as a 2.2-ism crept into the asyncore code.
+if os.name == 'posix':
+    import fcntl
+    if not hasattr(fcntl, 'F_GETFL'):
+        import FCNTL
+        fcntl.F_GETFL = FCNTL.F_GETFL
+        fcntl.F_SETFL = FCNTL.F_SETFL
+
+from medusa import asyncore
+sys.modules['asyncore'] = asyncore
+
+
+
+from medusa.test import max_sockets
+CONNECTION_LIMIT=max_sockets.max_select_sockets()
+
+ZSERVER_VERSION='1.1b1'
+import App.FindHomes
+try:
+    import App.version_txt
+    ZOPE_VERSION=App.version_txt.version_txt()
+except:
+    ZOPE_VERSION='experimental'
+
+
+# Try to poke zLOG default logging into asyncore
+# XXX We should probably should do a better job of this,
+#     however that would mean that ZServer required zLOG.
+try:
+    from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+    register_subsystem('ZServer')
+    severity={'info':INFO, 'warning':WARNING, 'error': ERROR}
+
+    def log_info(self, message, type='info'):
+        if message[:14]=='adding channel' or \
+           message[:15]=='closing channel' or \
+           message == 'Computing default hostname':
+            LOG('ZServer', BLATHER, message)
+        else:
+            LOG('ZServer', severity[type], message)
+
+    import asyncore
+    asyncore.dispatcher.log_info=log_info
+except:
+    pass
+
+# A routine to try to arrange for request sockets to be closed
+# on exec. This makes it easier for folks who spawn long running
+# processes from Zope code. Thanks to Dieter Maurer for this.
+try:
+    import fcntl
+    try:
+        from fcntl import F_SETFD, FD_CLOEXEC
+    except ImportError:
+        from FCNTL import F_SETFD, FD_CLOEXEC
+
+    def requestCloseOnExec(sock):
+        try:    fcntl.fcntl(sock.fileno(), F_SETFD, FD_CLOEXEC)
+        except: pass
+
+except (ImportError, AttributeError):
+
+    def requestCloseOnExec(sock):
+        pass
+
+import asyncore
+from medusa import resolver, logger
+from HTTPServer import zhttp_server, zhttp_handler
+from HTTPS_Server import zhttps_server, zhttps0_handler, zhttps_handler
+from PCGIServer import PCGIServer
+from FCGIServer import FCGIServer
+from FTPServer import FTPServer
+from PubCore import setNumberOfThreads
+from medusa.monitor import secure_monitor_server
+
+# override the service name in logger.syslog_logger
+logger.syslog_logger.svc_name='ZServer'
diff --git a/demo/Zope/ZServer/medusa/ftps_server.py b/demo/Zope/ZServer/medusa/ftps_server.py
new file mode 100644 (file)
index 0000000..8fee339
--- /dev/null
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server. 
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+    
+    """FTP/TLS server channel for Medusa."""
+
+    def __init__(self, server, ssl_ctx, conn, addr):
+        """Initialise the channel."""
+        self.ssl_ctx = ssl_ctx
+        self.server = server
+        self.current_mode = 'a'
+        self.addr = addr
+        asynchat.async_chat.__init__(self, conn)
+        self.set_terminator('\r\n')
+        self.client_addr = (addr[0], 21)
+        self.client_dc = None
+        self.in_buffer = ''
+        self.closing = 0
+        self.passive_acceptor = None
+        self.passive_connection = None
+        self.filesystem = None
+        self.authorized = 0
+        self._ssl_accepting = 0
+        self._ssl_accepted = 0
+        self._pbsz = None
+        self._prot = None
+        resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+        self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+    def writable(self):
+        return self._ssl_accepting or self._ssl_accepted
+
+    def handle_read(self):
+        """Handle a read event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_read(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def handle_write(self):
+        """Handle a write event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_write(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+    def found_terminator(self):
+        """Dispatch the FTP command."""
+        line = self.in_buffer
+        if not len(line):
+            return
+
+        sp = string.find(line, ' ')
+        if sp != -1:
+            line = [line[:sp], line[sp+1:]]
+        else:
+            line = [line]
+
+        command = string.lower(line[0])
+        if string.find(command, 'stor') != -1:
+            while command and command[0] not in string.letters:
+                command = command[1:]
+        
+        func_name = 'cmd_%s' % command
+        if command != 'pass':
+            self.log('<== %s' % repr(self.in_buffer)[1:-1])
+        else:
+            self.log('<== %s' % line[0]+' <password>')
+
+        self.in_buffer = ''
+        if not hasattr(self, func_name):
+            self.command_not_understood(line[0])
+            return 
+    
+        func = getattr(self, func_name)
+        if not self.check_command_authorization(command):
+            self.command_not_authorized(command)
+        else:
+            try:
+                result = apply(func, (line,))
+            except:
+                self.server.total_exceptions.increment()
+                (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+                if self.client_dc:
+                    try:
+                        self.client_dc_close()
+                    except:
+                        pass
+                resp = '451 Server error: %s, %s: file %s line: %s'
+                self.respond(resp % (t, v, file, line))
+
+    def make_xmit_channel(self):
+        """Create a connection for sending data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+                else:
+                    cdc = ftp_server.xmit_channel(self, addr)
+                    cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+                else:
+                    cdc = ftp_server.xmit_channel(self)
+        else:
+            if self._prot:
+                cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+            else:
+                cdc = ftp_server.xmit_channel(self, self.client_addr)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            if self.bind_local_minus_one:
+                cdc.bind(('', self.server.port - 1))
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def make_recv_channel(self, fd):
+        """Create a connection for receiving data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, addr, fd)
+                cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, None, fd)
+        else:
+            if self._prot:
+                cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+            else:
+                cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def cmd_auth(self, line):
+        """Prepare for TLS operation."""
+        # XXX Handle variations.
+        if line[1] != 'TLS':
+            self.command_not_understood (string.join(line))
+        else:
+            self.respond('234 AUTH TLS successful')
+            self._ssl_accepting = 1
+            self.socket = SSL.Connection(self.ssl_ctx, self.socket)    
+            self.socket.setup_addr(self.addr)
+            self.socket.setup_ssl()
+            self.socket.set_accept_state()
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+
+    def cmd_pbsz(self, line):
+        """Negotiate size of buffer for secure data transfer. For
+        FTP/TLS the only valid value for the parameter is '0'; any 
+        other value is accepted but ignored."""
+        if not (self._ssl_accepting or self._ssl_accepted):
+            return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+        self._pbsz = 1
+        self.respond('200 PBSZ=0 successful.')
+
+    def cmd_prot(self, line):
+        """Negotiate the security level of the data connection.""" 
+        if self._pbsz is None:
+            return self.respond('503 PBSZ must be issued prior to PROT')
+        if line[1] == 'C':
+            self.respond('200 Protection set to Clear')
+            self._pbsz = None
+            self._prot = None
+        elif line[1] == 'P': 
+            self.respond('200 Protection set to Private')
+            self._prot = 1
+        elif line[1] in ('S', 'E'):
+            self.respond('536 PROT %s unsupported' % line[1])
+        else:
+            self.respond('504 PROT %s unsupported' % line[1])
+            
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+    """FTP/TLS server for Medusa."""
+
+    SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+    ftp_channel_class = ftp_tls_channel
+
+    def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+        """Initialise the server."""
+        self.ssl_ctx = ssl_ctx
+        self.ip = ip
+        self.port = port
+        self.authorizer = authz
+
+        if host is None:
+            self.hostname = socket.gethostname()
+        else:
+            self.hostname = host
+
+        self.total_sessions = counter()
+        self.closed_sessions = counter()
+        self.total_files_out = counter()
+        self.total_files_in = counter()
+        self.total_bytes_out = counter()
+        self.total_bytes_in = counter()
+        self.total_exceptions = counter()
+
+        asyncore.dispatcher.__init__(self)
+        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.set_reuse_addr()
+        self.bind((self.ip, self.port))
+        self.listen(5)
+
+        if log_obj is None:
+            log_obj = sys.stdout
+
+        if resolver:
+            self.logger = logger.resolving_logger(resolver, log_obj)
+        else:
+            self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+        l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+        self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+    def handle_accept(self):
+        """Accept a socket and dispatch a channel to handle it."""
+        conn, addr = self.accept()
+        self.total_sessions.increment()
+        self.log_info('Connection from %s:%d' % addr)
+        self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+    """TLS protocol negotiation mixin for FTP/TLS."""
+
+    def tls_init(self, sock, ssl_ctx, client_addr):
+        """Perform TLS protocol negotiation."""
+        self.ssl_ctx = ssl_ctx
+        self.client_addr = client_addr
+        self._ssl_handshaking = 1
+        self._ssl_handshake_ok = 0
+        if sock:
+            self.socket = SSL.Connection(self.ssl_ctx, sock)
+            self.socket.setup_addr(self.client_addr)
+            self.socket.setup_ssl()
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+            self.add_channel()
+        # else the client hasn't connected yet; when that happens,
+        # handle_connect() will be triggered.
+
+    def tls_neg_ok(self):
+        """Return status of TLS protocol negotiation."""
+        if self._ssl_handshaking:
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+        return self._ssl_handshake_ok
+
+    def handle_connect(self):
+        """Handle a data connection that occurs after this instance came 
+        into being. When this handler is triggered, self.socket has been 
+        created and refers to the underlying connected socket."""
+        self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+        self.socket.setup_addr(self.client_addr)
+        self.socket.setup_ssl()
+        self._ssl_handshake_ok = self.socket.accept_ssl()
+        if self._ssl_handshake_ok:
+            self._ssl_handshaking = 0
+        self.add_channel()
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+    """TLS driver for a send-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+        """Initialise the driver."""
+        ftp_server.xmit_channel.__init__(self, channel, client_addr)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def readable(self):
+        """This channel is readable iff TLS negotiation is in progress.
+        (Which implies a connected channel, of course.)"""
+        if not self.connected:
+            return 0
+        else:
+            return self._ssl_handshaking
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress
+        or the application has data to send."""
+        if self._ssl_handshaking:
+            return 1
+        else:
+            return ftp_server.xmit_channel.writable(self)
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_write(self) 
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+    
+    """TLS driver for a receive-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+        """Initialise the driver."""
+        ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress."""
+        return self._ssl_handshaking
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_write(self) 
+
+
diff --git a/demo/Zope/ZServer/medusa/https_server.py b/demo/Zope/ZServer/medusa/https_server.py
new file mode 100644 (file)
index 0000000..9b932b7
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server. 
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+    ac_in_buffer_size = 1 << 16
+
+    def __init__(self, server, conn, addr):
+        http_server.http_channel.__init__(self, server, conn, addr)
+
+    def send(self, data):
+        try:
+            result = self.socket._write_nbio(data)
+            if result <= 0:
+                return 0
+            else:
+                self.server.bytes_out.increment(result)
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), why))
+            return 0
+
+    def recv(self, buffer_size):
+        try:
+            result = self.socket._read_nbio(buffer_size)
+            if result is None:
+                return ''
+            elif result == '':
+                self.close()
+                return ''
+            else:
+                self.server.bytes_in.increment(len(result))
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), why))
+            return ''
+
+
+class https_server(http_server.http_server):
+
+    SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+    channel_class=https_channel
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+        self.ssl_ctx=ssl_ctx
+        
+    def handle_accept(self):
+        # Cribbed from http_server.
+        self.total_clients.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            # linux: on rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            sys.stderr.write ('warning: server accept() threw an exception\n')
+            return
+
+        # Turn the vanilla socket into an SSL connection.
+        try:
+            ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+            ssl_conn._setup_ssl(addr)
+            ssl_conn.accept_ssl()
+            self.channel_class(self, ssl_conn, addr)
+        except SSL.SSLError:
+            pass
+
+    def writeable(self):
+        return 0
+
diff --git a/demo/Zope/ca.pem b/demo/Zope/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/Zope/dh1024.pem b/demo/Zope/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py b/demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py
new file mode 100644 (file)
index 0000000..1e3137f
--- /dev/null
@@ -0,0 +1,53 @@
+"""GuardedFile.GuardedFile
+
+Copyright (c) 2000-2003 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__ = '1.3'
+
+from AccessControl import getSecurityManager
+from Globals import HTMLFile, MessageDialog
+from OFS.Image import File, cookId
+
+manage_addForm = HTMLFile('add', globals(),Kind='GuardedFile',kind='GuardedFile')
+def manage_addGuardedFile(self, id, file, title='', precondition='', content_type='', REQUEST=None):
+    """
+    Add a new GuardedFile object.
+
+    Creates a new GuardedFile object 'id' with the content of 'file'.
+    """
+    # Object creation stuff, cribbed from OFS.Image.manage_addFile().
+    id, title = cookId(id, title, file)
+    self = self.this()
+    self._setObject(id, GuardedFile(id, title, '', content_type, precondition))
+    obj = self._getOb(id)
+    obj.manage_upload(file)
+
+    # Unset permission acquisition.
+    obj.manage_acquiredPermissions()
+
+    # Create a proxy role and set a specific permission for it.
+    proxy_role = "proxy_for_%s" % id
+    self._addRole(proxy_role)
+    obj.manage_role(proxy_role, ['View'])
+    uname = getSecurityManager().getUser().getUserName()
+    self.manage_addLocalRoles(uname, (proxy_role,), REQUEST)
+
+    # Feedback.
+    if REQUEST: return MessageDialog(
+        title  ='Success!',
+        message='GuardedFile "%s" has been created.' % id,
+        action ='manage_main')
+        
+
+class GuardedFile(File):
+    """A File object accessible by proxy only."""
+    meta_type = "GuardedFile"
+
+    def manage_beforeDelete(self, item, container):
+        """Delete self's proxy role."""
+        role = "proxy_for_%s" % self.__name__
+        container._delRoles([role], None)
+        self.manage_delLocalRoles(self.users_with_local_role(role))
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/README.txt b/demo/Zope/lib/python/Products/GuardedFile/README.txt
new file mode 100644 (file)
index 0000000..d35b9a5
--- /dev/null
@@ -0,0 +1,16 @@
+GuardedFile
+
+    A GuardedFile is a Zope File that is accessible *by proxy* only.
+
+    When a GuardedFile is created, all acquired permissions are unset. 
+    A proxy role is created in its container with the sole permission 
+    "View".
+
+    When the GuardedFile is deleted, its associated proxy role is also
+    removed.
+
+    In all other aspects a GuardedFile behaves exactly like a File.
+
+    
+    $Id: README.txt 299 2005-06-09 17:32:28Z heikki $
+    $Revision: 1.2 $
diff --git a/demo/Zope/lib/python/Products/GuardedFile/TODO.txt b/demo/Zope/lib/python/Products/GuardedFile/TODO.txt
new file mode 100644 (file)
index 0000000..8afc8c7
--- /dev/null
@@ -0,0 +1 @@
+1. Icon.
diff --git a/demo/Zope/lib/python/Products/GuardedFile/__init__.py b/demo/Zope/lib/python/Products/GuardedFile/__init__.py
new file mode 100644 (file)
index 0000000..f587c75
--- /dev/null
@@ -0,0 +1,25 @@
+"""GuardedFile.__init__
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL."""
+
+__version__ = '1.1'
+
+import GuardedFile
+
+def initialize(context):
+    try:
+        context.registerClass(
+            GuardedFile.GuardedFile,
+            constructors=(GuardedFile.manage_addForm, GuardedFile.manage_addGuardedFile)
+            #icon='folder.gif'
+            )
+        context.registerBaseClass(GuardedFile.GuardedFile)
+
+    except:
+        import sys, traceback, string
+        type, val, tb = sys.exc_info()
+        sys.stderr.write(string.join(
+            traceback.format_exception(type, val, tb),''))
+        del type, val, tb
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/add.dtml b/demo/Zope/lib/python/Products/GuardedFile/add.dtml
new file mode 100644 (file)
index 0000000..ed78fa6
--- /dev/null
@@ -0,0 +1,78 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _, form_title='Add GuardedFile')">
+
+<p class="form-help">
+ A GuardedFile is a Zope File that is accessible <em>by proxy</em> only.
+</p>
+
+<p class="form-help">
+ When a GuardedFile is created, all acquired permissions are unset.
+ A proxy role is created in its container with the sole permission 
+ "View".
+</p>
+
+<p class="form-help">
+ When the GuardedFile is deleted, its associated proxy role is also
+ removed.
+</p>
+
+<p class="form-help">
+ In all other aspects a GuardedFile behaves exactly like a File.
+</p>
+
+<p class="form-help">
+ You can create a new <dtml-var kind> using the form below. 
+ Select a file from your local computer by clicking the
+ <em>Browse</em> button. The file you select will be uploaded
+ to Zope as a <dtml-var kind>.
+</P>
+
+<FORM ACTION="manage_add<dtml-var Kind>" METHOD="POST"
+      ENCTYPE="multipart/form-data">
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Id
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="id" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-optional">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    File
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="file" name="file:string" size="25" value="" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    </td>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit" 
+     value=" Add " /> 
+    </div>
+    </td>
+  </tr>
+</TABLE>
+</FORM>
+
+<dtml-var manage_page_footer>
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/refresh.txt b/demo/Zope/lib/python/Products/GuardedFile/refresh.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/demo/Zope/lib/python/Products/GuardedFile/version.txt b/demo/Zope/lib/python/Products/GuardedFile/version.txt
new file mode 100644 (file)
index 0000000..1e5ebbf
--- /dev/null
@@ -0,0 +1 @@
+GuardedFile-1-1-0
diff --git a/demo/Zope/lib/python/Products/ZSmime/README.txt b/demo/Zope/lib/python/Products/ZSmime/README.txt
new file mode 100644 (file)
index 0000000..f43740b
--- /dev/null
@@ -0,0 +1,18 @@
+ZSmime enables Zope to generate S/MIME-signed/encrypted messages.
+
+ZSmime is useful where Zope accepts confidential information over the
+web, e.g., credit card information, Swiss bank account instructions, etc. 
+Such information can be protected by ZSmime and relayed off-site 
+immediately. This reduces the value of the information carried on-site
+and in turn reduces the effect of a successful attack against the site.
+
+Even if the S/MIME-protected information remains on-site, it is now 
+encrypted - this introduces additional cost in defeating the protection 
+and may mitigate the impact of a successful site penetration.
+
+ZSmime adds a DTML tag "dtml-smime" to Zope. 
+
+
+$Id: README.txt 299 2005-06-09 17:32:28Z heikki $
+$Revision: 1.1 $
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/SmimeTag.py b/demo/Zope/lib/python/Products/ZSmime/SmimeTag.py
new file mode 100644 (file)
index 0000000..142bec9
--- /dev/null
@@ -0,0 +1,104 @@
+"""ZSmime.SmimeTag
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__ = '1.2'
+
+# Zope tag stuff.
+from DocumentTemplate.DT_String import String
+from DocumentTemplate.DT_Util import *
+from DocumentTemplate.DT_Var import Var, Call
+
+# M2Crypto.
+from M2Crypto import BIO, SMIME, X509
+
+SmimeError = "SmimeTag Error"
+
+class SmimeTag:
+    """<dtml-smime>"""
+
+    name = 'smime'
+    blockContinuations = ()
+
+    def __init__(self, blocks):
+        tname, args, section = blocks[0]
+        self.section = section
+
+        args = parse_params(args, signer=None, recipients=None)
+        has_key = args.has_key
+
+        if has_key('signer'):
+            self.signer = args['signer']
+            try:
+                Call(self.signer)
+            except ParseError:
+                raise SmimeError, ('Invalid parameter "signer".')
+        else:
+            raise SmimeError, ('The parameter "signer" was not specified in tag.')
+
+        if has_key('recipients'):
+            self.recipients = args['recipients']
+            try:
+                Call(self.recipients)
+            except ParseError:
+                raise SmimeError, ('Invalid parameter "recipients".')
+        else:
+            raise SmimeError, ('The parameter "recipients" was not specified in tag.')
+
+
+    def render(self, md):
+        # Render the dtml block.
+        data = render_blocks(self.section.blocks, md)
+        data_bio = BIO.MemoryBuffer(data)
+
+        # Prepare to S/MIME.
+        s = SMIME.SMIME()
+
+        # Render the signer key, load into BIO. 
+        try:
+            signer = Var(self.signer).render(md)
+        except ParseError:
+            raise SmimeError, ('Invalid parameter "signer".')
+        signer_key_bio = BIO.MemoryBuffer(signer)
+        signer_cert_bio = BIO.MemoryBuffer(signer) # XXX Kludge.
+        
+        # Sign the data.
+        s.load_key_bio(signer_key_bio, signer_cert_bio)
+        p7 = s.sign(data_bio, flags=SMIME.PKCS7_TEXT)
+
+        # Recreate coz sign() has consumed the MemoryBuffer.
+        # May be cheaper to seek to start.
+        data_bio = BIO.MemoryBuffer(data)
+
+        # Render recipients, load into BIO.
+        try:
+            recip = Var(self.recipients).render(md)
+        except ParseError:
+            raise SmimeError, ('Invalid parameter "recipients".')
+        recip_bio = BIO.MemoryBuffer(recip)
+
+        # Load recipient certificates.
+        sk = X509.X509_Stack()
+        sk.push(X509.load_cert_bio(recip_bio))
+        s.set_x509_stack(sk)
+
+        # Set a cipher.
+        s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+        # Encrypt.
+        tmp_bio = BIO.MemoryBuffer()
+        s.write(tmp_bio, p7)
+        p7 = s.encrypt(tmp_bio)
+
+        # Finally, return the now signed/encrypted PKCS7.
+        out = BIO.MemoryBuffer()
+        s.write(out, p7)
+        return out.getvalue()
+
+
+    __call__ = render
+
+
+String.commands['smime'] = SmimeTag
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/__init__.py b/demo/Zope/lib/python/Products/ZSmime/__init__.py
new file mode 100644 (file)
index 0000000..ebb77f6
--- /dev/null
@@ -0,0 +1,9 @@
+"""ZSmime.__init__
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__='1.1'
+
+import SmimeTag
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/version.txt b/demo/Zope/lib/python/Products/ZSmime/version.txt
new file mode 100644 (file)
index 0000000..ac26a94
--- /dev/null
@@ -0,0 +1 @@
+ZSmime-1-0-1
diff --git a/demo/Zope/server.pem b/demo/Zope/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/Zope/starts b/demo/Zope/starts
new file mode 100644 (file)
index 0000000..2676243
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+umask 077
+reldir=`dirname $0`
+cwd=`cd $reldir; pwd`
+# Zope's event logger is controlled by the "EVENT_LOG_FILE" environment
+# variable.  If you don't have a "EVENT_LOG_FILE" environment variable
+# (or its older alias "STUPID_LOG_FILE") set, Zope will log to the standard
+# output.  For more information on EVENT_LOG_FILE, see doc/ENVIRONMENT.txt.
+ZLOGFILE=$EVENT_LOG_FILE
+if [ -z "$ZLOGFILE" ]; then
+        ZLOGFILE=$STUPID_LOG_FILE
+fi
+if [ -z "$ZLOGFILE" ]; then
+        EVENT_LOG_FILE=""
+        export EVENT_LOG_FILE
+fi
+exec /usr/local/bin/python2.1 $cwd/z2s.py -D "$@"
diff --git a/demo/Zope/starts.bat b/demo/Zope/starts.bat
new file mode 100755 (executable)
index 0000000..c8bd3b6
--- /dev/null
@@ -0,0 +1 @@
+"C:\pkg\zope260\bin\python.exe" "C:\pkg\zope260\z2s.py" -D %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/demo/Zope/utilities/x509_user.py b/demo/Zope/utilities/x509_user.py
new file mode 100644 (file)
index 0000000..1d350d6
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+"""
+This is a very simple program to manage the access_x509 database. The 
+overriding goal is program portability, hence its use of 'anydbm'.
+
+Invoke it thusly:
+
+    x509_user.py 
+        -u <username> 
+        [ -x <X.509 subject DN> ]
+        [ -f <database> ] 
+
+<username> is the Zope username; it must be present.
+
+<X.509 subject DN> is the X.509 certificate's subject distinguished name
+to associate with the user. If it is present, the association is created 
+or updated. If it is absent, the association is removed.
+
+<database> defaults to 'access_x509'.
+
+(I told you this is a dumb program.)
+
+
+To read the subject distinguished name from the certificate 'client.pem', 
+invoke 'openssl' thusly:
+
+    openssl x509 -subject -noout -in client.pem
+
+This produces the output:
+
+    subject=/C=SG/O=M2Crypto Client/CN=M2Crypto Client/Email=ngps@post1.com
+
+
+Next, invoke this tool:
+
+    x509_user.py -u superuser \\
+        -f "/C=SG/O=M2Crypto Client/CN=M2Crypto Client/Email=ngps@post1.com"
+
+This associates the user who owns client.pem to the Zope "superuser".
+
+
+Copyright (c) 2000 Ng Pheng Siong. This program is released under the ZPL.
+"""
+
+import anydbm, getopt, sys
+
+x509_db = 'access_x509'
+username = subject_dn = None
+
+argerr='Usage'
+
+optlist, optarg=getopt.getopt(sys.argv[1:], 'f:u:x:')   # ;-)
+for opt in optlist:
+    if '-f' in opt:
+        x509_db = opt[1]
+    elif '-u' in opt:
+        username = opt[1]
+    elif '-x' in opt:
+        subject_dn = opt[1]
+
+if username is None:
+    raise argerr, '\n' + __doc__
+
+db = anydbm.open(x509_db, 'cw')
+if subject_dn is None:
+    # Remove the association...
+    try:
+        subject_dn = db[username]
+        del db[subject_dn]
+        del db[username]
+    except:
+        pass
+else:
+    # Create/update the association.
+    db[subject_dn] = username
+    db[username] = subject_dn
+db.close()
+
diff --git a/demo/Zope/z2s.py b/demo/Zope/z2s.py
new file mode 100644 (file)
index 0000000..33885ab
--- /dev/null
@@ -0,0 +1,1078 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Zope 2 ZServer start-up file
+
+Usage: %(program)s [options] [environment settings]
+
+Options:
+
+  -h
+
+    Output this text.
+
+  -z path
+
+    The location of the Zope installation.
+    The default is the location of this script, %(here)s.
+
+  -Z 0 or 1
+
+    UNIX only! This option is ignored on Windows.
+
+    This option controls whether a management process will be created
+    that restarts Zope after a shutdown or crash.
+    
+    If the argument to -Z is non-null (e.g. "-Z1" or "-Zyes"), a
+    management process will be used.  If the argument to -Z is "-", or
+    "0", (e.g. "-Z-" or "-Z0"), a management process will not be used.
+    On UNIX, the default behavior is to create a separate management
+    process (e.g. -Z1) if the -Z option is not specified.
+
+    (Note: the -Z option in Zopes before Zope 2.6 used to be used to specify
+    a pidfile name for the management process.  This pidfile no longer
+    exists).
+
+  -t n
+
+    The number of threads to use, if ZODB3 is used. The default is
+    %(NUMBER_OF_THREADS)s.
+
+  -i n
+
+    Set the interpreter check interval. This integer value
+    determines how often the interpreter checks for periodic things
+    such as thread switches and signal handlers. The Zope default
+    is 500, but you may want to experiment with other values that
+    may increase performance in your particular environment.
+
+  -D
+
+    Run in Zope debug mode.  This causes the Zope process not to
+    detach from the controlling terminal, and is equivalent to
+    supplying the environment variable setting Z_DEBUG_MODE=1
+
+  -a ipaddress
+
+    The IP address to listen on.  If this is an empty string
+    (e.g. -a ''), then all addresses on the machine are used. The
+    default is %(IP_ADDRESS)s.
+
+  -d ipaddress
+
+    IP address of your DNS server. If this is an empty string
+    (e.g. -d ''), then IP addresses will not be logged. If you have
+    DNS service on your local machine then you can set this to
+    127.0.0.1.  The default is: %(DNS_IP)s.
+
+  -u username or uid number
+
+    The username to run ZServer as.  You may want to run ZServer as
+    a dedicated user.  This only works under Unix, and if ZServer
+    is started as root, and is required in that case.
+
+  -P [ipaddress:]number
+
+    Set the web, ftp and monitor port numbers simultaneously
+    as offsets from the number.  The web port number will be number+80.
+    The FTP port number will be number+21.  The monitor port number will
+    be number+99.
+
+    The number can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -P options can be provided to run multiple sets of servers.
+
+  -w port
+
+    The Web server (HTTP) port.  This defaults to %(HTTP_PORT)s. The
+    standard port for HTTP services is 80.  If this is a dash
+    (e.g. -w -), then HTTP is disabled.
+
+    The number can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -w options can be provided to run multiple servers.
+
+  -y port
+
+    The SSL Web server (HTTPS) port.  This defaults to %(HTTPS_PORT)s. The
+    standard port for HTTPS services is 443.  If this is a dash
+    (e.g. -y -), then HTTPS is disabled.
+
+    The number can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -y options can be provided to run multiple servers.
+
+  -W port
+
+    The "WebDAV source" port.  If this is a dash (e.g. -W -), then
+    "WebDAV source" is disabled.  The default is disabled.  Note that
+    this feature is a workaround for the lack of "source-link" support
+    in standard WebDAV clients.
+
+    The port can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -W options can be provided to run multiple servers.
+
+  -Y port
+
+    The "WebDAV source over HTTPS" port.  If this is a dash (e.g. -Y -), then
+    "WebDAV source over HTTPS" is disabled.  The default is disabled.  Note that
+    this feature is a workaround for the lack of "source-link" support
+    in standard WebDAV clients.
+
+    The port can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -Y options can be provided to run multiple servers.
+
+  -x
+
+    If present, this option causes Zope to run in X.509 certificate-based
+    authentication mode.
+
+  -C
+  --force-http-connection-close
+
+    If present, this option causes Zope to close all HTTP connections,
+    regardless of the 'Connection:' header (or lack of one) sent by
+    the client.
+
+  -f port
+
+    The FTP port.  If this is a dash (e.g. -f -), then FTP
+    is disabled.  The standard port for FTP services is 21.  The
+    default is %(FTP_PORT)s.
+
+    The port can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -f options can be provided to run multiple servers.
+
+  -p path
+
+    Path to the PCGI resource file.  The default value is
+    %(PCGI_FILE)s, relative to the Zope location.  If this is a dash
+    (-p -) or the file does not exist, then PCGI is disabled.
+
+  -F path_or_port
+
+    Either a port number (for inet sockets) or a path name (for unix
+    domain sockets) for the FastCGI Server.  If the flag and value are
+    not specified then the FastCGI Server is disabled.
+
+  -m port
+
+    The secure monitor server port. If this is a dash
+    (-m -), then the monitor server is disabled. The monitor server
+    allows interactive Python style access to a running ZServer. To
+    access the server see medusa/monitor_client.py or
+    medusa/monitor_client_win32.py. The monitor server password is the
+    same as the Zope emergency user password set in the 'access'
+    file. The default is to not start up a monitor server.
+
+    The port can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple -m options can be provided to run multiple servers.
+
+  --icp port
+
+    The ICP port. ICP can be used to distribute load between back-end
+    zope servers, if you are using an ICP-aware front-end proxy such
+    as Squid.
+
+    The port can be preeceeded by an ip address follwed by a colon
+    to specify an address to listen on. This allows different servers
+    to listen on different addresses.
+
+    Multiple --icp options can be provided to run multiple servers.
+
+  -l path
+
+    Path to the ZServer log file. If this is a relative path then the
+    log file will be written to the 'var' directory. The default is
+    %(LOG_FILE)s.
+
+  -r
+
+    Run ZServer is read-only mode. ZServer won't write anything to disk.
+    No log files, no pid files, nothing. This means that you can't do a
+    lot of stuff like use PCGI, and zdaemon. ZServer will log hits to
+    STDOUT and zLOG will log to STDERR.
+
+  -L
+
+    Enable locale (internationalization) support. The value passed for
+    this option should be the name of the locale to be used (see your
+    operating system documentation for locale information specific to
+    your system). If an empty string is passed for this option (-L ''),
+    Zope will set the locale to the user's default setting (typically
+    specified in the $LANG environment variable). If your Python
+    installation does not support the locale module, the requested
+    locale is not supported by your system or an empty string was
+    passed but no default locale can be found, an error will be raised
+    and Zope will not start.
+
+  -X
+
+    Disable servers. This might be used to effectively disable all
+    default server settings or previous server settings in the option
+    list before providing new settings. For example to provide just a
+    web server:
+
+      %(program)s -X -w80
+
+  -M file
+
+    Save detailed logging information to the given file.
+    This log includes separate entries for:
+
+      - The start of a request,
+      - The start of processing the request in an application thread,
+      - The start of response output, and
+      - The end of the request.
+
+Environment settings are of the form: NAME=VALUE.
+
+Note: you *must* use Python 2.1 or later!
+"""
+
+
+# This is required path hackery for the win32 binary distribution
+# that ensures that the bundled python libraries are used. In a
+# win32 binary distribution, the installer will have replaced the
+# marker string with the actual software home. If that has not
+# happened, then the path munging code is skipped.
+swhome=r'INSERT_SOFTWARE_HOME'
+if swhome != 'INSERT_SOFTWARE_HOME':
+    import sys
+    sys.path.insert(0, '%s/lib/python' % swhome)
+    sys.path.insert(1, '%s/bin/lib' % swhome)
+    sys.path.insert(2, '%s/bin/lib/plat-win' % swhome)
+    sys.path.insert(3, '%s/bin/lib/win32' % swhome)
+    sys.path.insert(4, '%s/bin/lib/win32/lib' % swhome)
+    sys.path.insert(5, '%s' % swhome)
+
+
+import os, sys, getopt, codecs, string
+import socket
+
+from types import StringType, IntType
+# workaround to allow unicode encoding conversions in DTML
+dummy = codecs.lookup('iso-8859-1')
+
+sys.setcheckinterval(500)
+
+program=sys.argv[0]
+here=os.path.join(os.getcwd(), os.path.split(program)[0])
+
+########################################################################
+# Configuration section
+
+## General configuration options
+##
+
+# This is the IP address of the network interface you want your servers to
+# be visible from.  This can be changed to '' to listen on all interfaces.
+IP_ADDRESS=''
+
+# IP address of your DNS server. Set to '' if you do not want to resolve
+# IP addresses. If you have DNS service on your local machine then you can
+# set this to '127.0.0.1'
+DNS_IP=''
+
+# User id to run ZServer as. Note that this only works under Unix, and if
+# ZServer is started by root. This no longer defaults to 'nobody' since
+# that can lead to a Zope file compromise.
+UID=None
+
+# Log file location. If this is a relative path, then it is joined the
+# the 'var' directory.
+LOG_FILE='Z2.log'
+
+## HTTP configuration
+##
+
+# Port for HTTP Server. The standard port for HTTP services is 80.
+HTTP_PORT=8080
+
+# Port for HTTPS Server. The standard port for HTTPS services is 443.
+HTTPS_PORT=8443
+
+# HTTP enivornment settings.
+HTTP_ENV={}
+
+# HTTPS enivornment settings.
+HTTPS_ENV={}
+
+# Should we close all HTTP connections, ignoring the (usually absent)
+# 'Connection:' header?
+FORCE_HTTP_CONNECTION_CLOSE=0
+
+# Port for the special "WebDAV source view" HTTP handler.  There is no
+# standard port for this handler, which is disabled by default.
+WEBDAV_SOURCE_PORT=[]
+
+# Port for the special "WebDAV source view over SSL" HTTP handler.  There is no
+# standard port for this handler, which is disabled by default.
+WEBDAV_SSL_SOURCE_PORT=[]
+
+# Should we use client X.509 certificate-based authentication?
+X509_REMOTE_USER=None
+
+## FTP configuration
+
+# Port for the FTP Server. The standard port for FTP services is 21.
+FTP_PORT=8021
+
+## PCGI configuration
+
+# You can configure the PCGI server manually, or have it read its
+# configuration information from a PCGI info file.
+PCGI_FILE='Zope.cgi'
+
+## Monitor configuration
+MONITOR_PORT=0
+
+## ICP configuration
+ICP_PORT=0
+
+# Module to be published, which must be Main or Zope
+MODULE='Zope'
+
+# The size of the thread pool, if ZODB3 is used.
+NUMBER_OF_THREADS=4
+
+# Localization support
+LOCALE_ID=None
+
+# Socket path or port for the FastCGI Server
+FCGI_PORT=None
+
+# Detailed log file
+DETAILED_LOG_FILE=''
+
+# Use a daemon process
+USE_DAEMON = 1
+
+#
+########################################################################
+
+########################################################################
+# Handle command-line arguments:
+
+def server_info(old, v, offset=0):
+    # interpret v as a port or address/port and get new value
+    if v == '-': v=''
+    l=v.find(':')
+    if l >= 0:
+        a=v[:l]
+        v=v[l+1:]
+    else:
+        a=IP_ADDRESS
+
+    if not v: return v
+
+    try:
+        v=int(v)
+        if v < 0: raise 'Invalid port', v
+        v=v+offset
+    except: raise 'Invalid port', v
+
+    if isinstance(old, IntType): old=[(a,v)]
+    else: old.append((a,v))
+
+    return old
+
+
+try:
+    python_version = sys.version.split()[0]
+    if python_version < '2.1':
+        raise 'Invalid python version', python_version
+    if python_version[:3] == '2.1':
+        if python_version[4:5] < '3':
+            import warnings
+            err = ('You are running Python version %s.  This Python version '
+                   'has known bugs that may cause Zope to run improperly. '
+                   'Consider upgrading to a Python in the 2.1 series '
+                   'with at least version number 2.1.3.  (Note that Zope does '
+                   'not yet run under any Python 2.2 version).' %
+                   python_version)
+            warnings.warn(err)
+    if python_version[:3] == '2.2':
+        import warnings
+        err = ('You are running Python version %s.  This Python version '
+               'has not yet been tested with Zope and you may experience '
+               'operational problems as a result.  Consider using '
+               'Python 2.1.3 instead.' % python_version)
+        warnings.warn(err)
+
+
+    opts, args = getopt.getopt(sys.argv[1:],
+                               'hz:Z:t:i:a:d:u:w:W:y:Y:x:f:p:m:Sl:2DP:rF:L:XM:C',
+                               ['icp=', 'force-http-connection-close'
+                               ])
+
+    DEBUG=0
+    READ_ONLY=0
+    if sys.platform == 'win32':
+        USE_DAEMON = 0
+        
+
+    # Get environment variables
+    for a in args:
+        if a.find('='):
+            a=a.split('=')
+            o=a[0]
+            v='='.join(a[1:])
+            if o:
+                os.environ[o]=v
+                HTTP_ENV[o]=v
+        else:
+            raise 'Invalid argument', a
+
+    for o, v in opts:
+        if o=='-z': here=v
+        elif o=='-Z':
+            if v in ('-', '0', ''):
+                USE_DAEMON=0
+            elif sys.platform != 'win32':
+                USE_DAEMON = 1
+        elif o=='-r': READ_ONLY=1
+        elif o=='-t':
+            try: v=int(v)
+            except: raise 'Invalid number of threads', v
+            NUMBER_OF_THREADS=v
+
+        elif o=='-i':
+            try: v=int(v)
+            except: raise 'Invalid value for -i option', v
+            sys.setcheckinterval(v)
+
+        elif o=='-a': IP_ADDRESS=v
+        elif o=='-d':
+            if v=='-': v=''
+            DNS_IP=v
+        elif o=='-u': UID=v
+        elif o=='-D':
+            os.environ['Z_DEBUG_MODE']='1'
+            DEBUG=1
+        elif o=='-S': sys.ZMANAGED=1
+        elif o=='-X':
+            MONITOR_PORT=HTTP_PORT=FTP_PORT=FCGI_PORT=ICP_PORT=0
+            WEBDAV_SOURCE_PORT=0
+            PCGI_FILE=''
+        elif o=='-m':
+            MONITOR_PORT=server_info(MONITOR_PORT, v)
+        elif o=='-w':
+            HTTP_PORT=server_info(HTTP_PORT, v)
+        elif o=='-y':
+            HTTPS_PORT=server_info(HTTPS_PORT, v)
+        elif o=='-C' or o=='--force-http-connection-close':
+            FORCE_HTTP_CONNECTION_CLOSE=1
+        elif o=='-W':
+            WEBDAV_SOURCE_PORT=server_info(WEBDAV_SOURCE_PORT, v)
+        elif o=='-Y':
+            WEBDAV_SSL_SOURCE_PORT=server_info(WEBDAV_SSL_SOURCE_PORT, v)
+        elif o=='-x':
+            if v in ('-', '0', ''):
+                X509_REMOTE_USER=None
+            else:
+                X509_REMOTE_USER=1
+        elif o=='-f':
+            FTP_PORT=server_info(FTP_PORT, v)
+        elif o=='-P':
+            HTTP_PORT=server_info(HTTP_PORT, v, 80)
+            FTP_PORT=server_info(FTP_PORT, v, 21)
+        elif o=='--icp':
+            ICP_PORT=server_info(ICP_PORT, v)
+
+        elif o=='-p':
+            if v=='-': v=''
+            PCGI_FILE=v
+        elif o=='-h':
+            print __doc__ % vars()
+            sys.exit(0)
+        elif o=='-2': MODULE='Main'
+        elif o=='-l': LOG_FILE=v
+        elif o=='-L':
+            if v: LOCALE_ID=v
+            else: LOCALE_ID=''
+        elif o=='-F':
+            if v=='-': v=''
+            FCGI_PORT=v
+        elif o=='-M': DETAILED_LOG_FILE=v
+
+except SystemExit: sys.exit(0)
+except:
+    print __doc__ % vars()
+    print
+    print 'Error:'
+    print "%s: %s" % (sys.exc_type, sys.exc_value)
+    sys.exit(1)
+
+#
+########################################################################
+
+########################################################################
+# OK, let's get going!
+
+# Jigger path:
+sys.path=[os.path.join(here,'lib','python'),here
+          ]+filter(None, sys.path)
+
+
+
+# Try to set the locale if specified on the command
+# line. If the locale module is not available or the
+# requested locale is not supported by the local
+# machine, raise an error so that the user is made
+# aware of the problem.
+
+def set_locale(val):
+    try:
+        import locale
+    except:
+        raise SystemExit, (
+            'The locale module could not be imported.\n'
+            'To use localization options, you must ensure\n'
+            'that the locale module is compiled into your\n'
+            'Python installation.'
+            )
+    try:
+        locale.setlocale(locale.LC_ALL, val)
+    except:
+        raise SystemExit, (
+            'The specified locale is not supported by your system.\n'
+            'See your operating system documentation for more\n'
+            'information on locale support.'
+            )
+if LOCALE_ID is not None:
+    set_locale(LOCALE_ID)
+
+import zdaemon
+# from this point forward we can use the zope logger
+# importing ZDaemon before importing ZServer causes ZServer logging
+# not to work.
+
+# Import ZServer before we open the database or get at interesting
+# application code so that ZServer's asyncore gets to be the
+# official one. Also gets SOFTWARE_HOME, INSTANCE_HOME, and CLIENT_HOME
+import ZServer
+
+# install signal handlers if on posix
+if os.name == 'posix':
+    from Signals import Signals
+    Signals.registerZopeSignals()
+
+# Location of the ZServer pid file. When Zope starts up it will write
+# its PID to this file.  If Zope is run under zdaemon control, zdaemon
+# will write to this pidfile instead of Zope.
+PID_FILE=os.path.join(CLIENT_HOME, 'Z2.pid')
+
+if USE_DAEMON and not READ_ONLY:
+    import App.FindHomes
+    sys.ZMANAGED=1
+    # zdaemon.run creates a process which "manages" the actual Zope
+    # process (restarts it if it dies).  The management process passes along
+    # signals that it receives to its child.
+    zdaemon.run(sys.argv, os.path.join(CLIENT_HOME, PID_FILE))
+
+os.chdir(CLIENT_HOME)
+
+def _warn_nobody():
+    zLOG.LOG("z2", zLOG.INFO, ("Running Zope as 'nobody' can compromise "
+                               "your Zope files; consider using a "
+                               "dedicated user account for Zope") )
+
+try:
+    # Import logging support
+    import zLOG
+    import ZLogger
+
+    if READ_ONLY:
+        if hasattr(zLOG, '_set_stupid_dest'):
+            zLOG._set_stupid_dest(sys.stderr)
+        else:
+            zLOG._stupid_dest = sys.stderr
+    else:
+        zLOG.log_write = ZLogger.ZLogger.log_write
+
+    if DETAILED_LOG_FILE:
+        from ZServer import DebugLogger
+        logfile=os.path.join(CLIENT_HOME, DETAILED_LOG_FILE)
+        zLOG.LOG('z2', zLOG.BLATHER,
+                 'Using detailed request log file %s' % logfile)
+        DL=DebugLogger.DebugLogger(logfile)
+        DebugLogger.log=DL.log
+        DebugLogger.reopen=DL.reopen
+        sys.__detailedlog=DL
+
+    # Import Zope (or Main)
+    if MODULE == 'Zope':
+        import Zope
+        Zope.startup()
+    else:
+        exec "import "+MODULE in {}
+
+    # Location of the ZServer log file. This file logs all ZServer activity.
+    # You may wish to create different logs for different servers. See
+    # medusa/logger.py for more information.
+    if not os.path.isabs(LOG_FILE):
+        LOG_PATH=os.path.join(CLIENT_HOME, LOG_FILE)
+    else:
+        LOG_PATH=LOG_FILE
+
+    # import ZServer stuff
+
+    # First, we need to increase the number of threads
+    if MODULE=='Zope':
+        from ZServer import setNumberOfThreads
+        setNumberOfThreads(NUMBER_OF_THREADS)
+
+    from ZServer import resolver, logger, asyncore
+
+    from ZServer import zhttp_server, zhttp_handler
+    from ZServer import zhttps_server, zhttps0_handler, zhttps_handler    
+    from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+    from ZServer import PCGIServer,FTPServer,FCGIServer
+
+    from ZServer import secure_monitor_server
+
+    from M2Crypto import SSL, Rand
+
+    ## ZServer startup
+    ##
+
+    ## In X509_REMOTE_USER mode, we log the client cert's subject DN.
+    if X509_REMOTE_USER:
+        
+        import base64, string, time
+
+        def log (self, bytes):
+            user_agent=self.get_header('user-agent')
+            if not user_agent: user_agent=''
+            referer=self.get_header('referer')
+            if not referer: referer=''  
+
+            get_peer_cert = getattr(self.channel, 'get_peer_cert', None)
+            if get_peer_cert is not None:
+                name = str(get_peer_cert().get_subject())
+            else:
+                name = 'Anonymous'
+            auth=self.get_header('Authorization')                
+            if auth is not None:
+                if string.lower(auth[:6]) == 'basic ':
+                    try: decoded=base64.decodestring(auth[6:])
+                    except base64.binascii.Error: decoded=''
+                    t = string.split(decoded, ':', 1)
+                    if len(t) < 2:
+                        name = 'Unknown (bad auth string)'
+                    else:
+                        name = t[0]
+
+            self.channel.server.logger.log (
+                self.channel.addr[0],
+                ' - %s [%s] "%s" %d %d "%s" "%s"\n' % (
+                    name,
+                    self.log_date_string (time.time()),
+                    self.request,
+                    self.reply_code,
+                    bytes,
+                    referer,
+                    user_agent
+                    )
+                )
+
+        from ZServer.medusa import http_server
+        http_server.http_request.log = log
+
+    # Resolver and Logger, used by other servers
+    if DNS_IP:
+        rs = resolver.caching_resolver(DNS_IP)
+    else:
+        rs=None
+
+    if READ_ONLY:
+        lg = logger.file_logger('-') # log to stdout
+        zLOG.LOG('z2', zLOG.BLATHER, 'Logging access log to stdout')
+    elif os.environ.has_key('ZSYSLOG_ACCESS'):
+        if os.environ.has_key("ZSYSLOG_ACCESS_FACILITY"):
+            lg = logger.syslog_logger(
+                os.environ['ZSYSLOG_ACCESS'],
+                facility=os.environ['ZSYSLOG_ACCESS_FACILITY'])
+        else:
+            lg = logger.syslog_logger(os.environ['ZSYSLOG_ACCESS'])
+        zLOG.LOG('z2', zLOG.BLATHER, 'Using local syslog access log')
+    elif os.environ.has_key('ZSYSLOG_ACCESS_SERVER'):
+        (addr, port) = os.environ['ZSYSLOG_ACCESS_SERVER'].split( ':')
+        lg = logger.syslog_logger((addr, int(port)))
+        zLOG.LOG('z2', zLOG.BLATHER, 'Using remote syslog access log')
+    else:
+        lg = logger.file_logger(LOG_PATH)
+        zLOG.LOG('z2', zLOG.BLATHER, 'Using access log file %s' % LOG_PATH)
+    sys.__lg = lg
+
+    port_err=('\n\nZope wants to use %(socktype)s port %(port)s for its '
+              '%(protocol)s service, but it is already in use by another '
+              'application on this machine.  Either shut the application down '
+              'which is using this port, or start Zope with a different '
+              '%(protocol)s port via the "%(switch)s" command-line switch.\n')
+
+    # HTTP Server
+    if HTTP_PORT:
+        if isinstance(HTTP_PORT, IntType): HTTP_PORT=((IP_ADDRESS, HTTP_PORT),)
+        for address, port in HTTP_PORT:
+            try:
+                hs = zhttp_server(
+                    ip=address,
+                    port=port,
+                    resolver=rs,
+                    logger_object=lg)
+            except socket.error, why:
+                if why[0] == 98: # address in use
+                    raise port_err % {'port':port,
+                                      'socktype':'TCP',
+                                      'protocol':'HTTP',
+                                      'switch':'-w'}
+                raise
+            # Handler for a published module. zhttp_handler takes 3 arguments:
+            # The name of the module to publish, and optionally the URI base
+            # which is basically the SCRIPT_NAME, and optionally a dictionary
+            # with CGI environment variables which override default
+            # settings. The URI base setting is useful when you want to
+            # publish more than one module with the same HTTP server. The CGI
+            # environment setting is useful when you want to proxy requests
+            # from another web server to ZServer, and would like the CGI
+            # environment to reflect the CGI environment of the other web
+            # server.
+            try:
+                del HTTP_ENV['HTTPS']
+            except KeyError:
+                pass
+            zh = zhttp_handler(MODULE, '', HTTP_ENV)
+            if FORCE_HTTP_CONNECTION_CLOSE:
+                zh._force_connection_close = 1
+            hs.install_handler(zh)
+
+    # HTTPS Server
+    if HTTPS_PORT:
+        ssl_ctx = SSL.Context('sslv23')
+        ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+        ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+        ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+        #ssl_ctx.set_allow_unknown_ca(1)
+        ssl_ctx.set_session_id_ctx(MODULE)
+        ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+        if X509_REMOTE_USER:
+            ssl_ctx.set_verify(SSL.verify_peer, 10)
+        else:
+            ssl_ctx.set_verify(SSL.verify_none, 10)
+        if type(HTTPS_PORT) is type(0): HTTPS_PORT=((IP_ADDRESS, HTTPS_PORT),)
+    
+        for address, port in HTTPS_PORT:
+            hss = zhttps_server(
+                ip=address,
+                port=port,
+                ssl_ctx=ssl_ctx,
+                resolver=rs,
+                logger_object=lg)
+    
+            try:
+                del HTTPS_ENV['HTTP']
+            except KeyError:
+                pass
+            HTTPS_ENV['HTTPS']='ON'
+
+            if X509_REMOTE_USER:
+                zsh = zhttps_handler(MODULE, '', HTTPS_ENV)
+            else:
+                zsh = zhttps0_handler(MODULE, '', HTTPS_ENV)
+            hss.install_handler(zsh)
+
+    # WebDAV source Server (runs HTTP, but munges request to return
+    #  'manage_FTPget').
+    if WEBDAV_SOURCE_PORT:
+        if isinstance(WEBDAV_SOURCE_PORT, IntType):
+            WEBDAV_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SOURCE_PORT),)
+        for address, port in WEBDAV_SOURCE_PORT:
+            try:
+                hs = zhttp_server(
+                    ip=address,
+                    port=port,
+                    resolver=rs,
+                    logger_object=lg)
+            except socket.error, why:
+                if why[0] == 98: # address in use
+                    raise port_err % {'port':port,
+                                      'socktype':'TCP',
+                                      'protocol':'WebDAV source',
+                                      'switch':'-W'}
+                raise
+
+            # Handler for a published module. zhttp_handler takes 3 arguments:
+            # The name of the module to publish, and optionally the URI base
+            # which is basically the SCRIPT_NAME, and optionally a dictionary
+            # with CGI environment variables which override default
+            # settings. The URI base setting is useful when you want to
+            # publish more than one module with the same HTTP server. The CGI
+            # environment setting is useful when you want to proxy requests
+            # from another web server to ZServer, and would like the CGI
+            # environment to reflect the CGI environment of the other web
+            # server.
+            zh = WebDAVSrcHandler(MODULE, '', HTTP_ENV)
+            hs.install_handler(zh)
+
+            # enable document retrieval of the document source on the
+            # standard HTTP port
+
+            clients = os.environ.get('WEBDAV_SOURCE_PORT_CLIENTS')
+            if clients:
+                import re
+                sys.WEBDAV_SOURCE_PORT_CLIENTS = re.compile(clients).search
+            else:
+                sys.WEBDAV_SOURCE_PORT_CLIENTS = None
+
+    # WebDAV-over-SSL source Server (runs HTTPS, but munges request to return
+    #  'manage_FTPget').
+    if WEBDAV_SSL_SOURCE_PORT:
+        ssl_ctx = SSL.Context('sslv23')
+        ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+        ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+        ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+        ssl_ctx.set_verify(SSL.verify_none, 10)
+        ssl_ctx.set_session_id_ctx(MODULE)
+        ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+        if type(WEBDAV_SSL_SOURCE_PORT) is type(0):
+            WEBDAV_SSL_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SSL_SOURCE_PORT),)
+        for address, port in WEBDAV_SSL_SOURCE_PORT:
+            hss = zhttps_server(
+                ip=address,
+                port=port,
+                ssl_ctx=ssl_ctx,
+                resolver=rs,
+                logger_object=lg)
+
+            try:
+                del HTTPS_ENV['HTTP']
+            except KeyError:
+                pass
+            HTTPS_ENV['HTTPS']='ON'
+
+            zsh = WebDAVSrcHandler(MODULE, '', HTTPS_ENV)
+            hss.install_handler(zsh)
+
+    # FTP Server
+    if FTP_PORT:
+        if isinstance(FTP_PORT, IntType): FTP_PORT=((IP_ADDRESS, FTP_PORT),)
+        for address, port in FTP_PORT:
+            try:
+                FTPServer(
+                   module=MODULE,
+                   ip=address,
+                   port=port,
+                   resolver=rs,
+                   logger_object=lg)
+            except socket.error, why:
+                if why[0] == 98: # address in use
+                    raise port_err % {'port':port,
+                                      'socktype':'TCP',
+                                      'protocol':'FTP',
+                                      'switch':'-f'}
+                raise
+
+    # PCGI Server
+    if PCGI_FILE and not READ_ONLY:
+        PCGI_FILE=os.path.join(here, PCGI_FILE)
+        if os.path.exists(PCGI_FILE):
+            zpcgi = PCGIServer(
+                module=MODULE,
+                ip=IP_ADDRESS,
+                pcgi_file=PCGI_FILE,
+                resolver=rs,
+                logger_object=lg)
+
+
+    # FastCGI Server
+    if FCGI_PORT and not READ_ONLY:
+        fcgiPort = None
+        fcgiPath = None
+        try:
+            fcgiPort = int(FCGI_PORT)
+        except ValueError:
+            fcgiPath = FCGI_PORT
+        try:
+            zfcgi = FCGIServer(module=MODULE,
+                               ip=IP_ADDRESS,
+                               port=fcgiPort,
+                               socket_file=fcgiPath,
+                               resolver=rs,
+                               logger_object=lg)
+        except socket.error, why:
+            if why[0] == 98: # address in use
+                raise port_err % {'port':fcgiPort,
+                                  'socktype':'TCP',
+                                  'protocol':'FastCGI',
+                                  'switch':'-F'}
+            raise
+
+
+    # Monitor Server
+    if MONITOR_PORT:
+        from AccessControl.User import emergency_user
+        if not hasattr(emergency_user, '__null_user__'):
+            pw = emergency_user._getPassword()
+        else:
+            pw = None
+            zLOG.LOG("z2", zLOG.WARNING, 'Monitor server not started'
+                     ' because no emergency user exists.')
+        if pw:
+            if isinstance(MONITOR_PORT, IntType):
+                MONITOR_PORT=((IP_ADDRESS, MONITOR_PORT),)
+            for address, port in MONITOR_PORT:
+                try:
+                    monitor=secure_monitor_server(
+                        password=pw,
+                        hostname=address,
+                        port=port)
+                except socket.error, why:
+                    if why[0] == 98: # address in use
+                        raise port_err % {'port':port,
+                                          'socktype':'TCP',
+                                          'protocol':'monitor server',
+                                          'switch':'-m'}
+                    raise
+
+    if ICP_PORT:
+        if isinstance(ICP_PORT, IntType): ICP_PORT=((IP_ADDRESS, ICP_PORT),)
+        from ZServer.ICPServer import ICPServer
+        for address, port in ICP_PORT:
+            try:
+                ICPServer(address,port)
+            except socket.error, why:
+                if why[0] == 98: # address in use
+                    raise port_err % {'port':port,
+                                      'socktype':'UDP',
+                                      'protocol':'ICP',
+                                      'switch':'--icp'}
+                raise
+
+    if not USE_DAEMON and not READ_ONLY:
+        if os.path.exists(PID_FILE): os.unlink(PID_FILE)
+        pf = open(PID_FILE, 'w')
+        pid='%s\n' % os.getpid()
+        pf.write(pid)
+        pf.close()
+
+    # Warn if we were started as nobody.
+    try:
+        import pwd
+        if os.getuid():
+            if pwd.getpwuid(os.getuid())[0] == 'nobody':
+                _warn_nobody()
+    except:
+        pass
+
+    # Drop root privileges if we have them, and do some sanity checking
+    # to make sure we're not starting with an obviously insecure setup.
+    try:
+        if os.getuid() == 0:
+            try:
+                import initgroups
+            except:
+                raise SystemExit, 'initgroups is required to safely setuid'
+            if UID == None:
+                raise SystemExit, ('A user was not specified to setuid '
+                                   'to; fix this to start as root (see '
+                                   'doc/SETUID.txt)')
+            import stat
+            client_home_stat = os.stat(CLIENT_HOME)
+            client_home_faults = []
+            if not (client_home_stat[stat.ST_MODE]&01000):
+                client_home_faults.append('does not have the sticky bit set')
+            if client_home_stat[stat.ST_UID] != 0:
+                client_home_faults.append('is not owned by root')
+            if client_home_faults:
+                client_home_faults.append('fix this to start as root (see '
+                                          'doc/SETUID.txt)')
+                err = '%s %s' % (CLIENT_HOME, ', '.join(client_home_faults))
+                raise SystemExit, err
+
+            try:
+                try:    UID = string.atoi(UID)
+                except: pass
+                gid = None
+                if isinstance(UID, StringType):
+                    uid = pwd.getpwnam(UID)[2]
+                    gid = pwd.getpwnam(UID)[3]
+                elif isinstance(UID, IntType):
+                    uid = pwd.getpwuid(UID)[2]
+                    gid = pwd.getpwuid(UID)[3]
+                    UID = pwd.getpwuid(UID)[0]
+                else:
+                    raise KeyError
+                if UID == 'nobody':
+                    _warn_nobody()
+                try:
+                    initgroups.initgroups(UID, gid)
+                    if gid is not None:
+                        try:
+                            os.setgid(gid)
+                        except OSError:
+                            pass
+                    os.setuid(uid)
+                except OSError:
+                    pass
+            except KeyError:
+                zLOG.LOG("z2", zLOG.ERROR, ("Can't find UID %s" % UID))
+    except AttributeError:
+        pass
+    except:
+        raise
+
+    # Check umask sanity if we're on posix.
+    if os.name == 'posix' and not os.environ.get('Z_DEBUG_MODE'):
+        # umask is silly, blame POSIX.  We have to set it to get its value.
+        current_umask = os.umask(0)
+        os.umask(current_umask)
+        if current_umask != 077:
+            current_umask = '%03o' % current_umask
+            zLOG.LOG("z2", zLOG.INFO, (
+                'Your umask of %s may be too permissive; for the security of '
+                'your Zope data, it is recommended you use 077' % current_umask
+                ))
+
+except:
+    # Log startup exception and tell zdaemon not to restart us.
+    try:
+        zLOG.LOG("z2", zLOG.PANIC, "Startup exception",
+                 error=sys.exc_info())
+    except: pass
+    sys.exit(0)
+
+# Start Medusa, Ye Hass!
+Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
+sys.ZServerExitCode=0
+asyncore.loop()
+Rand.save_file('%s/randpool.dat' % INSTANCE_HOME)
+sys.exit(sys.ZServerExitCode)
diff --git a/demo/Zope/z2s.py.diff b/demo/Zope/z2s.py.diff
new file mode 100644 (file)
index 0000000..f79cd94
--- /dev/null
@@ -0,0 +1,266 @@
+--- z2s.py     Sun Oct 26 17:51:00 2003
++++ /usr/local/home/ngps/pkg/zope261/z2.py     Thu Jan 30 22:41:42 2003
+@@ -105,21 +105,9 @@
+     Multiple -w options can be provided to run multiple servers.
+-  -y port
+-
+-    The SSL Web server (HTTPS) port.  This defaults to %(HTTPS_PORT)s. The
+-    standard port for HTTPS services is 443.  If this is a dash
+-    (e.g. -y -), then HTTPS is disabled.
+-
+-    The number can be preeceeded by an ip address follwed by a colon
+-    to specify an address to listen on. This allows different servers
+-    to listen on different addresses.
+-
+-    Multiple -y options can be provided to run multiple servers.
+-
+   -W port
+-    The "WebDAV source" port.  If this is a dash (e.g. -W -), then
++    The "WebDAV source" port.  If this is a dash (e.g. -w -), then
+     "WebDAV source" is disabled.  The default is disabled.  Note that
+     this feature is a workaround for the lack of "source-link" support
+     in standard WebDAV clients.
+@@ -130,24 +118,6 @@
+     Multiple -W options can be provided to run multiple servers.
+-  -Y port
+-
+-    The "WebDAV source over HTTPS" port.  If this is a dash (e.g. -Y -), then
+-    "WebDAV source over HTTPS" is disabled.  The default is disabled.  Note that
+-    this feature is a workaround for the lack of "source-link" support
+-    in standard WebDAV clients.
+-
+-    The port can be preeceeded by an ip address follwed by a colon
+-    to specify an address to listen on. This allows different servers
+-    to listen on different addresses.
+-
+-    Multiple -Y options can be provided to run multiple servers.
+-
+-  -x
+-
+-    If present, this option causes Zope to run in X.509 certificate-based
+-    authentication mode.
+-
+   -C
+   --force-http-connection-close
+@@ -316,15 +286,9 @@
+ # Port for HTTP Server. The standard port for HTTP services is 80.
+ HTTP_PORT=8080
+-# Port for HTTPS Server. The standard port for HTTPS services is 443.
+-HTTPS_PORT=8443
+-
+ # HTTP enivornment settings.
+ HTTP_ENV={}
+-# HTTPS enivornment settings.
+-HTTPS_ENV={}
+-
+ # Should we close all HTTP connections, ignoring the (usually absent)
+ # 'Connection:' header?
+ FORCE_HTTP_CONNECTION_CLOSE=0
+@@ -333,13 +297,6 @@
+ # standard port for this handler, which is disabled by default.
+ WEBDAV_SOURCE_PORT=[]
+-# Port for the special "WebDAV source view over SSL" HTTP handler.  There is no
+-# standard port for this handler, which is disabled by default.
+-WEBDAV_SSL_SOURCE_PORT=[]
+-
+-# Should we use client X.509 certificate-based authentication?
+-X509_REMOTE_USER=None
+-
+ ## FTP configuration
+ # Port for the FTP Server. The standard port for FTP services is 21.
+@@ -429,7 +386,7 @@
+     opts, args = getopt.getopt(sys.argv[1:],
+-                               'hz:Z:t:i:a:d:u:w:W:y:Y:x:f:p:m:Sl:2DP:rF:L:XM:C',
++                               'hz:Z:t:i:a:d:u:w:W:f:p:m:Sl:2DP:rF:L:XM:C',
+                                ['icp=', 'force-http-connection-close'
+                                ])
+@@ -486,19 +443,10 @@
+             MONITOR_PORT=server_info(MONITOR_PORT, v)
+         elif o=='-w':
+             HTTP_PORT=server_info(HTTP_PORT, v)
+-        elif o=='-y':
+-            HTTPS_PORT=server_info(HTTPS_PORT, v)
+         elif o=='-C' or o=='--force-http-connection-close':
+             FORCE_HTTP_CONNECTION_CLOSE=1
+         elif o=='-W':
+             WEBDAV_SOURCE_PORT=server_info(WEBDAV_SOURCE_PORT, v)
+-        elif o=='-Y':
+-            WEBDAV_SSL_SOURCE_PORT=server_info(WEBDAV_SSL_SOURCE_PORT, v)
+-        elif o=='-x':
+-            if v in ('-', '0', ''):
+-                X509_REMOTE_USER=None
+-            else:
+-                X509_REMOTE_USER=1
+         elif o=='-f':
+             FTP_PORT=server_info(FTP_PORT, v)
+         elif o=='-P':
+@@ -653,60 +601,14 @@
+     from ZServer import resolver, logger, asyncore
+     from ZServer import zhttp_server, zhttp_handler
+-    from ZServer import zhttps_server, zhttps0_handler, zhttps_handler    
+     from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+     from ZServer import PCGIServer,FTPServer,FCGIServer
+     from ZServer import secure_monitor_server
+-    from M2Crypto import SSL, Rand
+-
+     ## ZServer startup
+     ##
+-    ## In X509_REMOTE_USER mode, we log the client cert's subject DN.
+-    if X509_REMOTE_USER:
+-        
+-        import base64, string, time
+-
+-        def log (self, bytes):
+-            user_agent=self.get_header('user-agent')
+-            if not user_agent: user_agent=''
+-            referer=self.get_header('referer')
+-            if not referer: referer=''  
+-
+-            get_peer_cert = getattr(self.channel, 'get_peer_cert', None)
+-            if get_peer_cert is not None:
+-                name = str(get_peer_cert().get_subject())
+-            else:
+-                name = 'Anonymous'
+-            auth=self.get_header('Authorization')                
+-            if auth is not None:
+-                if string.lower(auth[:6]) == 'basic ':
+-                    try: decoded=base64.decodestring(auth[6:])
+-                    except base64.binascii.Error: decoded=''
+-                    t = string.split(decoded, ':', 1)
+-                    if len(t) < 2:
+-                        name = 'Unknown (bad auth string)'
+-                    else:
+-                        name = t[0]
+-
+-            self.channel.server.logger.log (
+-                self.channel.addr[0],
+-                ' - %s [%s] "%s" %d %d "%s" "%s"\n' % (
+-                    name,
+-                    self.log_date_string (time.time()),
+-                    self.request,
+-                    self.reply_code,
+-                    bytes,
+-                    referer,
+-                    user_agent
+-                    )
+-                )
+-
+-        from ZServer.medusa import http_server
+-        http_server.http_request.log = log
+-
+     # Resolver and Logger, used by other servers
+     if DNS_IP:
+         rs = resolver.caching_resolver(DNS_IP)
+@@ -766,51 +668,11 @@
+             # from another web server to ZServer, and would like the CGI
+             # environment to reflect the CGI environment of the other web
+             # server.
+-            try:
+-                del HTTP_ENV['HTTPS']
+-            except KeyError:
+-                pass
+             zh = zhttp_handler(MODULE, '', HTTP_ENV)
+             if FORCE_HTTP_CONNECTION_CLOSE:
+                 zh._force_connection_close = 1
+             hs.install_handler(zh)
+-    # HTTPS Server
+-    if HTTPS_PORT:
+-        ssl_ctx = SSL.Context('sslv23')
+-        ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+-        ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+-        ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+-        #ssl_ctx.set_allow_unknown_ca(1)
+-        ssl_ctx.set_session_id_ctx(MODULE)
+-        ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+-        if X509_REMOTE_USER:
+-            ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+-            #ssl_ctx.set_verify(SSL.verify_peer, 10)
+-        else:
+-            ssl_ctx.set_verify(SSL.verify_none, 10)
+-        if type(HTTPS_PORT) is type(0): HTTPS_PORT=((IP_ADDRESS, HTTPS_PORT),)
+-    
+-        for address, port in HTTPS_PORT:
+-            hss = zhttps_server(
+-                ip=address,
+-                port=port,
+-                ssl_ctx=ssl_ctx,
+-                resolver=rs,
+-                logger_object=lg)
+-    
+-            try:
+-                del HTTPS_ENV['HTTP']
+-            except KeyError:
+-                pass
+-            HTTPS_ENV['HTTPS']='ON'
+-
+-            if X509_REMOTE_USER:
+-                zsh = zhttps_handler(MODULE, '', HTTPS_ENV)
+-            else:
+-                zsh = zhttps0_handler(MODULE, '', HTTPS_ENV)
+-            hss.install_handler(zsh)
+-
+     # WebDAV source Server (runs HTTP, but munges request to return
+     #  'manage_FTPget').
+     if WEBDAV_SOURCE_PORT:
+@@ -854,34 +716,6 @@
+             else:
+                 sys.WEBDAV_SOURCE_PORT_CLIENTS = None
+-    # WebDAV-over-SSL source Server (runs HTTPS, but munges request to return
+-    #  'manage_FTPget').
+-    if WEBDAV_SSL_SOURCE_PORT:
+-        ssl_ctx = SSL.Context('sslv23')
+-        ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+-        ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+-        ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+-        ssl_ctx.set_verify(SSL.verify_none, 10)
+-        ssl_ctx.set_session_id_ctx(MODULE)
+-        ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+-        if type(WEBDAV_SSL_SOURCE_PORT) is type(0):
+-            WEBDAV_SSL_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SSL_SOURCE_PORT),)
+-        for address, port in WEBDAV_SSL_SOURCE_PORT:
+-            hss = zhttps_server(
+-                ip=address,
+-                port=port,
+-                ssl_ctx=ssl_ctx,
+-                resolver=rs,
+-                logger_object=lg)
+-
+-            try:
+-                del HTTPS_ENV['HTTP']
+-            except KeyError:
+-                pass
+-            HTTPS_ENV['HTTPS']='ON'
+-
+-            zsh = WebDAVSrcHandler(MODULE, '', HTTPS_ENV)
+-            hss.install_handler(zsh)
+     # FTP Server
+     if FTP_PORT:
+@@ -1072,8 +906,6 @@
+     sys.exit(0)
+ # Start Medusa, Ye Hass!
+-Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
+ sys.ZServerExitCode=0
+ asyncore.loop()
+-Rand.save_file('%s/randpool.dat' % INSTANCE_HOME)
+ sys.exit(sys.ZServerExitCode)
diff --git a/demo/Zope27/INSTALL.txt b/demo/Zope27/INSTALL.txt
new file mode 100644 (file)
index 0000000..b3728c5
--- /dev/null
@@ -0,0 +1,200 @@
+=========================================
+ ZServerSSL for Zope 2.7.0b2
+=========================================
+
+:Author: Ng Pheng Siong
+:Id: $Id$
+:Date: $Date$
+:Web-Site: http://sandbox.rulemaker.net/ngps/zope/zssl/
+
+.. contents::
+
+
+Directories
+-----------------------
+
+This distribution is contained in ``m2crypto-0.12/demo/Zope27/``. Its
+directory structure assumes the following:
+
+- Zope 2.7.0b2 is installed in <install-dir>.
+- An instance has been created in <instance-home>.
+
+
+<install-dir>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <install-dir>:
+
+- install_dir/lib/python/ZServer/HTTPS_Server.py
+- install_dir/lib/python/ZServer/medusa/https_server.py
+
+The following patch files are to be applied to the corresponding
+directories in your <install-dir>:
+
+- install_dir/lib/python/ZServer/__init__.py.patch
+- install_dir/lib/python/ZServer/component.xml.patch
+- install_dir/lib/python/ZServer/datatypes.py.patch
+
+
+<instance-home>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <instance-home>:
+
+- instance_home/ssl/ca.pem
+- instance_home/ssl/server.pem
+- instance_home/ssl/dh1024.pem
+
+These are example files. For more information on them, consult the
+ZServerSSL HOWTO for Zope 2.6.
+
+The following patch files are to be applied to the corresponding
+directories in your <instance-home>:
+
+- instance_home/README.txt.patch
+- instance_home/etc/zope.conf.patch
+
+(Patching README.txt is optional.)
+
+There appears to be a bug in Zope 2.7.0b2, where INSTANCE_HOME in a
+running Zope points to <install-dir>, not <instance-home>. Workaround
+this with the following:
+
+::
+
+    $ (cd <install-dir>; ln -s <instance-home>/ssl)
+
+
+Launch ZServerSSL
+-------------------
+
+::
+
+    $ <instance-home>/bin/runzope
+
+
+Testing
+---------
+
+Below, we assume your Zope server is running on ``localhost``.
+
+
+HTTPS
+~~~~~~~
+
+This testing is done with Mozilla 1.1 on FreeBSD.
+
+1. With a browser, connect to https://localhost:8443/. Browse
+   around. Check out your browser's HTTPS informational screens.
+
+2. Connect to https://localhost:8443/manage. Verify that you can
+   access Zope's management functionality.
+
+
+WebDAV-over-HTTPS
+~~~~~~~~~~~~~~~~~~~
+
+This testing is done with Cadaver 0.21.0 on FreeBSD.
+
+::
+
+    $ cadaver https://localhost:8443/
+    WARNING: Untrusted server certificate presented:
+    Issued to: M2Crypto, SG
+    Issued by: M2Crypto, SG
+    Do you wish to accept the certificate? (y/n) y
+    Authentication required for Zope on server `localhost':
+    Username: admin
+    Password: 
+    dav:/> ls
+    Listing collection `/': succeeded.
+    Coll:   Control_Panel                          0  Sep 28 00:38
+    Coll:   temp_folder                            0  Sep 28 17:30
+            acl_users                              0  Sep 28 00:38
+            browser_id_manager                     0  Sep 28 00:38
+            error_log                              0  Sep 28 00:38
+            index_html                            28  Sep 28 00:39
+            session_data_manager                   0  Sep 28 00:38
+            standard_error_message              1189  Sep 28 00:39
+            standard_html_footer                  18  Sep 28 00:39
+            standard_html_header                  82  Sep 28 00:39
+            standard_template.pt                 282  Sep 28 00:39
+    dav:/> quit
+    Connection to `localhost' closed.
+    $ 
+
+
+Python with M2Crypto 
+~~~~~~~~~~~~~~~~~~~~~~
+
+This testing is done with M2Crypto 0.12 and Python 2.2.3 on FreeBSD.
+
+HTTPS
+```````
+
+>>> from M2Crypto import Rand, SSL, m2urllib
+>>> url = m2urllib.FancyURLopener()
+>>> url.addheader('Connection', 'close')
+>>> u = url.open('https://127.0.0.1:8443/')
+send: 'GET / HTTP/1.1\r\nHost: 127.0.0.1:8443\r\nAccept-Encoding: identity\r\nUser-agent: Python-urllib/1.15\r\nConnection: close\r\n\r\n'
+reply: 'HTTP/1.1 200 OK\r\n'
+header: Server: ZServerSSL/0.12
+header: Date: Sun, 28 Sep 2003 09:40:14 GMT
+header: Content-Length: 3055
+header: Etag: 
+header: Content-Type: text/html
+header: Connection: close
+>>> while 1:
+...    data = u.read()
+...    if not data: break
+...    print data
+... 
+
+::
+
+    [blah blah blah]
+    
+    <p>
+    Go directly to the <a href="/manage" target="_top">
+    Zope Management Interface</a> if you'd like to start working with Zope 
+    right away.  <strong>NOTE:  Some versions of Microsoft Internet Explorer,
+    (specifically IE 5.01 and early versions of IE 5.5) may have problems
+    displaying Zope management pages.  If you cannot view the management pages,
+    try upgrading your IE installation to the latest release version, or use
+    a different browser.</strong>
+    </p>
+    </li>
+    
+    <li>
+    <p>
+    Find out about <a href="http://www.zope.com/" target="_new">Zope 
+    Corporation</a>, the publishers of Zope.
+    </p>
+    </li>
+    
+    </ul>
+    
+    </body>
+    </html>
+
+>>> u.close()
+>>> 
+
+
+XMLRPC-over-HTTPS
+```````````````````
+
+>>> from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+>>> zs = Server('https://127.0.0.1:8443/', SSL_Transport())
+>>> print zs.propertyMap()
+[{'type': 'string', 'id': 'title', 'mode': 'w'}]
+>>> 
+
+
+Conclusion
+------------
+
+Yes, it works! ;-)
+
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py b/demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py
new file mode 100644 (file)
index 0000000..49b6177
--- /dev/null
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2000-2003, Ng Pheng Siong. All Rights Reserved.
+# This file is derived from Zope's ZServer/HTTPServer.py.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""
+Medusa HTTPS server for Zope
+
+changes from Medusa's http_server:
+
+    Request Threads -- Requests are processed by threads from a thread
+    pool.
+    
+    Output Handling -- Output is pushed directly into the producer
+    fifo by the request-handling thread. The HTTP server does not do
+    any post-processing such as chunking.
+
+    Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+    which mutiple requests come in on the same channel, before
+    responses are sent back. When requests are pipelined, the client
+    doesn't wait for the response before sending another request. The
+    server must ensure that responses are sent back in the same order
+    as requests are received.
+
+
+changes from Zope's HTTP server:
+
+    Well, this is a *HTTPS* server :)
+
+    X.509 certificate-based authentication -- When this is in force,
+    zhttps_handler, a subclass of zhttp_handler, is installed.  The
+    https server is configured to request an X.509 certificate from
+    the client. When the request reaches zhttps_handler, it sets
+    REMOTE_USER to the client's subject distinguished name (DN) from
+    the certificate. Zope's REMOTE_USER machinery takes care of the
+    rest, e.g., in conjunction with the RemoteUserFolder product.
+    
+""" 
+
+import sys, time, types
+
+from PubCore import handle
+import asyncore
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION
+from HTTPServer import zhttp_handler
+from zLOG import register_subsystem
+
+from M2Crypto import SSL
+from medusa.https_server import https_server, https_channel
+from asyncore import dispatcher
+
+
+ZSERVER_SSL_VERSION='0.12'
+
+register_subsystem('ZServer HTTPS_Server')
+
+
+class zhttps0_handler(zhttp_handler):
+    "zhttps0 handler - sets SSL request headers a la mod_ssl"
+
+    def __init__ (self, module, uri_base=None, env=None):
+        zhttp_handler.__init__(self, module, uri_base, env)
+
+    def get_environment(self, request):
+        env = zhttp_handler.get_environment(self, request)
+        env['SSL_CIPHER'] = request.channel.get_cipher()
+        return env
+
+
+class zhttps_handler(zhttps0_handler):
+    "zhttps handler - sets REMOTE_USER to user's X.509 certificate Subject DN"
+
+    def __init__ (self, module, uri_base=None, env=None):
+        zhttps0_handler.__init__(self, module, uri_base, env)
+
+    def get_environment(self, request):
+        env = zhttps0_handler.get_environment(self, request)
+        peer = request.channel.get_peer_cert()
+        if peer is not None:
+            env['REMOTE_USER'] = str(peer.get_subject())
+        return env
+
+
+class zhttps_channel(https_channel):
+    "https channel"
+
+    closed=0
+    zombie_timeout=100*60 # 100 minutes
+    
+    def __init__(self, server, conn, addr):
+        https_channel.__init__(self, server, conn, addr)
+        self.queue=[]
+        self.working=0
+        self.peer_found=0
+    
+    def push(self, producer, send=1):
+        # this is thread-safe when send is false
+        # note, that strings are not wrapped in 
+        # producers by default
+        if self.closed:
+            return
+        self.producer_fifo.push(producer)
+        if send: self.initiate_send()
+        
+    push_with_producer=push
+
+    def work(self):
+        "try to handle a request"
+        if not self.working:
+            if self.queue:
+                self.working=1
+                try: module_name, request, response=self.queue.pop(0)
+                except: return
+                handle(module_name, request, response)
+        
+    def close(self):
+        self.closed=1
+        while self.queue:
+            self.queue.pop()
+        if self.current_request is not None:
+            self.current_request.channel=None # break circ refs
+            self.current_request=None
+        while self.producer_fifo:
+            p=self.producer_fifo.first()
+            if p is not None and type(p) != types.StringType:
+                p.more() # free up resources held by producer
+            self.producer_fifo.pop()
+        self.del_channel()
+        #self.socket.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+        self.socket.close()
+
+    def done(self):
+        "Called when a publishing request is finished"
+        self.working=0
+        self.work()
+
+    def kill_zombies(self):
+        now = int (time.time())
+        for channel in asyncore.socket_map.values():
+            if channel.__class__ == self.__class__:
+                if (now - channel.creation_time) > channel.zombie_timeout:
+                    channel.close()
+
+
+class zhttps_server(https_server):    
+    "https server"
+    
+    SERVER_IDENT='ZServerSSL/%s' % (ZSERVER_SSL_VERSION,)
+    
+    channel_class = zhttps_channel
+    shutup = 0
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        self.shutup = 1
+        https_server.__init__(self, ip, port, ssl_ctx, resolver, logger_object)
+        self.ssl_ctx = ssl_ctx
+        self.shutup = 0        
+        self.log_info('(%s) HTTPS server started at %s\n'
+                      '\tHostname: %s\n\tPort: %d' % (
+                        self.SERVER_IDENT,
+                        time.ctime(time.time()),
+                        self.server_name,
+                        self.server_port
+                        ))
+        
+    def log_info(self, message, type='info'):
+        if self.shutup: return
+        dispatcher.log_info(self, message, type)
+
+    def readable(self):
+        return self.accepting and \
+                len(asyncore.socket_map) < CONNECTION_LIMIT
+
+    def listen(self, num):
+        # override asyncore limits for nt's listen queue size
+        self.accepting = 1
+        return self.socket.listen (num)
+
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch b/demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch
new file mode 100644 (file)
index 0000000..0ba84b7
--- /dev/null
@@ -0,0 +1,10 @@
+--- __init__.py.org    Sat Sep 27 20:23:00 2003
++++ __init__.py        Sun Oct 26 18:01:27 2003
+@@ -68,6 +68,7 @@
+ import asyncore
+ from medusa import resolver, logger
+ from HTTPServer import zhttp_server, zhttp_handler
++from HTTPS_Server import zhttps_server, zhttps0_handler, zhttps_handler
+ from PCGIServer import PCGIServer
+ from FCGIServer import FCGIServer
+ from FTPServer import FTPServer
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch b/demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch
new file mode 100644 (file)
index 0000000..364d91f
--- /dev/null
@@ -0,0 +1,28 @@
+--- component.xml.org  Sat Sep 27 20:21:22 2003
++++ component.xml      Sat Sep 27 21:11:26 2003
+@@ -21,6 +21,25 @@
+      </key>
+   </sectiontype>
++  <sectiontype name="https-server"
++               datatype=".HTTPS_ServerFactory"
++               implements="ZServer.server">
++     <key name="address" datatype="inet-address"/>
++     <key name="force-connection-close" datatype="boolean" default="off"/>
++     <key name="webdav-source-clients">
++       <description>
++         Regular expression used to identify clients who should
++         receive WebDAV source responses to GET requests.
++       </description>
++     </key>
++     <key name="x509-remote-user" datatype="boolean" default="on">
++       <description>
++         If "on", request client X.509 certificate and set REMOTE_USER to
++       said certificate's Subject Distinguished Name.
++       </description>
++     </key>
++  </sectiontype>
++
+   <sectiontype name="webdav-source-server"
+                datatype=".WebDAVSourceServerFactory"
+                implements="ZServer.server">
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch b/demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch
new file mode 100644 (file)
index 0000000..b2ad9c8
--- /dev/null
@@ -0,0 +1,59 @@
+--- datatypes.py.org   Sat Sep 27 20:21:15 2003
++++ datatypes.py       Sun Oct 26 21:19:58 2003
+@@ -72,7 +72,56 @@
+     def createHandler(self):
+         from ZServer import HTTPServer
++        try:
++            del self.cgienv['HTTPS']
++        except KeyError:
++            pass
+         return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
++
++
++class HTTPS_ServerFactory(HTTPServerFactory):
++    def __init__(self, section):
++        HTTPServerFactory.__init__(self, section)
++        self.x509_remote_user = section.x509_remote_user
++        from M2Crypto import Rand, SSL
++        Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
++        ssl_ctx = SSL.Context('sslv23')
++        ssl_ctx.load_cert_chain('%s/ssl/server.pem' % INSTANCE_HOME)
++        ssl_ctx.load_verify_locations('%s/ssl/ca.pem' % INSTANCE_HOME,'')
++        ssl_ctx.load_client_CA('%s/ssl/ca.pem' % INSTANCE_HOME)
++        ssl_ctx.set_session_id_ctx('Zope 2.7.0b2')
++        ssl_ctx.set_tmp_dh('%s/ssl/dh1024.pem' % INSTANCE_HOME)
++        if self.x509_remote_user:
++            ssl_ctx.set_verify(SSL.verify_peer, 10)
++        else:
++            ssl_ctx.set_verify(SSL.verify_none, 10)
++        self.ssl_ctx = ssl_ctx
++
++    def create(self):
++        from ZServer import HTTPS_Server
++        from ZServer.AccessLogger import access_logger
++        handler = self.createHandler()
++        handler._force_connection_close = self.force_connection_close
++        if self.webdav_source_clients:
++            handler.set_webdav_source_clients(self.webdav_source_clients)
++        server = HTTPS_Server.zhttps_server(ip=self.host, port=self.port,
++                                            ssl_ctx=self.ssl_ctx,
++                                            resolver=self.dnsresolver,
++                                            logger_object=access_logger)
++        server.install_handler(handler)
++        return server
++
++    def createHandler(self):
++        from ZServer import HTTPS_Server
++        try:
++            del self.cgienv['HTTP']
++        except KeyError:
++            pass
++        self.cgienv['HTTPS'] = 'ON'
++        if self.x509_remote_user:
++            return HTTPS_Server.zhttps_handler(self.module, '', self.cgienv)
++        else:
++            return HTTPS_Server.zhttps0_handler(self.module, '', self.cgienv)
+ class WebDAVSourceServerFactory(HTTPServerFactory):
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py b/demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py
new file mode 100644 (file)
index 0000000..84a0197
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server. 
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+    ac_in_buffer_size = 1 << 16
+
+    def __init__(self, server, conn, addr):
+        http_server.http_channel.__init__(self, server, conn, addr)
+
+    def send(self, data):
+        try:
+            result = self.socket._write_nbio(data)
+            if result <= 0:
+                return 0
+            else:
+                self.server.bytes_out.increment(result)
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), why))
+            return 0
+
+    def recv(self, buffer_size):
+        try:
+            result = self.socket._read_nbio(buffer_size)
+            if result is None:
+                return ''
+            elif result == '':
+                self.close()
+                return ''
+            else:
+                self.server.bytes_in.increment(len(result))
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), why))
+            return ''
+
+
+class https_server(http_server.http_server):
+
+    SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+    channel_class=https_channel
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+        self.ssl_ctx=ssl_ctx
+        
+    def handle_accept(self):
+        # Cribbed from http_server.
+        self.total_clients.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            # linux: on rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            sys.stderr.write ('warning: server accept() threw an exception\n')
+            return
+
+        # Turn the vanilla socket into an SSL connection.
+        try:
+            ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+            ssl_conn._setup_ssl(addr)
+            ssl_conn.accept_ssl()
+            self.channel_class(self, ssl_conn, addr)
+        except SSL.SSLError:
+            pass
+
+    def writeable(self):
+        return 0
+
diff --git a/demo/Zope27/instance_home/README.txt.patch b/demo/Zope27/instance_home/README.txt.patch
new file mode 100644 (file)
index 0000000..0d39005
--- /dev/null
@@ -0,0 +1,7 @@
+--- README.txt.org     Sat Sep 27 20:56:11 2003
++++ README.txt Sat Sep 27 20:56:44 2003
+@@ -7,3 +7,4 @@
+   log/         Log files
+   Products/    Installed products specific to the instance
+   var/         Run-time data files, including the object database
++  ssl/               ZServerSSL data files
diff --git a/demo/Zope27/instance_home/etc/zope.conf.patch b/demo/Zope27/instance_home/etc/zope.conf.patch
new file mode 100644 (file)
index 0000000..cc9b093
--- /dev/null
@@ -0,0 +1,16 @@
+--- zope.conf.org      Sat Sep 27 21:03:22 2003
++++ zope.conf  Sat Sep 27 21:05:08 2003
+@@ -650,6 +650,13 @@
+   # force-connection-close on
+ </http-server>
++<https-server>
++  # valid keys are "address", "force-connection-close" and "x509-remote-user"
++  address 8443
++  # force-connection-close on
++  x509-remote-user on
++</https-server>
++
+ <ftp-server>
+   # valid key is "address"
+   address 8021
diff --git a/demo/Zope27/instance_home/ssl/ca.pem b/demo/Zope27/instance_home/ssl/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/Zope27/instance_home/ssl/dh1024.pem b/demo/Zope27/instance_home/ssl/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/Zope27/instance_home/ssl/server.pem b/demo/Zope27/instance_home/ssl/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ZopeX3/INSTALL.txt b/demo/ZopeX3/INSTALL.txt
new file mode 100644 (file)
index 0000000..67adbc8
--- /dev/null
@@ -0,0 +1,73 @@
+=========================================
+ ZServerSSL for ZopeX3.0.0 
+=========================================
+
+:Author: Ng Pheng Siong
+:Id: $Id: INSTALL.txt 354 2006-02-25 00:13:15Z heikki $
+:Date: $Date$
+:Web-Site: http://sandbox.rulemaker.net/ngps/zope/zssl/
+
+.. contents::
+
+
+Directories
+-----------------------
+
+Directory structure assumes the following:
+
+- ZopeX3.0.0 is installed in <install-dir>.
+- An instance has been created in <instance-home>.
+
+
+<install-dir>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <install-dir>:
+
+- install_dir/lib/python/zope/server/http/https_server.py
+- install_dir/lib/python/zope/server/http/https_serverchannel.py
+- install_dir/lib/python/zope/server/http/publisherhttps_server.py
+- install_dir/lib/python/zope/app/server/https.py
+
+
+<instance-home>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <instance-home>:
+
+- instance_home/ssl/ca.pem
+- instance_home/ssl/server.pem
+- instance_home/ssl/dh1024.pem
+
+These are example files. For more information on them, consult the
+ZServerSSL HOWTO for Zope 2.6.
+
+The following patch files are to be applied to the corresponding
+directories:
+
+- install_dir/lib/python/zope/app/server/configure.zcml.patch
+- instance_home/etc/zope.conf.patch
+
+
+Launch ZServerSSL
+-------------------
+
+::
+
+    $ <instance-home>/bin/runzope
+
+
+Testing
+---------
+
+This section TDB. I have tested ZServerSSL for Zope 3 with 'openssl
+s_client' and 'openssl s_client -nbio' successfully.
+
+
+Conclusion
+------------
+
+Yes, it works! ;-)
+
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch b/demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch
new file mode 100644 (file)
index 0000000..8d8f031
--- /dev/null
@@ -0,0 +1,28 @@
+--- configure.zcml.org Mon Sep 27 16:10:47 2004
++++ configure.zcml     Mon Sep 27 16:11:42 2004
+@@ -5,6 +5,12 @@
+       provides="zope.app.applicationcontrol.interfaces.IServerControl" />
+   <utility
++      name="HTTPS"
++      component=".https.https"
++      provides=".servertype.IServerType"
++      />
++
++  <utility
+       name="HTTP"
+       component=".http.http"
+       provides=".servertype.IServerType"
+@@ -13,6 +19,12 @@
+   <utility
+       name="PostmortemDebuggingHTTP"
+       component=".http.pmhttp"
++      provides=".servertype.IServerType"
++      />
++
++  <utility
++      name="PostmortemDebuggingHTTPS"
++      component=".https.pmhttps"
+       provides=".servertype.IServerType"
+       />
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py b/demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py
new file mode 100644 (file)
index 0000000..2278f3e
--- /dev/null
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS server factories
+
+$Id: https.py 240 2004-10-02 12:40:14Z ngps $
+"""
+
+from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
+from zope.app.server.servertype import ServerType
+from zope.server.http.commonaccesslogger import CommonAccessLogger
+from zope.server.http.publisherhttps_server import PMDBHTTPS_Server
+from zope.server.http.publisherhttps_server import PublisherHTTPS_Server
+
+https = ServerType(PublisherHTTPS_Server,
+                  HTTPPublicationRequestFactory,
+                  CommonAccessLogger,
+                  8443, True)
+
+pmhttps = ServerType(PMDBHTTPS_Server,
+                    HTTPPublicationRequestFactory,
+                    CommonAccessLogger,
+                    8376, True)
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py
new file mode 100644 (file)
index 0000000..9e1eb3e
--- /dev/null
@@ -0,0 +1,104 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server
+
+This is a HTTPS version of HTTPServer.
+
+$Id: https_server.py 240 2004-10-02 12:40:14Z ngps $
+"""
+
+import asyncore, logging, os.path
+
+from zope.server.http.httpserver import HTTPServer
+from zope.server.http.https_serverchannel import HTTPS_ServerChannel
+from M2Crypto import SSL, version
+
+
+# 2004-09-27, ngps:
+# 'sslv2' or 'sslv23' interoperates with Firefox and IE.
+# 'sslv3' or 'tlsv1' doesn't.
+def make_ssl_context(dir, ssl_proto='sslv23'):
+    sslctx = SSL.Context(ssl_proto)
+    sslctx.load_cert(os.path.join(dir, 'server.pem'))
+    sslctx.load_verify_locations(os.path.join(dir, 'ca.pem'))
+    sslctx.load_client_CA(os.path.join(dir, 'ca.pem'))
+    sslctx.set_verify(SSL.verify_none, 10)
+    sslctx.set_session_id_ctx('someblahblahthing')
+    sslctx.set_tmp_dh(os.path.join(dir, 'dh1024.pem'))
+    #sslctx.set_info_callback() # debugging only; not thread-safe
+    return sslctx
+
+
+class HTTPS_Server(HTTPServer):
+    """This is a generic HTTPS Server."""
+
+    channel_class = HTTPS_ServerChannel
+    SERVER_IDENT = 'zope.server.zserverssl_https'
+
+    def __init__(self, ip, port, ssl_ctx=None, task_dispatcher=None, adj=None, start=1,
+                 hit_log=None, verbose=0):
+        HTTPServer.__init__(self, ip, port, task_dispatcher, adj, start, hit_log, verbose)
+        if ssl_ctx is None:
+            self.ssl_ctx = make_ssl_context(os.path.realpath(__file__))
+        else: 
+            self.ssl_ctx = ssl_ctx
+
+    def executeRequest(self, task):
+        """Execute an HTTP request."""
+        # This is a default implementation, meant to be overridden.
+        body = "The HTTPS server is running!\r\n" * 10
+        task.response_headers['Content-Type'] = 'text/plain'
+        task.response_headers['Content-Length'] = str(len(body))
+        task.write(body)
+
+    def handle_accept(self):
+        """See zope.server.interfaces.IDispatcherEventHandler"""
+        try:
+            v = self.accept()
+            if v is None:
+                return
+            conn, addr = v
+        except socket.error:
+            # Linux: On rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            if self.adj.log_socket_errors:
+                self.log_info ('warning: server accept() threw an exception',
+                               'warning')
+            return
+        for (level, optname, value) in self.adj.socket_options:
+            conn.setsockopt(level, optname, value)
+        # Turn the vanilla socket into an SSL connection.
+        try:
+            ssl_conn = SSL.Connection(self.ssl_ctx, conn)
+            ssl_conn._setup_ssl(addr)
+            ssl_conn.accept_ssl()
+            self.channel_class(self, ssl_conn, addr, self.adj)
+        except SSL.SSLError, why:
+            self.log_info('accept: cannot make SSL connection %s' % (why,), 'warning')
+            pass
+
+
+
+if __name__ == '__main__':
+
+    from zope.server.taskthreads import ThreadedTaskDispatcher
+    td = ThreadedTaskDispatcher()
+    td.setThreadCount(4)
+    HTTPS_Server('', 8443, ssl_ctx=None, task_dispatcher=td, verbose=1)
+
+    try:
+        import asyncore
+        while 1:
+            asyncore.poll(5)
+
+    except KeyboardInterrupt:
+        print 'shutting down...'
+        td.shutdown()
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py
new file mode 100644 (file)
index 0000000..c33a84d
--- /dev/null
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server Channel
+
+$Id: https_serverchannel.py 240 2004-10-02 12:40:14Z ngps $
+"""
+from zope.server.serverchannelbase import ServerChannelBase
+from zope.server.http.httptask import HTTPTask
+from zope.server.http.httprequestparser import HTTPRequestParser
+from zope.server.http.httpserverchannel import HTTPServerChannel
+from M2Crypto import SSL
+
+
+class HTTPS_ServerChannel(HTTPServerChannel):
+    """HTTPS-specific Server Channel"""
+
+    task_class = HTTPTask
+    parser_class = HTTPRequestParser
+
+    def send(self, data):
+        try:
+            result = self.socket._write_nbio(data)
+            if result <= 0:
+                return 0
+            else:
+                #self.server.bytes_out.increment(result)
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), why), 'warning')
+            return 0
+
+    def recv(self, buffer_size):
+        try:
+            result = self.socket._read_nbio(buffer_size)
+            if result is None:
+                return ''
+            elif result == '':
+                self.close()
+                return ''
+            else:
+                #self.server.bytes_in.increment(len(result))
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), why), 'warning')
+            return ''
+
+
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py
new file mode 100644 (file)
index 0000000..aa8738d
--- /dev/null
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server that uses the Zope Publisher for executing a task.
+
+$Id: publisherhttps_server.py 240 2004-10-02 12:40:14Z ngps $
+"""
+import os.path, sys
+from zope.server.http.https_server import HTTPS_Server, make_ssl_context
+from zope.publisher.publish import publish
+
+
+def get_instance_ssldir():
+    # This is real cheesy: It seems Zope3 doesn't have convenient
+    # programmatic access to INSTANCE_HOME. This code relies on zopectl
+    # setting the first entry of PYTHONPATH to $INSTANCE_HOME/lib/python.
+    return os.path.join(os.path.dirname(os.path.dirname(sys.path[0])), 'ssl')
+
+
+class PublisherHTTPS_Server(HTTPS_Server):
+    """Zope Publisher-specific HTTPS Server"""
+
+    def __init__(self, request_factory, sub_protocol=None, *args, **kw):
+
+        # The common HTTP
+        self.request_factory = request_factory
+
+        # An HTTP server is not limited to serving up HTML; it can be
+        # used for other protocols, like XML-RPC, SOAP and so as well
+        # Here we just allow the logger to output the sub-protocol type.
+        if sub_protocol:
+            self.SERVER_IDENT += ' (%s)' %str(sub_protocol)
+
+        kw['ssl_ctx'] = make_ssl_context(get_instance_ssldir())
+        HTTPS_Server.__init__(self, *args, **kw)
+
+    def executeRequest(self, task):
+        """Overrides HTTPServer.executeRequest()."""
+        env = task.getCGIEnvironment()
+        env['HTTPS'] = 'ON'
+        try:
+            del env['HTTP']
+        except KeyError:
+            pass
+        instream = task.request_data.getBodyStream()
+
+        request = self.request_factory(instream, task, env)
+        response = request.response
+        response.setHeaderOutput(task)
+        response.setHTTPTransaction(task)
+        publish(request)
+
+
+class PMDBHTTPS_Server(PublisherHTTPS_Server):
+    """Enter the post-mortem debugger when there's an error"""
+
+    def executeRequest(self, task):
+        """Overrides HTTPServer.executeRequest()."""
+        env = task.getCGIEnvironment()
+        env['HTTPS'] = 'ON'
+        try:
+            del env['HTTP']
+        except KeyError:
+            pass
+        instream = task.request_data.getBodyStream()
+
+        request = self.request_factory(instream, task, env)
+        response = request.response
+        response.setHeaderOutput(task)
+        try:
+            publish(request, handle_errors=False)
+        except:
+            import sys, pdb
+            print "%s:" % sys.exc_info()[0]
+            print sys.exc_info()[1]
+            pdb.post_mortem(sys.exc_info()[2])
+            raise
+
diff --git a/demo/ZopeX3/instance_home/etc/zope.conf.patch b/demo/ZopeX3/instance_home/etc/zope.conf.patch
new file mode 100644 (file)
index 0000000..f281246
--- /dev/null
@@ -0,0 +1,26 @@
+--- zope.conf.org      Tue Sep 28 09:49:02 2004
++++ zope.conf  Tue Sep 28 09:49:27 2004
+@@ -20,6 +20,11 @@
+   address 8080
+ </server>
++<server>
++  type HTTPS
++  address 8443
++</server>
++
+ # For debugging purposes, you can use this publisher instead/as well
+ # (obviously if it's as well, use a different port number). If there's
+ # an exception, Zope will drop into pdb at the point of the exception.
+@@ -27,6 +32,11 @@
+ #<server>
+ #  type PostmortemDebuggingHTTP
+ #  address 8080
++#</server>
++#
++#<server>
++#  type PostmortemDebuggingHTTPS
++#  address 8443
+ #</server>
+ <server>
diff --git a/demo/ZopeX3/instance_home/ssl/ca.pem b/demo/ZopeX3/instance_home/ssl/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/ZopeX3/instance_home/ssl/dh1024.pem b/demo/ZopeX3/instance_home/ssl/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/ZopeX3/instance_home/ssl/server.pem b/demo/ZopeX3/instance_home/ssl/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/bio_mem_rw.py b/demo/bio_mem_rw.py
new file mode 100644 (file)
index 0000000..bcb78b8
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env python2.0
+
+"""Demonstrates the use of m2.bio_set_mem_eof_return().
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import m2
+m2.lib_init()
+
+use_mem = 1
+
+if use_mem:
+    bio = m2.bio_new(m2.bio_s_mem())
+else:
+    bio = m2.bio_new_file('XXX', 'wb')
+ciph = m2.bf_cbc()
+filt = m2.bio_new(m2.bio_f_cipher())
+m2.bio_set_cipher(filt, ciph, 'key', 'iv', 1)
+m2.bio_push(filt, bio)
+m2.bio_write(filt, '12345678901234567890')
+m2.bio_flush(filt)
+m2.bio_pop(filt)
+m2.bio_free(filt)
+if use_mem:
+    m2.bio_set_mem_eof_return(bio, 0)
+    xxx = m2.bio_read(bio, 100)
+    print `xxx`, len(xxx)
+m2.bio_free(bio)
+
+if use_mem:
+    bio = m2.bio_new(m2.bio_s_mem())
+    m2.bio_write(bio, xxx)
+    m2.bio_set_mem_eof_return(bio, 0)
+else:
+    bio = m2.bio_new_file('XXX', 'rb')
+ciph = m2.bf_cbc()
+filt = m2.bio_new(m2.bio_f_cipher())
+m2.bio_set_cipher(filt, ciph, 'key', 'iv', 0)
+m2.bio_push(filt, bio)
+yyy = m2.bio_read(filt, 100)
+print `yyy`
+m2.bio_pop(filt)
+m2.bio_free(filt)
+m2.bio_free(bio)
+
diff --git a/demo/dhtest.py b/demo/dhtest.py
new file mode 100644 (file)
index 0000000..9fd87c6
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+"""DH demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand
+
+def test():
+    print 'generating dh params:'
+    a = DH.gen_params(128, 2)
+    b = DH.set_params(a.p, a.g)
+    a.gen_key()
+    b.gen_key()
+    print 'p = ', `a.p`
+    print 'g = ', `a.g`
+    print 'a.pub =', `a.pub`
+    print 'a.priv =', `a.priv`
+    print 'b.pub =', `b.pub`
+    print 'b.priv =', `b.priv`
+    print 'a.key = ', `a.compute_key(b.pub)`
+    print 'b.key = ', `b.compute_key(a.pub)`
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    test()
+    Rand.save_file('randpool.dat')
diff --git a/demo/dsa1024pvtkey.pem b/demo/dsa1024pvtkey.pem
new file mode 100644 (file)
index 0000000..8379ed1
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvQIBAAKBgQD08iE3LXjSB/lA6Gq19XMzfmTvRBgFn+t9qNN6awFhrkowgyrI
+HaR6oCCHSvUcjdC0JcJdz8lSqofZkPknX6EEDkmlzZiUhtTZf0XiooeKigAiSlE2
+PpoS4RFcOOqOwVwRJI3mC2lzypI46/OPS0IOZFsxhXQpn1xnkmpEt83ZwwIVAPNw
+e6agkM25mv12Il7IuBBNl3cPAoGBAKOES1BV2E3zWj6gXOXFP02dk5A+zd7z4Qj+
+cx5euat07cAHYU0BZGJMTXlHSGf7YQOePuaxs9vTtSeUb1TeMFEr63Jispc9Kzce
+rd+E/IjiX7KCMbeGHnhtzC0FU/squZ76vp1TAXSozpfBvn73zAwAFPo/rHO4k6kH
+lXAez1QqAoGBAN6iYzbOnMckBtouHGBrdF4ea750DYnH5O2cij+yjgLMttuaxmZe
+0iFtJpXp6m4IHKAzIGgKhUGabAz+4O2/ZnmNu0oZzXkpBLL84pksDd0nObtgueL6
+sdTbhGl1kqpWRiK9T16gwqYxdcZiG5M5qbWtJIWWdv3mI9ql0XfUPWfpAhUA8e81
+iGFXunNE3ecKjCOKUL2EnEA=
+-----END DSA PRIVATE KEY-----
diff --git a/demo/dsa_bench.py b/demo/dsa_bench.py
new file mode 100644 (file)
index 0000000..cd4b1f9
--- /dev/null
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+
+"""
+    DSA demo and benchmark.
+
+      Usage:  python -O dsa_bench.py [option option option ...]
+        where options may include:
+          makenewkey  showpubkey  showdigest  showprofile
+          md5  sha1  sha256  sha512
+          <key length>
+    
+    NB:
+      DSA is formally defined with SHA-1 and key length 1024. 
+      The OpenSSL implementation actually supports most any 
+      hashing algorithm and key length, as long as the key
+      length is longer than the digest length.  If not SHA-1
+      and 1024, you should be very clear.  The use of "DSA"
+      without any qualifiers implies SHA-1 and 1024.
+    
+    Larry Bugbee
+    November 2006
+    
+    
+    Some portions are Copyright (c) 1999-2003 Ng Pheng Siong. 
+    All rights reserved.
+
+    Portions created by Open Source Applications Foundation 
+    (OSAF) are Copyright (C) 2004 OSAF. All Rights Reserved.
+
+"""
+
+from M2Crypto import DSA, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey  = 0     # 1 = make/save new key, 0 = use existing
+showpubkey  = 0     # 1 = show the public key value
+showdigest  = 0     # 1 = show the digest value
+showprofile = 0     # 1 = use the python profiler
+
+hashalgs = ['md5', 'ripemd160', 'sha1', 
+            'sha224', 'sha256', 'sha384', 'sha512']
+
+# default hashing algorithm
+hashalg = 'sha1'
+
+# default key length
+keylen  = 1024
+
+# number of speed test loops
+N1 = N2 = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(dsa, dgst):
+    print '  testing signing and verification...',
+    try:
+        r,s = dsa.sign(dgst)
+    except Exception, e:
+        print '\n\n    *** %s *** \n' % e
+        sys.exit()
+    if not dsa.verify(dgst, r, s):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def test_asn1(dsa, dgst):
+    # XXX Randomly fails: bug in there somewhere... (0.9.4)
+    print '  testing asn1 signing and verification...',
+    blob = dsa.sign_asn1(dgst)
+    if not dsa.verify_asn1(dgst, blob):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def speed():
+    from time import time
+    t1 = time()
+    for i in range(N1):
+        r,s = dsa.sign(dgst)
+    print '    %d signings:      %8.2fs' % (N1, (time() - t1))
+    t1 = time()
+    for i in range(N2):
+        dsa.verify(dgst, r, s)
+    print '    %d verifications: %8.2fs' % (N2, (time() - t1))
+        
+def test_speed(dsa, dgst):
+    print '  measuring speed...'
+    if showprofile:
+        import profile
+        profile.run('speed()')
+    else:
+        speed()
+        print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(keylen, hashalg):
+    global dsa, dgst     # this exists ONLY for speed testing
+    
+    Rand.load_file('randpool.dat', -1) 
+        
+    pvtkeyfilename = 'DSA%dpvtkey.pem' % (keylen)
+    pubkeyfilename = 'DSA%dpubkey.pem' % (keylen)  
+    
+    if makenewkey:
+        print '  making and saving a new key'
+        dsa = DSA.gen_params(keylen)
+        dsa.gen_key()
+        dsa.save_key(pvtkeyfilename, None )  # no pswd callback
+        dsa.save_pub_key(pubkeyfilename)
+    else:
+        print '  loading an existing key'
+        dsa = DSA.load_key(pvtkeyfilename)
+    print '  dsa key length:', len(dsa)
+    
+    if not dsa.check_key():
+        raise 'key is not initialised'
+        
+    if showpubkey:
+        dsa_pub = dsa.pub
+        pub_pem = base64.encodestring(dsa_pub)
+        print '  PEM public key is: \n',pub_pem
+
+    # since we are testing signing and verification, let's not 
+    # be fussy about the digest.  Just make one.
+    md = EVP.MessageDigest(hashalg)
+    md.update('can you spell subliminal channel?')
+    dgst = md.digest()
+    print '  hash algorithm: %s' % hashalg
+    if showdigest:
+        print '  %s digest: \n%s' % (hashalg, base64.encodestring(dgst))
+    
+    test(dsa, dgst)
+#    test_asn1(dsa, dgst)
+    test_speed(dsa, dgst)
+    Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+    print """
+  Usage:  python -O %s [option option option ...]
+    where options may include:
+      makenewkey  showpubkey  showdigest  showprofile
+      md5  sha1  sha256  sha512
+      <key length>
+""" % sys.argv[0]
+    sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+    for arg in sys.argv[1:]:
+        if arg in hashalgs:         hashalg = arg; continue
+        if arg == 'makenewkey':   makenewkey  = 1; continue
+        if arg == 'showpubkey':   showpubkey  = 1; continue
+        if arg == 'showdigest':   showdigest  = 1; continue
+        if arg == 'showprofile':  showprofile = 1; continue
+        try:
+            keylen = int(arg)
+        except:
+            print '\n  *** argument "%s" not understood ***' % arg
+            print_usage()
+        
+    main(keylen, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
diff --git a/demo/dsatest.pem b/demo/dsatest.pem
new file mode 100644 (file)
index 0000000..8d5d97f
--- /dev/null
@@ -0,0 +1,8 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIH4AgEAAkEA0NGZ0GRXdPLh/0c980Ot8ZbfV/DvJ19ZzsDhKXRxNNw36Ms4lb9Y
+ZMnJ1CliIDkpHx8sXEak0vkdeB2efGGBPQIVAJY7PF7CiA+jj+t3EyHf/sgVagPP
+AkEApkvDehftx8Kt+3GRsYkEgcKqsU6tue+QQOFOFYsCbMq/3rxIEKk0q1PqHfid
++BsMiEY4FFmF5BqmgGAf6+V9twJATbbgPKi/EboVrtBdkTM52LSCQHPa/CEcj322
+0s5Ix1dwojdQaNpq6HhCm6+g9SXPENy9I/PK85YnawI4A6w1pQIULRB2HSm1X14c
++guvmhIobv6wE50=
+-----END DSA PRIVATE KEY-----
diff --git a/demo/dsatest.py b/demo/dsatest.py
new file mode 100644 (file)
index 0000000..854b8b9
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+"""DSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DSA, EVP, Rand
+
+md=EVP.MessageDigest('sha1')
+md.update('can you spell subliminal channel?')
+dgst=md.digest()
+
+d=DSA.load_key('dsatest.pem')
+
+def test():
+    print 'testing signing...',
+    r,s=d.sign(dgst)
+    if not d.verify(dgst, r, s):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def test_asn1():
+    # XXX Randomly fails: bug in there somewhere... (0.9.4)
+    print 'testing asn1 signing...',
+    blob=d.sign_asn1(dgst)
+    if not d.verify_asn1(dgst, blob):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def speed():
+    from time import time
+    N1 = 5242
+    N2 = 2621
+    t1 = time()
+    for i in range(N1):
+        r,s = d.sign(dgst)
+    print '%d signings: %8.2fs' % (N1, (time() - t1))
+    t1 = time()
+    for i in range(N2):
+        d.verify(dgst, r, s)
+    print '%d verifications: %8.2fs' % (N2, (time() - t1))
+        
+def test_speed():
+    print 'measuring speed...'
+    import profile
+    profile.run('speed()')
+
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    test()
+    test_asn1()
+    #test_speed()
+    Rand.save_file('randpool.dat')
+
diff --git a/demo/ec/ecdhtest.py b/demo/ec/ecdhtest.py
new file mode 100644 (file)
index 0000000..6d407b6
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+"""ECDH demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. 
+All rights reserved."""
+
+from M2Crypto import EC,Rand
+
+def test():
+    print 'generating ec keys:'
+    a=EC.gen_params(EC.NID_sect233k1)
+    a.gen_key()
+    b=EC.gen_params(EC.NID_sect233k1)
+    b.gen_key()
+    a_shared_key = a.compute_dh_key(b.pub())
+    b_shared_key = b.compute_dh_key(a.pub())
+    print 'shared key according to a = ', `a_shared_key`
+    print 'shared key according to b = ', `b_shared_key`
+    if a_shared_key == b_shared_key:
+        print 'ok'
+    else:
+        print 'not ok'
+
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    test()
+    Rand.save_file('randpool.dat')
diff --git a/demo/ec/ecdsa_bench.py b/demo/ec/ecdsa_bench.py
new file mode 100644 (file)
index 0000000..bafceb1
--- /dev/null
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+
+"""
+    ECDSA demo and benchmark.
+
+      Usage:  python -O ecdsa_bench.py [option option option ...]
+        where options may include:
+          makenewkey  showpubkey  showdigest  showprofile
+          md5  sha1  sha256  sha512
+          secp160r1  secp224r1  secp192k1  sect283r1
+          sect283k1  secp256k1  secp384r1  secp521r1
+        (other curves and hashes are supported, see below)
+
+    Larry Bugbee, June 2006
+    
+    Portions:
+      Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+      Copyright (c) 2005 Vrije Universiteit Amsterdam. All rights reserved.
+    
+"""
+
+from M2Crypto import EC, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey  = 0     # 1 = make/save new key, 0 = use existing
+showpubkey  = 0     # 1 = show the public key value
+showdigest  = 0     # 1 = show the digest value
+showprofile = 0     # 1 = use the python profiler
+
+hashalgs = ['md5', 'ripemd160', 'sha1', 
+            'sha224', 'sha256', 'sha384', 'sha512']
+        
+curves   = ['secp112r1',
+            'secp112r2',
+            'secp128r1',
+            'secp128r2',
+            'secp160k1',
+            'secp160r1',
+            'secp160r2',
+            'secp192k1',
+            'secp224k1',
+            'secp224r1',
+            'secp256k1',
+            'secp384r1',
+            'secp521r1',
+            'sect113r1',
+            'sect113r2',
+            'sect131r1',
+            'sect131r2',
+            'sect163k1',
+            'sect163r1',
+            'sect163r2',
+            'sect193r1',
+            'sect193r2',
+            'sect233k1',
+            'sect233r1',
+            'sect239k1',
+            'sect283k1',
+            'sect283r1',
+            'sect409k1',
+            'sect409r1',
+            'sect571k1',
+            'sect571r1',
+            'X9_62_prime192v1',
+            'X9_62_prime192v2',
+            'X9_62_prime192v3',
+            'X9_62_prime239v1',
+            'X9_62_prime239v2',
+            'X9_62_prime239v3',
+            'X9_62_prime256v1',
+            'X9_62_c2pnb163v1',
+            'X9_62_c2pnb163v2',
+            'X9_62_c2pnb163v3',
+            'X9_62_c2pnb176v1',
+            'X9_62_c2tnb191v1',
+            'X9_62_c2tnb191v2',
+            'X9_62_c2tnb191v3',
+            'X9_62_c2pnb208w1',
+            'X9_62_c2tnb239v1',
+            'X9_62_c2tnb239v2',
+            'X9_62_c2tnb239v3',
+            'X9_62_c2pnb272w1',
+            'X9_62_c2pnb304w1',
+            'X9_62_c2tnb359v1',
+            'X9_62_c2pnb368w1',
+            'X9_62_c2tnb431r1',
+            'wap_wsg_idm_ecid_wtls1',
+            'wap_wsg_idm_ecid_wtls3',
+            'wap_wsg_idm_ecid_wtls4',
+            'wap_wsg_idm_ecid_wtls5',
+            'wap_wsg_idm_ecid_wtls6',
+            'wap_wsg_idm_ecid_wtls7',
+            'wap_wsg_idm_ecid_wtls8',
+            'wap_wsg_idm_ecid_wtls9',
+            'wap_wsg_idm_ecid_wtls10',
+            'wap_wsg_idm_ecid_wtls11',
+            'wap_wsg_idm_ecid_wtls12',
+           ]
+
+# The following two curves, according to OpenSSL, have a 
+# "Questionable extension field!" and are not supported by 
+# the OpenSSL inverse function.  ECError: no inverse.
+# As such they cannot be used for signing.  They might, 
+# however, be usable for encryption but that has not 
+# been tested.  Until thir usefulness can be established,
+# they are not supported at this time.
+#
+#      Oakley-EC2N-3: 
+#        IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
+#      Oakley-EC2N-4: 
+#        IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
+#
+#      aka 'ipsec3' and 'ipsec4'
+
+# curves2 is a shorthand convenience so as to not require the
+# entering the "X9_62_" prefix
+curves2  = ['prime192v1',
+            'prime192v2',
+            'prime192v3',
+            'prime239v1',
+            'prime239v2',
+            'prime239v3',
+            'prime256v1',
+            'c2pnb163v1',
+            'c2pnb163v2',
+            'c2pnb163v3',
+            'c2pnb176v1',
+            'c2tnb191v1',
+            'c2tnb191v2',
+            'c2tnb191v3',
+            'c2pnb208w1',
+            'c2tnb239v1',
+            'c2tnb239v2',
+            'c2tnb239v3',
+            'c2pnb272w1',
+            'c2pnb304w1',
+            'c2tnb359v1',
+            'c2pnb368w1',
+            'c2tnb431r1',
+           ]
+
+# default hashing algorithm
+hashalg = 'sha1'
+
+# default elliptical curve
+curve   = 'secp160r1'    
+
+# for a complete list of supported algorithms and curves, see 
+# the bottom of this file
+
+# number of speed test loops
+N1 = N2 = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(ec, dgst):
+    print '  testing signing and verification...',
+    try:
+#        ec = EC.gen_params(EC.NID_secp160r1)
+#        ec.gen_key()
+        r,s = ec.sign_dsa(dgst)
+    except Exception, e:
+        print '\n\n    *** %s *** \n' % e
+        sys.exit()
+    if not ec.verify_dsa(dgst, r, s):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def test_asn1(ec, dgst):
+    print '  testing asn1 signing and verification...',
+    blob = ec.sign_dsa_asn1(dgst)
+    if not ec.verify_dsa_asn1(dgst, blob):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def speed():
+    from time import time
+    t1 = time()
+    for i in range(N1):
+        r,s = ec.sign_dsa(dgst)
+    print '    %d signings:      %8.2fs' % (N1, (time() - t1))
+    t1 = time()
+    for i in range(N2):
+        ec.verify_dsa(dgst, r, s)
+    print '    %d verifications: %8.2fs' % (N2, (time() - t1))
+        
+def test_speed(ec, dgst):
+    print '  measuring speed...'
+    if showprofile:
+        import profile
+        profile.run('speed()')
+    else:
+        speed()
+        print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(curve, hashalg):
+    global ec, dgst     # this exists ONLY for speed testing
+    
+    Rand.load_file('randpool.dat', -1) 
+    
+    if curve in curves2:
+        curve = 'X9_62_' + curve
+    ec_curve = eval('EC.NID_%s' % curve)
+    
+    pvtkeyfilename = '%spvtkey.pem' % (curve)
+    pubkeyfilename = '%spubkey.pem' % (curve)  
+    
+    if makenewkey:
+        print '  making and saving a new key'
+        ec = EC.gen_params(ec_curve)
+        ec.gen_key()
+        ec.save_key(pvtkeyfilename, None )
+        ec.save_pub_key(pubkeyfilename)
+    else:
+        print '  loading an existing key'
+        ec=EC.load_key(pvtkeyfilename)
+    print '  ecdsa key length:', len(ec)
+    print '  curve: %s' % curve
+    
+    if not ec.check_key():
+        raise 'key is not initialised'
+        
+    if showpubkey:
+        ec_pub = ec.pub()
+        pub_der = ec_pub.get_der()
+        pub_pem = base64.encodestring(pub_der)
+        print '  PEM public key is: \n',pub_pem
+
+    # since we are testing signing and verification, let's not 
+    # be fussy about the digest.  Just make one.
+    md = EVP.MessageDigest(hashalg)
+    md.update('can you spell subliminal channel?')
+    dgst = md.digest()
+    print '  hash algorithm: %s' % hashalg
+    if showdigest:
+        print '  %s digest: \n%s' % (base64.encodestring(dgst))
+    
+    test(ec, dgst)
+#    test_asn1(ec, dgst)
+    test_speed(ec, dgst)
+    Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+    print """
+  Usage:  python -O %s [option option option ...]
+    where options may include:
+      makenewkey  showpubkey  showdigest  showprofile
+      md5  sha1  sha256  sha512
+      secp160r1  secp224r1  secp192k1  sect283r1
+      sect283k1  secp256k1  secp384r1  secp521r1
+    (other curves and hashes are supported, check pgm src)
+""" % sys.argv[0]
+    sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+    for arg in sys.argv[1:]:
+        if arg in hashalgs:         hashalg = arg; continue
+        if arg in curves + curves2: curve   = arg; continue
+        if arg == 'makenewkey':   makenewkey  = 1; continue
+        if arg == 'showpubkey':   showpubkey  = 1; continue
+        if arg == 'showdigest':   showdigest  = 1; continue
+        if arg == 'showprofile':  showprofile = 1; continue
+        
+        print '\n  *** argument "%s" not understood ***' % arg
+        print_usage()
+        
+    main(curve, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+
+"""
+        Elliptical curves supported by OpenSSL
+        ======================================
+
+$ openssl ecparam -list_curves                              
+  secp112r1 : SECG/WTLS curve over a 112 bit prime field
+  secp112r2 : SECG curve over a 112 bit prime field
+  secp128r1 : SECG curve over a 128 bit prime field
+  secp128r2 : SECG curve over a 128 bit prime field
+  secp160k1 : SECG curve over a 160 bit prime field
+  secp160r1 : SECG curve over a 160 bit prime field
+  secp160r2 : SECG/WTLS curve over a 160 bit prime field
+  secp192k1 : SECG curve over a 192 bit prime field
+  secp224k1 : SECG curve over a 224 bit prime field
+  secp224r1 : NIST/SECG curve over a 224 bit prime field
+  secp256k1 : SECG curve over a 256 bit prime field
+  secp384r1 : NIST/SECG curve over a 384 bit prime field
+  secp521r1 : NIST/SECG curve over a 521 bit prime field
+  prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field
+  prime192v2: X9.62 curve over a 192 bit prime field
+  prime192v3: X9.62 curve over a 192 bit prime field
+  prime239v1: X9.62 curve over a 239 bit prime field
+  prime239v2: X9.62 curve over a 239 bit prime field
+  prime239v3: X9.62 curve over a 239 bit prime field
+  prime256v1: X9.62/SECG curve over a 256 bit prime field
+  sect113r1 : SECG curve over a 113 bit binary field
+  sect113r2 : SECG curve over a 113 bit binary field
+  sect131r1 : SECG/WTLS curve over a 131 bit binary field
+  sect131r2 : SECG curve over a 131 bit binary field
+  sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field
+  sect163r1 : SECG curve over a 163 bit binary field
+  sect163r2 : NIST/SECG curve over a 163 bit binary field
+  sect193r1 : SECG curve over a 193 bit binary field
+  sect193r2 : SECG curve over a 193 bit binary field
+  sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field
+  sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field
+  sect239k1 : SECG curve over a 239 bit binary field
+  sect283k1 : NIST/SECG curve over a 283 bit binary field
+  sect283r1 : NIST/SECG curve over a 283 bit binary field
+  sect409k1 : NIST/SECG curve over a 409 bit binary field
+  sect409r1 : NIST/SECG curve over a 409 bit binary field
+  sect571k1 : NIST/SECG curve over a 571 bit binary field
+  sect571r1 : NIST/SECG curve over a 571 bit binary field
+  c2pnb163v1: X9.62 curve over a 163 bit binary field
+  c2pnb163v2: X9.62 curve over a 163 bit binary field
+  c2pnb163v3: X9.62 curve over a 163 bit binary field
+  c2pnb176v1: X9.62 curve over a 176 bit binary field
+  c2tnb191v1: X9.62 curve over a 191 bit binary field
+  c2tnb191v2: X9.62 curve over a 191 bit binary field
+  c2tnb191v3: X9.62 curve over a 191 bit binary field
+  c2pnb208w1: X9.62 curve over a 208 bit binary field
+  c2tnb239v1: X9.62 curve over a 239 bit binary field
+  c2tnb239v2: X9.62 curve over a 239 bit binary field
+  c2tnb239v3: X9.62 curve over a 239 bit binary field
+  c2pnb272w1: X9.62 curve over a 272 bit binary field
+  c2pnb304w1: X9.62 curve over a 304 bit binary field
+  c2tnb359v1: X9.62 curve over a 359 bit binary field
+  c2pnb368w1: X9.62 curve over a 368 bit binary field
+  c2tnb431r1: X9.62 curve over a 431 bit binary field
+  wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field
+  wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field
+  wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field
+  wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field
+  wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field
+  wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field
+  wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field
+  wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field
+  wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field
+  wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field
+  wap-wsg-idm-ecid-wtls12: WTLS curvs over a 224 bit prime field
+          Oakley-EC2N-3: 
+                IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
+                Not suitable for ECDSA.
+                Questionable extension field!
+          Oakley-EC2N-4: 
+                IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
+                Not suitable for ECDSA.
+                Questionable extension field!
+
+"""
diff --git a/demo/ec/ecdsatest.pem b/demo/ec/ecdsatest.pem
new file mode 100644 (file)
index 0000000..cc37a65
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MG0CAQEEHVkQ54w5gN39TScPaC+TTKmJunupCfuNqEcWOZeXoAcGBSuBBAAaoUAD
+PgAEAIcWBNwIi1fP2Sd33wpayKBuw2oqBVvgvfYiipMcASzxCf6IFUC03IOob/Lu
+Y3mPHRZKwzSKBlD1ZkXh
+-----END EC PRIVATE KEY-----
diff --git a/demo/ec/ecdsatest.py b/demo/ec/ecdsatest.py
new file mode 100644 (file)
index 0000000..61ad625
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+"""ECDSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+from M2Crypto import EC, EVP, Rand
+import base64
+
+md=EVP.MessageDigest('sha1')
+md.update('can you spell subliminal channel?')
+dgst=md.digest()
+
+ec=EC.load_key('ecdsatest.pem')
+#ec=EC.gen_params(EC.NID_sect233k1)
+#ec.gen_key()
+ec_pub = ec.pub()
+pub_der = ec_pub.get_der()
+pub_pem = base64.encodestring(pub_der)
+print 'PEM public key is',pub_pem
+ec.save_key( 'ecdsatest.pem', None )
+
+
+def test():
+    print 'testing signing...',
+    r,s=ec.sign_dsa(dgst)
+    if not ec.verify_dsa(dgst, r, s):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def test_asn1():
+    # XXX Randomly fails: bug in there somewhere... (0.9.4)
+    print 'testing asn1 signing...',
+    blob=ec.sign_dsa_asn1(dgst)
+    if not ec.verify_dsa_asn1(dgst, blob):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def speed():
+    from time import time
+    N1 = 5242
+    N2 = 2621
+    t1 = time()
+    for i in range(N1):
+        r,s = ec.sign(dgst)
+    print '%d signings: %8.2fs' % (N1, (time() - t1))
+    t1 = time()
+    for i in range(N2):
+        ec.verify(dgst, r, s)
+    print '%d verifications: %8.2fs' % (N2, (time() - t1))
+        
+def test_speed():
+    print 'measuring speed...'
+    import profile
+    profile.run('speed()')
+
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    test()
+    test_asn1()
+    #test_speed()
+    Rand.save_file('randpool.dat')
+
diff --git a/demo/ec/secp160r1pvtkey.pem b/demo/ec/secp160r1pvtkey.pem
new file mode 100644 (file)
index 0000000..6eac827
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN EC PRIVATE KEY-----
+MFACAQEEFN1C7AYNKDl9dBLfm0QW1nB7WGs7oAcGBSuBBAAIoSwDKgEErnCVbfXH
+11Ax4AZq4eh8c3gWIBeaRu6tGRpITFCjtHot78c/oVqBrg==
+-----END EC PRIVATE KEY-----
diff --git a/demo/https.howto/ca.pem b/demo/https.howto/ca.pem
new file mode 100644 (file)
index 0000000..d8ba0d3
--- /dev/null
@@ -0,0 +1,59 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 0 (0x0)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+        Validity
+            Not Before: Apr 22 04:35:56 2006 GMT
+            Not After : Apr 21 04:35:56 2009 GMT
+        Subject: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:d8:80:02:8f:b5:7d:9f:9b:79:76:67:a5:66:64:
+                    c0:30:0c:71:65:f1:c6:78:01:a0:29:d4:3a:2c:e5:
+                    ee:58:4d:db:53:c5:74:6e:4e:f7:b6:a5:8e:ef:ab:
+                    e8:7c:f5:5d:2d:18:ba:95:b8:15:43:6e:5a:78:c2:
+                    91:05:08:b2:7e:cf:c4:d3:bb:ac:c7:43:27:fb:8f:
+                    43:0d:7b:d0:d1:32:51:86:11:6e:3e:aa:68:19:88:
+                    b9:cf:d5:72:f0:a4:73:d1:69:c4:65:14:0e:12:64:
+                    7e:1f:df:18:09:0b:6a:4b:cd:bf:ae:59:82:15:1c:
+                    90:0f:c3:e5:cb:b3:ed:86:4d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+            X509v3 Authority Key Identifier: 
+                keyid:5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+
+    Signature Algorithm: sha1WithRSAEncryption
+        6b:9e:71:4a:ad:d2:1c:b7:58:1a:6e:8b:89:92:8d:4e:62:61:
+        06:2e:e8:11:f8:9c:a0:e2:11:7c:b6:e2:be:ef:b9:b1:35:20:
+        d1:81:62:c5:ca:3c:4f:c9:88:72:f7:50:d8:e8:e0:06:43:ee:
+        c5:5c:38:9b:e7:24:46:a6:ee:8d:b0:70:4e:75:96:00:db:d6:
+        59:f9:58:74:67:9f:ca:9c:12:fc:77:a7:0e:5a:38:22:5b:de:
+        c9:33:35:bd:d0:4c:9f:6a:0f:71:7b:db:cb:fd:da:bc:39:4f:
+        23:1e:74:5b:ff:8d:73:72:16:a9:9f:57:54:96:3e:2c:f0:65:
+        af:df
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQDEwlsb2Nh
+bGhvc3QwHhcNMDYwNDIyMDQzNTU2WhcNMDkwNDIxMDQzNTU2WjBEMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQD
+Ewlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANiAAo+1fZ+b
+eXZnpWZkwDAMcWXxxngBoCnUOizl7lhN21PFdG5O97alju+r6Hz1XS0YupW4FUNu
+WnjCkQUIsn7PxNO7rMdDJ/uPQw170NEyUYYRbj6qaBmIuc/VcvCkc9FpxGUUDhJk
+fh/fGAkLakvNv65ZghUckA/D5cuz7YZNAgMBAAGjfjB8MAwGA1UdEwQFMAMBAf8w
+LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
+A1UdDgQWBBRc+cWbsAI3PGZzTQ7LWj27OkYi3TAfBgNVHSMEGDAWgBRc+cWbsAI3
+PGZzTQ7LWj27OkYi3TANBgkqhkiG9w0BAQUFAAOBgQBrnnFKrdIct1gabouJko1O
+YmEGLugR+Jyg4hF8tuK+77mxNSDRgWLFyjxPyYhy91DY6OAGQ+7FXDib5yRGpu6N
+sHBOdZYA29ZZ+Vh0Z5/KnBL8d6cOWjgiW97JMzW90Eyfag9xe9vL/dq8OU8jHnRb
+/41zchapn1dUlj4s8GWv3w==
+-----END CERTIFICATE-----
diff --git a/demo/https.howto/dh1024.pem b/demo/https.howto/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/https.howto/get_https.py b/demo/https.howto/get_https.py
new file mode 100755 (executable)
index 0000000..9728fab
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+from M2Crypto import Rand, SSL, httpslib
+
+def get_https():
+    ctx = SSL.Context()
+    if ctx.load_verify_locations('ca.pem') != 1:
+        raise Exception('CA certificates not loaded')
+    ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+    h = httpslib.HTTPSConnection('localhost', 9443, ssl_context=ctx)
+    h.set_debuglevel(1)
+    h.putrequest('GET', '/')
+    h.endheaders()
+    resp = h.getresponse()
+    while 1:
+        data = resp.read()
+        if not data: 
+            break
+        print data
+    h.close()
+
+Rand.load_file('../randpool.dat', -1) 
+get_https()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/https.howto/https_cli.py b/demo/https.howto/https_cli.py
new file mode 100644 (file)
index 0000000..bb34625
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+import sys
+from M2Crypto import Rand, SSL, httpslib, threading
+
+
+def test_httpslib():
+    ctx = SSL.Context()
+    if ctx.load_verify_locations('ca.pem') != 1:
+        raise Exception('CA certificates not loaded')
+    ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+    ctx.set_info_callback()
+    h = httpslib.HTTPSConnection('localhost', 9443, ssl_context=ctx)
+    h.set_debuglevel(1)
+    h.putrequest('GET', '/')
+    h.putheader('Accept', 'text/html')
+    h.putheader('Accept', 'text/plain')
+    h.putheader('Connection', 'close')
+    h.endheaders()
+    resp = h.getresponse()
+    f = resp.fp
+    c = 0
+    while 1:
+        # Either of following two works.
+        #data = f.readline()   
+        data = resp.read()
+        if not data: break
+        c = c + len(data)
+        sys.stdout.write(data)
+        sys.stdout.flush()
+    f.close()
+    h.close()
+
+
+if __name__=='__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    #threading.init()
+    test_httpslib()
+    #threading.cleanup()
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/https.howto/orig_https_srv.py b/demo/https.howto/orig_https_srv.py
new file mode 100644 (file)
index 0000000..83be0fb
--- /dev/null
@@ -0,0 +1,153 @@
+"""This server extends BaseHTTPServer and SimpleHTTPServer thusly:
+1. One thread per connection.
+2. Generates directory listings. 
+
+In addition, it has the following properties:
+1. Works over HTTPS only.
+2. Displays SSL handshaking and SSL session info.
+3. Performs SSL renegotiation when a magic url is requested.
+
+TODO:
+1. Cache stat() of directory entries.
+2. Fancy directory indexing.
+3. Interface ZPublisher.
+
+Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+import os, sys
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+from M2Crypto import Rand, SSL, threading
+from M2Crypto.SSL.SSLServer import ThreadingSSLServer
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+def mkdirlist(path, url):
+    dirlist = os.listdir(path)
+    dirlist.sort()
+    f = StringIO()
+    f.write('<title>Index listing for %s</title>\r\n' % (url,))
+    f.write('<h1>Index listing for %s</h1>\r\n' % (url,))
+    f.write('<pre>\r\n')
+    for d in dirlist:
+        if os.path.isdir(os.path.join(path, d)):
+            d2 = d + '/'
+        else:
+            d2 = d
+        if url == '/':
+            f.write('<a href="/%s">%s</a><br>\r\n' % (d, d2))
+        else:
+            f.write('<a href="%s/%s">%s</a><br>\r\n' % (url, d, d2))
+    f.write('</pre>\r\n\r\n')
+    f.reset()
+    return f
+
+
+class HTTP_Handler(SimpleHTTPRequestHandler):
+
+    server_version = "https_srv/0.1"
+    reneg = 0
+
+    # Cribbed from SimpleHTTPRequestHander to add the ".der" entry,
+    # which facilitates installing your own certificates into browsers.
+    extensions_map = {
+            '': 'text/plain',   # Default, *must* be present
+            '.html': 'text/html',
+            '.htm': 'text/html',
+            '.gif': 'image/gif',
+            '.jpg': 'image/jpeg',
+            '.jpeg': 'image/jpeg',
+            '.der': 'application/x-x509-ca-cert'
+            }
+
+    def send_head(self):
+        if self.path[1:8] == '_reneg_':
+            self.reneg = 1
+            self.path = self.path[8:]
+        path = self.translate_path(self.path)
+        if os.path.isdir(path):
+            f = mkdirlist(path, self.path)
+            filetype = 'text/html'
+        else:
+            try:
+                f = open(path, 'rb')
+                filetype = self.guess_type(path)
+            except IOError:
+                self.send_error(404, "File not found")
+                return None
+        self.send_response(200)
+        self.send_header("Content-type", filetype)
+        self.end_headers()
+        return f
+
+    def do_GET(self):
+        #sess = self.request.get_session()
+        #self.log_message('\n%s', sess.as_text())
+        f = self.send_head()
+        if self.reneg:
+            self.reneg = 0
+            self.request.renegotiate()
+            sess = self.request.get_session()
+            self.log_message('\n%s', sess.as_text())
+        if f:
+            self.copyfile(f, self.wfile)
+            f.close()
+
+    def do_HEAD(self):
+        #sess = self.request.get_session()
+        #self.log_message('\n%s', sess.as_text())
+        f = self.send_head()
+        if f:
+            f.close()
+
+
+class HTTPS_Server(ThreadingSSLServer):
+    def __init__(self, server_addr, handler, ssl_ctx):
+        ThreadingSSLServer.__init__(self, server_addr, handler, ssl_ctx)
+        self.server_name = server_addr[0]
+        self.server_port = server_addr[1]
+
+    def finish(self):
+        self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+        self.request.close()
+
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+    ctx=SSL.Context(protocol)
+    ctx.load_cert(certfile)
+    ctx.load_client_ca(cafile)
+    if ctx.load_verify_locations(cafile) != 1:
+        raise Exception('CA certificates not loaded')
+    ctx.set_verify(verify, verify_depth)
+    ctx.set_allow_unknown_ca(1)
+    ctx.set_session_id_ctx('https_srv')
+    ctx.set_info_callback()
+    return ctx
+
+
+if __name__ == '__main__':
+    if len(sys.argv) < 2:
+        wdir = '.'
+    else:
+        wdir = sys.argv[1]
+    Rand.load_file('../randpool.dat', -1)
+    threading.init()
+    ctx = init_context('sslv23', 'server.pem', 'ca.pem', \
+        SSL.verify_none)
+        #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+    ctx.set_tmp_dh('dh1024.pem')
+    os.chdir(wdir)
+    httpsd = HTTPS_Server(('', 9443), HTTP_Handler, ctx)
+    httpsd.serve_forever()
+    threading.cleanup()
+    Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/https.howto/server.pem b/demo/https.howto/server.pem
new file mode 100644 (file)
index 0000000..bcd826c
--- /dev/null
@@ -0,0 +1,74 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+        Validity
+            Not Before: Apr 22 04:36:56 2006 GMT
+            Not After : Apr 22 04:36:56 2007 GMT
+        Subject: C=US, ST=CA, O=M2Crypto Server, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:a5:cc:39:ad:ba:81:3d:bd:05:f4:61:50:9f:9c:
+                    f6:ad:ec:29:d9:78:1e:24:61:f7:1b:36:bf:69:d8:
+                    b3:45:ae:6f:3a:4c:4f:d6:13:6d:60:8d:f2:bb:2a:
+                    c4:1b:79:fd:e2:f8:d6:3c:56:53:3b:27:f7:3f:70:
+                    a4:64:99:63:46:2e:3f:ef:52:da:a9:04:5b:6e:d4:
+                    40:57:c5:59:61:d3:3f:7d:b8:03:c1:9b:65:46:2a:
+                    c5:9d:70:b7:ca:79:6e:dd:e4:3f:c2:f4:2f:2e:81:
+                    32:c8:e9:a6:b6:a8:c8:1f:48:be:7a:66:56:98:fc:
+                    3c:25:fc:d9:3d:73:07:30:71
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                F0:48:3A:32:88:8C:80:D7:22:AB:56:F8:86:B3:04:47:10:76:37:BE
+            X509v3 Authority Key Identifier: 
+                keyid:5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+
+    Signature Algorithm: sha1WithRSAEncryption
+        39:47:95:5c:ea:7e:db:b8:e0:80:f6:e5:d4:9f:83:bc:41:89:
+        31:97:c8:a4:95:0d:5d:6d:cc:64:8d:19:71:17:75:4b:7f:fb:
+        35:88:bf:68:e2:a2:be:c5:71:71:56:2a:92:31:25:2a:4b:98:
+        4e:77:42:45:78:45:21:a5:76:99:92:39:32:7d:a2:4c:38:b0:
+        f1:db:7f:d1:4d:23:99:35:1e:0e:a1:59:a3:ff:9c:51:ef:4c:
+        11:c9:32:61:38:11:7d:57:2a:81:9a:96:1f:b3:88:f7:ab:5b:
+        58:f7:79:9b:a8:e3:b7:09:90:8e:c9:7d:44:4f:af:85:dc:c8:
+        29:4d
+-----BEGIN CERTIFICATE-----
+MIICfTCCAeagAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQDEwlsb2Nh
+bGhvc3QwHhcNMDYwNDIyMDQzNjU2WhcNMDcwNDIyMDQzNjU2WjBIMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExGDAWBgNVBAoTD00yQ3J5cHRvIFNlcnZlcjESMBAG
+A1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClzDmt
+uoE9vQX0YVCfnPat7CnZeB4kYfcbNr9p2LNFrm86TE/WE21gjfK7KsQbef3i+NY8
+VlM7J/c/cKRkmWNGLj/vUtqpBFtu1EBXxVlh0z99uAPBm2VGKsWdcLfKeW7d5D/C
+9C8ugTLI6aa2qMgfSL56ZlaY/Dwl/Nk9cwcwcQIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQU8Eg6MoiMgNciq1b4hrMERxB2N74wHwYDVR0jBBgwFoAUXPnFm7AC
+Nzxmc00Oy1o9uzpGIt0wDQYJKoZIhvcNAQEFBQADgYEAOUeVXOp+27jggPbl1J+D
+vEGJMZfIpJUNXW3MZI0ZcRd1S3/7NYi/aOKivsVxcVYqkjElKkuYTndCRXhFIaV2
+mZI5Mn2iTDiw8dt/0U0jmTUeDqFZo/+cUe9MEckyYTgRfVcqgZqWH7OI96tbWPd5
+m6jjtwmQjsl9RE+vhdzIKU0=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQClzDmtuoE9vQX0YVCfnPat7CnZeB4kYfcbNr9p2LNFrm86TE/W
+E21gjfK7KsQbef3i+NY8VlM7J/c/cKRkmWNGLj/vUtqpBFtu1EBXxVlh0z99uAPB
+m2VGKsWdcLfKeW7d5D/C9C8ugTLI6aa2qMgfSL56ZlaY/Dwl/Nk9cwcwcQIDAQAB
+AoGBAIwzbZbePsnxXOaxoBbJCcQ7D4yJSZvkh6womKauC7Lh9carn1tc5EIg5uCl
+Il5Fw466c5dkPE+q1SZ9X1Z+avYh4ypHjpcr1Lfvwo0wfOBx5MBj6ppMdwgrMXuo
+jRChK3gBZXmKaIH19h/sGp/lZRW2HiX63aN11KDmtfwo6Bq9AkEA2OftYJUqceK3
+/E8q6OE1oQLm+oK6CPZ29A5TRNOadRu1opDR/y59GcUQ5ebJNH8DXyF82lSi3DKt
+SNoSOF32cwJBAMOuKGUAwnq1yH3q+MAF7CZjGou7Ar6VRseyLnD5nttynT85QRW8
+N/WCosKLhV7wi9kKJmHGUJfRAqdo14D8IIsCQQCVjrU6FyABDpZVvjCUClT0BBBH
+QsQLUgWLGiWIG28wuD5xLPHexas0jZCtNIgfTkSA35I66Iiy065vwQ03GHLJAkAG
+eGC/jjngAtjBSR62grufPVGoYyOhF6CCg+LDO43EJdMLPyJmzJVxGcO1+RUM4ZlO
+MOa5/uu1SWT0EiRmEHAnAkBusVcHcd6d4uaoiCybIhF4hL4GsbKoImIciakNlteA
+c1RZZHc2jzO/Ihoz50H1njXwY86YbjncOXw8shtayd8j
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa/00_README b/demo/medusa/00_README
new file mode 100644 (file)
index 0000000..1f0ce36
--- /dev/null
@@ -0,0 +1,32 @@
+
+ 19 Sep 2001
+-------------
+
+M2Crypto HTTPS and FTP/TLS servers
+
+All the files in this directory are from the Apr 2001 release 
+of Medusa, except for the following:
+
+- 00_README (this file)
+- server.pem, the server's certificate
+- ca.pem, my CA certificate
+- https_server.py
+- ftps_server.py
+- START.py
+- START_xmlrpc.py
+- index.html, a sample HTML file
+- poison_handler.py, a webpoison clone
+
+By default, http_server listens on port 9080 and https_server port 9443.
+Document root is current directory, and serves up index.html. 
+
+The xmlrpc server is accessible below '/RPC2'. It requires Fredrik Lundh's
+xmlrpc_handler on PYTHONPATH.
+
+The FTP/TLS server listens on port 9021 by default. I've only tested it with
+the 'anonymous' authentication handler.
+
+Medusa files are copyright Sam Rushing. My files are copyright me.
+
+
+
diff --git a/demo/medusa/START.py b/demo/medusa/START.py
new file mode 100644 (file)
index 0000000..62021f7
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa 
+import asyncore
+import default_handler
+import filesys
+import ftp_server
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+import ftps_server
+from M2Crypto import Rand, SSL, threading
+
+HTTP_PORT=9080
+HTTPS_PORT=9443
+FTP_PORT = 9021
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1) 
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+ssl_ctx.load_verify_location('ca.pem')
+ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+#fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk130/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+#class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+#    def call (self, method, params):
+#        print 'method="%s" params=%s' % (method, params)
+#        return "Sure, that works"
+#rpch = rpc_demo()
+#hs.install_handler(rpch)
+#hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+fauthz = ftp_server.anon_authorizer('/usr/local/pkg/apache/htdocs')
+ftps = ftps_server.ftp_tls_server(fauthz, ssl_ctx, port=FTP_PORT)
+
+sh=status_handler.status_extension([hs, hss, ftps])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa/START_xmlrpc.py b/demo/medusa/START_xmlrpc.py
new file mode 100644 (file)
index 0000000..193e8bf
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa 
+import asyncore
+import default_handler
+import filesys
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+from M2Crypto import Rand, SSL
+
+# XMLrpc
+import xmlrpc_handler
+
+
+HTTP_PORT=9080
+HTTPS_PORT=9443
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1) 
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+#ssl_ctx.load_verify_location('ca.pem')
+#ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+#ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+#fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk118/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+# Cribbed from xmlrpc_handler.py.
+# This is where you implement your RPC functionality.
+class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+    def call (self, method, params):
+        print 'method="%s" params=%s' % (method, params)
+        return "Sure, that works"
+
+rpch = rpc_demo()
+hs.install_handler(rpch)
+hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+sh=status_handler.status_extension([hss])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa/asynchat.py b/demo/medusa/asynchat.py
new file mode 100644 (file)
index 0000000..2e51f1a
--- /dev/null
@@ -0,0 +1,292 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#      $Id: asynchat.py 299 2005-06-09 17:32:28Z heikki $
+#      Author: Sam Rushing <rushing@nightmare.com>
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+# 
+#                         All Rights Reserved
+# 
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+# 
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""A class supporting chat-style (command/response) protocols.
+
+This class adds support for 'chat' style protocols - where one side
+sends a 'command', and the other sends a response (examples would be
+the common internet protocols - smtp, nntp, ftp, etc..).
+
+The handle_read() method looks at the input stream for the current
+'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
+for multi-line output), calling self.found_terminator() on its
+receipt.
+
+for example:
+Say you build an async nntp client using this class.  At the start
+of the connection, you'll have self.terminator set to '\r\n', in
+order to process the single-line greeting.  Just before issuing a
+'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST
+command will be accumulated (using your own 'collect_incoming_data'
+method) up to the terminator, and then control will be returned to
+you - by calling your self.found_terminator() method.
+"""
+
+import socket
+import asyncore
+import string
+
+class async_chat (asyncore.dispatcher):
+       """This is an abstract class.  You must derive from this class, and add
+       the two methods collect_incoming_data() and found_terminator()"""
+
+       # these are overridable defaults
+
+       ac_in_buffer_size       = 4096
+       ac_out_buffer_size      = 4096
+
+       def __init__ (self, conn=None):
+               self.ac_in_buffer = ''
+               self.ac_out_buffer = ''
+               self.producer_fifo = fifo()
+               asyncore.dispatcher.__init__ (self, conn)
+
+       def set_terminator (self, term):
+               "Set the input delimiter.  Can be a fixed string of any length, an integer, or None"
+               self.terminator = term
+
+       def get_terminator (self):
+               return self.terminator
+
+       # grab some more data from the socket,
+       # throw it to the collector method,
+       # check for the terminator,
+       # if found, transition to the next state.
+
+       def handle_read (self):
+
+               try:
+                       data = self.recv (self.ac_in_buffer_size)
+               except socket.error, why:
+                       self.handle_error()
+                       return
+
+               self.ac_in_buffer = self.ac_in_buffer + data
+
+               # Continue to search for self.terminator in self.ac_in_buffer,
+               # while calling self.collect_incoming_data.  The while loop
+               # is necessary because we might read several data+terminator
+               # combos with a single recv(1024).
+
+               while self.ac_in_buffer:
+                       lb = len(self.ac_in_buffer)
+                       terminator = self.get_terminator()
+                       if terminator is None:
+                               # no terminator, collect it all
+                               self.collect_incoming_data (self.ac_in_buffer)
+                               self.ac_in_buffer = ''
+                       elif type(terminator) == type(0):
+                               # numeric terminator
+                               n = terminator
+                               if lb < n:
+                                       self.collect_incoming_data (self.ac_in_buffer)
+                                       self.ac_in_buffer = ''
+                                       self.terminator = self.terminator - lb
+                               else:
+                                       self.collect_incoming_data (self.ac_in_buffer[:n])
+                                       self.ac_in_buffer = self.ac_in_buffer[n:]
+                                       self.terminator = 0
+                                       self.found_terminator()
+                       else:
+                               # 3 cases:
+                               # 1) end of buffer matches terminator exactly:
+                               #    collect data, transition
+                               # 2) end of buffer matches some prefix:
+                               #    collect data to the prefix
+                               # 3) end of buffer does not match any prefix:
+                               #    collect data
+                               terminator_len = len(terminator)
+                               index = string.find (self.ac_in_buffer, terminator)
+                               if index != -1:
+                                       # we found the terminator
+                                       if index > 0:
+                                               # don't bother reporting the empty string (source of subtle bugs)
+                                               self.collect_incoming_data (self.ac_in_buffer[:index])
+                                       self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
+                                       # This does the Right Thing if the terminator is changed here.
+                                       self.found_terminator()
+                               else:
+                                       # check for a prefix of the terminator
+                                       index = find_prefix_at_end (self.ac_in_buffer, terminator)
+                                       if index:
+                                               if index != lb:
+                                                       # we found a prefix, collect up to the prefix
+                                                       self.collect_incoming_data (self.ac_in_buffer[:-index])
+                                                       self.ac_in_buffer = self.ac_in_buffer[-index:]
+                                               break
+                                       else:
+                                               # no prefix, collect it all
+                                               self.collect_incoming_data (self.ac_in_buffer)
+                                               self.ac_in_buffer = ''
+
+       def handle_write (self):
+               self.initiate_send ()
+               
+       def handle_close (self):
+               self.close()
+
+       def push (self, data):
+               self.producer_fifo.push (simple_producer (data))
+               self.initiate_send()
+
+       def push_with_producer (self, producer):
+               self.producer_fifo.push (producer)
+               self.initiate_send()
+
+       def readable (self):
+               "predicate for inclusion in the readable for select()"
+               return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
+
+       def writable (self):
+               "predicate for inclusion in the writable for select()"
+               # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
+               # this is about twice as fast, though not as clear.
+               return not (
+                       (self.ac_out_buffer is '') and
+                       self.producer_fifo.is_empty() and
+                       self.connected
+                       )
+
+       def close_when_done (self):
+               "automatically close this channel once the outgoing queue is empty"
+               self.producer_fifo.push (None)
+
+       # refill the outgoing buffer by calling the more() method
+       # of the first producer in the queue
+       def refill_buffer (self):
+               _string_type = type('')
+               while 1:
+                       if len(self.producer_fifo):
+                               p = self.producer_fifo.first()
+                               # a 'None' in the producer fifo is a sentinel,
+                               # telling us to close the channel.
+                               if p is None:
+                                       if not self.ac_out_buffer:
+                                               self.producer_fifo.pop()
+                                               self.close()
+                                       return
+                               elif type(p) is _string_type:
+                                       self.producer_fifo.pop()
+                                       self.ac_out_buffer = self.ac_out_buffer + p
+                                       return
+                               data = p.more()
+                               if data:
+                                       self.ac_out_buffer = self.ac_out_buffer + data
+                                       return
+                               else:
+                                       self.producer_fifo.pop()
+                       else:
+                               return
+
+       def initiate_send (self):
+               obs = self.ac_out_buffer_size
+               # try to refill the buffer
+               if (len (self.ac_out_buffer) < obs):
+                       self.refill_buffer()
+
+               if self.ac_out_buffer and self.connected:
+                       # try to send the buffer
+                       try:
+                               num_sent = self.send (self.ac_out_buffer[:obs])
+                               if num_sent:
+                                       self.ac_out_buffer = self.ac_out_buffer[num_sent:]
+
+                       except socket.error, why:
+                               self.handle_error()
+                               return
+
+       def discard_buffers (self):
+               # Emergencies only!
+               self.ac_in_buffer = ''
+               self.ac_out_buffer = ''
+               while self.producer_fifo:
+                       self.producer_fifo.pop()
+
+
+class simple_producer:
+
+       def __init__ (self, data, buffer_size=512):
+               self.data = data
+               self.buffer_size = buffer_size
+
+       def more (self):
+               if len (self.data) > self.buffer_size:
+                       result = self.data[:self.buffer_size]
+                       self.data = self.data[self.buffer_size:]
+                       return result
+               else:
+                       result = self.data
+                       self.data = ''
+                       return result
+
+class fifo:
+       def __init__ (self, list=None):
+               if not list:
+                       self.list = []
+               else:
+                       self.list = list
+               
+       def __len__ (self):
+               return len(self.list)
+
+       def is_empty (self):
+               return self.list == []
+
+       def first (self):
+               return self.list[0]
+
+       def push (self, data):
+               self.list.append (data)
+
+       def pop (self):
+               if self.list:
+                       result = self.list[0]
+                       del self.list[0]
+                       return (1, result)
+               else:
+                       return (0, None)
+
+# Given 'haystack', see if any prefix of 'needle' is at its end.  This
+# assumes an exact match has already been checked.  Return the number of
+# characters matched.
+# for example:
+# f_p_a_e ("qwerty\r", "\r\n") => 1
+# f_p_a_e ("qwertydkjf", "\r\n") => 0
+# f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>
+
+# this could maybe be made faster with a computed regex?
+# [answer: no; circa Python-2.0, Jan 2001]
+# new python:   28961/s
+# old python:   18307/s
+# re:           12820/s
+# regex:        14035/s
+
+def find_prefix_at_end (haystack, needle):
+       l = len(needle) - 1
+       while l and not haystack.endswith(needle[:l]):
+               l -= 1
+       return l
diff --git a/demo/medusa/asyncore.py b/demo/medusa/asyncore.py
new file mode 100644 (file)
index 0000000..7f751aa
--- /dev/null
@@ -0,0 +1,552 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#      $Id: asyncore.py 299 2005-06-09 17:32:28Z heikki $
+#      Author: Sam Rushing <rushing@nightmare.com>
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+# 
+#                         All Rights Reserved
+# 
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+# 
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""Basic infrastructure for asynchronous socket service clients and servers.
+
+There are only two ways to have a program on a single processor do "more
+than one thing at a time".  Multi-threaded programming is the simplest and 
+most popular way to do it, but there is another very different technique,
+that lets you have nearly all the advantages of multi-threading, without
+actually using multiple threads. it's really only practical if your program
+is largely I/O bound. If your program is CPU bound, then pre-emptive
+scheduled threads are probably what you really need. Network servers are
+rarely CPU-bound, however. 
+
+If your operating system supports the select() system call in its I/O 
+library (and nearly all do), then you can use it to juggle multiple
+communication channels at once; doing other work while your I/O is taking
+place in the "background."  Although this strategy can seem strange and
+complex, especially at first, it is in many ways easier to understand and
+control than multi-threaded programming. The module documented here solves
+many of the difficult problems for you, making the task of building
+sophisticated high-performance network servers and clients a snap. 
+"""
+
+import exceptions
+import select
+import socket
+import string
+import sys
+
+import os
+if os.name == 'nt':
+       EWOULDBLOCK     = 10035
+       EINPROGRESS     = 10036
+       EALREADY        = 10037
+       ECONNRESET  = 10054
+       ENOTCONN        = 10057
+       ESHUTDOWN       = 10058
+else:
+       from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN
+
+try:
+       socket_map
+except NameError:
+       socket_map = {}
+
+class ExitNow (exceptions.Exception):
+       pass
+
+DEBUG = 0
+
+def poll (timeout=0.0, map=None):
+       global DEBUG
+       if map is None:
+               map = socket_map
+       if map:
+               r = []; w = []; e = []
+               for fd, obj in map.items():
+                       if obj.readable():
+                               r.append (fd)
+                       if obj.writable():
+                               w.append (fd)
+               r,w,e = select.select (r,w,e, timeout)
+
+               if DEBUG:
+                       print r,w,e
+
+               for fd in r:
+                       try:
+                               obj = map[fd]
+                               try:
+                                       obj.handle_read_event()
+                               except ExitNow:
+                                       raise ExitNow
+                               except:
+                                       obj.handle_error()
+                       except KeyError:
+                               pass
+
+               for fd in w:
+                       try:
+                               obj = map[fd]
+                               try:
+                                       obj.handle_write_event()
+                               except ExitNow:
+                                       raise ExitNow
+                               except:
+                                       obj.handle_error()
+                       except KeyError:
+                               pass
+
+def poll2 (timeout=0.0, map=None):
+       import poll
+       if map is None:
+               map=socket_map
+       # timeout is in milliseconds
+       timeout = int(timeout*1000)
+       if map:
+               l = []
+               for fd, obj in map.items():
+                       flags = 0
+                       if obj.readable():
+                               flags = poll.POLLIN
+                       if obj.writable():
+                               flags = flags | poll.POLLOUT
+                       if flags:
+                               l.append ((fd, flags))
+               r = poll.poll (l, timeout)
+               for fd, flags in r:
+                       try:
+                               obj = map[fd]
+                               try:
+                                       if (flags  & poll.POLLIN):
+                                               obj.handle_read_event()
+                                       if (flags & poll.POLLOUT):
+                                               obj.handle_write_event()
+                               except ExitNow:
+                                       raise ExitNow
+                               except:
+                                       obj.handle_error()
+                       except KeyError:
+                               pass
+
+def poll3 (timeout=0.0, map=None):
+    # Use the poll() support added to the select module in Python 2.0
+    if map is None:
+        map=socket_map
+    # timeout is in milliseconds
+    timeout = int(timeout*1000)
+    pollster = select.poll()
+    if map:
+        l = []
+        for fd, obj in map.items():
+            flags = 0
+            if obj.readable():
+                flags = select.POLLIN
+            if obj.writable():
+                flags = flags | select.POLLOUT
+            if flags:
+                pollster.register(fd, flags)
+        r = pollster.poll (timeout)
+        for fd, flags in r:
+            try:
+                obj = map[fd]
+                try:
+                    if (flags  & select.POLLIN):
+                        obj.handle_read_event()
+                    if (flags & select.POLLOUT):
+                        obj.handle_write_event()
+                except ExitNow:
+                    raise ExitNow
+                except:
+                    obj.handle_error()
+            except KeyError:
+                pass
+
+def loop (timeout=30.0, use_poll=0, map=None):
+
+       if use_poll:
+               if hasattr (select, 'poll'):
+                       poll_fun = poll3
+               else:
+                       poll_fun = poll2
+       else:
+               poll_fun = poll
+
+       if map is None:
+               map=socket_map
+
+       while map:
+               poll_fun (timeout, map)
+
+class dispatcher:
+       debug = 0
+       connected = 0
+       accepting = 0
+       closing = 0
+       addr = None
+
+       def __init__ (self, sock=None, map=None):
+               if sock:
+                       self.set_socket (sock, map)
+                       # I think it should inherit this anyway
+                       self.socket.setblocking (0)
+                       self.connected = 1
+
+       def __repr__ (self):
+               try:
+                       status = []
+                       if self.accepting and self.addr:
+                               status.append ('listening')
+                       elif self.connected:
+                               status.append ('connected')
+                       if self.addr:
+                               status.append ('%s:%d' % self.addr)
+                       return '<%s %s at %x>' % (
+                               self.__class__.__name__,
+                               string.join (status, ' '),
+                               id(self)
+                               )
+               except:
+                       try:
+                               ar = repr(self.addr)
+                       except:
+                               ar = 'no self.addr!'
+                               
+                       return '<__repr__ (self) failed for object at %x (addr=%s)>' % (id(self),ar)
+
+       def add_channel (self, map=None):
+               #self.log_info ('adding channel %s' % self)
+               if map is None:
+                       map=socket_map
+               map [self._fileno] = self
+
+       def del_channel (self, map=None):
+               fd = self._fileno
+               if map is None:
+                       map=socket_map
+               if map.has_key (fd):
+                       #self.log_info ('closing channel %d:%s' % (fd, self))
+                       del map [fd]
+
+       def create_socket (self, family, type):
+               self.family_and_type = family, type
+               self.socket = socket.socket (family, type)
+               self.socket.setblocking(0)
+               self._fileno = self.socket.fileno()
+               self.add_channel()
+
+       def set_socket (self, sock, map=None):
+               self.__dict__['socket'] = sock
+               self._fileno = sock.fileno()
+               self.add_channel (map)
+
+       def set_reuse_addr (self):
+               # try to re-use a server port if possible
+               try:
+                       self.socket.setsockopt (
+                               socket.SOL_SOCKET, socket.SO_REUSEADDR,
+                               self.socket.getsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1
+                               )
+               except:
+                       pass
+
+       # ==================================================
+       # predicates for select()
+       # these are used as filters for the lists of sockets
+       # to pass to select().
+       # ==================================================
+
+       def readable (self):
+               return 1
+
+       if os.name == 'mac':
+               # The macintosh will select a listening socket for
+               # write if you let it.  What might this mean?
+               def writable (self):
+                       return not self.accepting
+       else:
+               def writable (self):
+                       return 1
+
+       # ==================================================
+       # socket object methods.
+       # ==================================================
+
+       def listen (self, num):
+               self.accepting = 1
+               if os.name == 'nt' and num > 5:
+                       num = 1
+               return self.socket.listen (num)
+
+       def bind (self, addr):
+               self.addr = addr
+               return self.socket.bind (addr)
+
+       def connect (self, address):
+               self.connected = 0
+               try:
+                       self.socket.connect (address)
+               except socket.error, why:
+                       if why[0] in (EINPROGRESS, EALREADY, EWOULDBLOCK):
+                               return
+                       else:
+                               raise socket.error, why
+               self.connected = 1
+               self.handle_connect()
+
+       def accept (self):
+               try:
+                       conn, addr = self.socket.accept()
+                       return conn, addr
+               except socket.error, why:
+                       if why[0] == EWOULDBLOCK:
+                               pass
+                       else:
+                               raise socket.error, why
+
+       def send (self, data):
+               try:
+                       result = self.socket.send (data)
+                       return result
+               except socket.error, why:
+                       if why[0] == EWOULDBLOCK:
+                               return 0
+                       else:
+                               raise socket.error, why
+                       return 0
+
+       def recv (self, buffer_size):
+               try:
+                       data = self.socket.recv (buffer_size)
+                       if not data:
+                               # a closed connection is indicated by signaling
+                               # a read condition, and having recv() return 0.
+                               self.handle_close()
+                               return ''
+                       else:
+                               return data
+               except socket.error, why:
+                       # winsock sometimes throws ENOTCONN
+                       if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
+                               self.handle_close()
+                               return ''
+                       else:
+                               raise socket.error, why
+
+       def close (self):
+               self.del_channel()
+               self.socket.close()
+
+       # cheap inheritance, used to pass all other attribute
+       # references to the underlying socket object.
+       def __getattr__ (self, attr):
+               return getattr (self.socket, attr)
+
+       # log and log_info maybe overriden to provide more sophisitcated
+       # logging and warning methods. In general, log is for 'hit' logging
+       # and 'log_info' is for informational, warning and error logging. 
+
+       def log (self, message):
+               sys.stderr.write ('log: %s\n' % str(message))
+
+       def log_info (self, message, type='info'):
+               if __debug__ or type != 'info':
+                       print '%s: %s' % (type, message)
+
+       def handle_read_event (self):
+               if self.accepting:
+                       # for an accepting socket, getting a read implies
+                       # that we are connected
+                       if not self.connected:
+                               self.connected = 1
+                       self.handle_accept()
+               elif not self.connected:
+                       self.handle_connect()
+                       self.connected = 1
+                       self.handle_read()
+               else:
+                       self.handle_read()
+
+       def handle_write_event (self):
+               # getting a write implies that we are connected
+               if not self.connected:
+                       self.handle_connect()
+                       self.connected = 1
+               self.handle_write()
+
+       def handle_expt_event (self):
+               self.handle_expt()
+
+       def handle_error (self):
+               (file,fun,line), t, v, tbinfo = compact_traceback()
+
+               # sometimes a user repr method will crash.
+               try:
+                       self_repr = repr (self)
+               except:
+                       self_repr = '<__repr__ (self) failed for object at %0x>' % id(self)
+
+               self.log_info (
+                       'uncaptured python exception, closing channel %s (%s:%s %s)' % (
+                               self_repr,
+                               t,
+                               v,
+                               tbinfo
+                               ),
+                       'error'
+                       )
+               self.close()
+
+       def handle_expt (self):
+               self.log_info ('unhandled exception', 'warning')
+
+       def handle_read (self):
+               self.log_info ('unhandled read event', 'warning')
+
+       def handle_write (self):
+               self.log_info ('unhandled write event', 'warning')
+
+       def handle_connect (self):
+               self.log_info ('unhandled connect event', 'warning')
+
+       def handle_accept (self):
+               self.log_info ('unhandled accept event', 'warning')
+
+       def handle_close (self):
+               self.log_info ('unhandled close event', 'warning')
+               self.close()
+
+# ---------------------------------------------------------------------------
+# adds simple buffered output capability, useful for simple clients.
+# [for more sophisticated usage use asynchat.async_chat]
+# ---------------------------------------------------------------------------
+
+class dispatcher_with_send (dispatcher):
+       def __init__ (self, sock=None):
+               dispatcher.__init__ (self, sock)
+               self.out_buffer = ''
+
+       def initiate_send (self):
+               num_sent = 0
+               num_sent = dispatcher.send (self, self.out_buffer[:512])
+               self.out_buffer = self.out_buffer[num_sent:]
+
+       def handle_write (self):
+               self.initiate_send()
+
+       def writable (self):
+               return (not self.connected) or len(self.out_buffer)
+
+       def send (self, data):
+               if self.debug:
+                       self.log_info ('sending %s' % repr(data))
+               self.out_buffer = self.out_buffer + data
+               self.initiate_send()
+
+# ---------------------------------------------------------------------------
+# used for debugging.
+# ---------------------------------------------------------------------------
+
+def compact_traceback ():
+       t,v,tb = sys.exc_info()
+       tbinfo = []
+       while 1:
+               tbinfo.append ((
+                       tb.tb_frame.f_code.co_filename,
+                       tb.tb_frame.f_code.co_name,                             
+                       str(tb.tb_lineno)
+                       ))
+               tb = tb.tb_next
+               if not tb:
+                       break
+
+       # just to be safe
+       del tb
+
+       file, function, line = tbinfo[-1]
+       info = '[' + string.join (
+               map (
+                       lambda x: string.join (x, '|'),
+                       tbinfo
+                       ),
+               '] ['
+               ) + ']'
+       return (file, function, line), t, v, info
+
+def close_all (map=None):
+       if map is None:
+               map=socket_map
+       for x in map.values():
+               x.socket.close()
+       map.clear()
+
+# Asynchronous File I/O:
+#
+# After a little research (reading man pages on various unixen, and
+# digging through the linux kernel), I've determined that select()
+# isn't meant for doing doing asynchronous file i/o.
+# Heartening, though - reading linux/mm/filemap.c shows that linux
+# supports asynchronous read-ahead.  So _MOST_ of the time, the data
+# will be sitting in memory for us already when we go to read it.
+#
+# What other OS's (besides NT) support async file i/o?  [VMS?]
+#
+# Regardless, this is useful for pipes, and stdin/stdout...
+
+import os
+if os.name == 'posix':
+       import fcntl
+       import FCNTL
+
+       class file_wrapper:
+               # here we override just enough to make a file
+               # look like a socket for the purposes of asyncore.
+               def __init__ (self, fd):
+                       self.fd = fd
+
+               def recv (self, *args):
+                       return apply (os.read, (self.fd,)+args)
+
+               def send (self, *args):
+                       return apply (os.write, (self.fd,)+args)
+
+               read = recv
+               write = send
+
+               def close (self):
+                       return os.close (self.fd)
+
+               def fileno (self):
+                       return self.fd
+
+       class file_dispatcher (dispatcher):
+               def __init__ (self, fd):
+                       dispatcher.__init__ (self)
+                       self.connected = 1
+                       # set it to non-blocking mode
+                       flags = fcntl.fcntl (fd, FCNTL.F_GETFL, 0)
+                       flags = flags | FCNTL.O_NONBLOCK
+                       fcntl.fcntl (fd, FCNTL.F_SETFL, flags)
+                       self.set_file (fd)
+
+               def set_file (self, fd):
+                       self._fileno = fd
+                       self.socket = file_wrapper (fd)
+                       self.add_channel()
+
diff --git a/demo/medusa/auth_handler.py b/demo/medusa/auth_handler.py
new file mode 100644 (file)
index 0000000..70eaf3c
--- /dev/null
@@ -0,0 +1,135 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1996-2000 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+# support for 'basic' authenticaion.
+
+import base64
+import md5
+import re
+import string
+import time
+import counter
+
+import default_handler
+
+get_header = default_handler.get_header
+
+import http_server
+import producers
+
+# This is a 'handler' that wraps an authorization method
+# around access to the resources normally served up by
+# another handler.
+
+# does anyone support digest authentication? (rfc2069)
+
+class auth_handler:
+       def __init__ (self, dict, handler, realm='default'):
+               self.authorizer = dictionary_authorizer (dict)
+               self.handler = handler
+               self.realm = realm
+               self.pass_count = counter.counter()
+               self.fail_count = counter.counter()
+
+       def match (self, request):
+               # by default, use the given handler's matcher
+               return self.handler.match (request)
+                               
+       def handle_request (self, request):
+               # authorize a request before handling it...
+               scheme = get_header (AUTHORIZATION, request.header)
+
+               if scheme:
+                       scheme = string.lower (scheme)
+                       if scheme == 'basic':
+                               cookie = AUTHORIZATION.group(2)
+                               try:
+                                       decoded = base64.decodestring (cookie)
+                               except:
+                                       print 'malformed authorization info <%s>' % cookie
+                                       request.error (400)
+                                       return
+                               auth_info = string.split (decoded, ':')
+                               if self.authorizer.authorize (auth_info):
+                                       self.pass_count.increment()
+                                       request.auth_info = auth_info
+                                       self.handler.handle_request (request)
+                               else:
+                                       self.handle_unauthorized (request)
+                       #elif scheme == 'digest':
+                       #       print 'digest: ',AUTHORIZATION.group(2)
+                       else:
+                               print 'unknown/unsupported auth method: %s' % scheme
+                               self.handle_unauthorized()
+               else:
+                       # list both?  prefer one or the other?
+                       # you could also use a 'nonce' here. [see below]
+                       #auth = 'Basic realm="%s" Digest realm="%s"' % (self.realm, self.realm)
+                       #nonce = self.make_nonce (request)
+                       #auth = 'Digest realm="%s" nonce="%s"' % (self.realm, nonce)
+                       #request['WWW-Authenticate'] = auth
+                       #print 'sending header: %s' % request['WWW-Authenticate']
+                       self.handle_unauthorized (request)
+               
+       def handle_unauthorized (self, request):
+               # We are now going to receive data that we want to ignore.
+               # to ignore the file data we're not interested in.
+               self.fail_count.increment()
+               request.channel.set_terminator (None)
+               request['Connection'] = 'close'
+               request['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
+               request.error (401)
+
+       def make_nonce (self, request):
+               "A digest-authentication <nonce>, constructed as suggested in RFC 2069"
+               ip = request.channel.server.ip
+               now = str (long (time.time()))[:-1]
+               private_key = str (id (self))
+               nonce = string.join ([ip, now, private_key], ':')
+               return self.apply_hash (nonce)
+
+       def apply_hash (self, s):
+               "Apply MD5 to a string <s>, then wrap it in base64 encoding."
+               m = md5.new()
+               m.update (s)
+               d = m.digest()
+               # base64.encodestring tacks on an extra linefeed.
+               return base64.encodestring (d)[:-1]
+
+       def status (self):
+               # Thanks to mwm@contessa.phone.net (Mike Meyer)
+               r = [
+                       producers.simple_producer (
+                               '<li>Authorization Extension : '
+                               '<b>Unauthorized requests:</b> %s<ul>' % self.fail_count
+                               )
+                       ]
+               if hasattr (self.handler, 'status'):
+                       r.append (self.handler.status())
+               r.append (
+                       producers.simple_producer ('</ul>')
+                       )
+               return producers.composite_producer (
+                       http_server.fifo (r)
+                       )
+
+class dictionary_authorizer:
+       def __init__ (self, dict):
+               self.dict = dict
+
+       def authorize (self, auth_info):
+               [username, password] = auth_info
+               if (self.dict.has_key (username)) and (self.dict[username] == password):
+                       return 1
+               else:
+                       return 0
+
+AUTHORIZATION = re.compile (
+       #               scheme  challenge
+       'Authorization: ([^ ]+) (.*)',
+       re.IGNORECASE
+       )
diff --git a/demo/medusa/ca.pem b/demo/medusa/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/medusa/counter.py b/demo/medusa/counter.py
new file mode 100644 (file)
index 0000000..997d687
--- /dev/null
@@ -0,0 +1,48 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# It is tempting to add an __int__ method to this class, but it's not
+# a good idea.  This class tries to gracefully handle integer
+# overflow, and to hide this detail from both the programmer and the
+# user.  Note that the __str__ method can be relied on for printing out
+# the value of a counter:
+#
+# >>> print 'Total Client: %s' % self.total_clients
+#
+# If you need to do arithmetic with the value, then use the 'as_long'
+# method, the use of long arithmetic is a reminder that the counter
+# will overflow.
+
+class counter:
+       "general-purpose counter"
+
+       def __init__ (self, initial_value=0):
+               self.value = initial_value
+       
+       def increment (self, delta=1):
+               result = self.value
+               try:
+                       self.value = self.value + delta
+               except OverflowError:
+                       self.value = long(self.value) + delta
+               return result
+
+       def decrement (self, delta=1):
+               result = self.value
+               try:
+                       self.value = self.value - delta
+               except OverflowError:
+                       self.value = long(self.value) - delta
+               return result
+
+       def as_long (self):
+               return long(self.value)
+
+       def __nonzero__ (self):
+               return self.value != 0
+
+       def __repr__ (self):
+               return '<counter value=%s at %x>' % (self.value, id(self))
+
+       def __str__ (self):
+               return str(long(self.value))
+               #return str(long(self.value))[:-1]
diff --git a/demo/medusa/default_handler.py b/demo/medusa/default_handler.py
new file mode 100644 (file)
index 0000000..4585b0f
--- /dev/null
@@ -0,0 +1,215 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1997 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+# standard python modules
+import os
+import re
+import posixpath
+import stat
+import string
+import time
+
+# medusa modules
+import http_date
+import http_server
+import mime_type_table
+import status_handler
+import producers
+
+unquote = http_server.unquote
+
+# This is the 'default' handler.  it implements the base set of
+# features expected of a simple file-delivering HTTP server.  file
+# services are provided through a 'filesystem' object, the very same
+# one used by the FTP server.
+#
+# You can replace or modify this handler if you want a non-standard
+# HTTP server.  You can also derive your own handler classes from
+# it.
+#
+# support for handling POST requests is available in the derived
+# class <default_with_post_handler>, defined below.
+#
+
+from counter import counter
+
+class default_handler:
+
+       valid_commands = ['get', 'head']
+
+       IDENT = 'Default HTTP Request Handler'
+
+       # Pathnames that are tried when a URI resolves to a directory name
+       directory_defaults = [
+               'index.html',
+               'default.html'
+               ]
+
+       default_file_producer = producers.file_producer
+
+       def __init__ (self, filesystem):
+               self.filesystem = filesystem
+               # count total hits
+               self.hit_counter = counter()
+               # count file deliveries
+               self.file_counter = counter()
+               # count cache hits
+               self.cache_counter = counter()
+
+       hit_counter = 0
+
+       def __repr__ (self):
+               return '<%s (%s hits) at %x>' % (
+                       self.IDENT,
+                       self.hit_counter,
+                       id (self)
+                       )
+
+       # always match, since this is a default
+       def match (self, request):
+               return 1
+
+       # handle a file request, with caching.
+
+       def handle_request (self, request):
+
+               if request.command not in self.valid_commands:
+                       request.error (400) # bad request
+                       return
+
+               self.hit_counter.increment()
+
+               path, params, query, fragment = request.split_uri()
+
+               if '%' in path:
+                       path = unquote (path)
+
+               # strip off all leading slashes
+               while path and path[0] == '/':
+                       path = path[1:]
+
+               if self.filesystem.isdir (path):
+                       if path and path[-1] != '/':
+                               request['Location'] = 'http://%s/%s/' % (
+                                       request.channel.server.server_name,
+                                       path
+                                       )
+                               request.error (301)
+                               return
+
+                       # we could also generate a directory listing here,
+                       # may want to move this into another method for that
+                       # purpose
+                       found = 0
+                       if path and path[-1] != '/':
+                               path = path + '/'
+                       for default in self.directory_defaults:
+                               p = path + default
+                               if self.filesystem.isfile (p):
+                                       path = p
+                                       found = 1
+                                       break
+                       if not found:
+                               request.error (404) # Not Found 
+                               return
+
+               elif not self.filesystem.isfile (path):
+                       request.error (404) # Not Found
+                       return
+
+               file_length = self.filesystem.stat (path)[stat.ST_SIZE]
+
+               ims = get_header_match (IF_MODIFIED_SINCE, request.header)
+
+               length_match = 1
+               if ims:
+                       length = ims.group (4)
+                       if length:
+                               try:
+                                       length = string.atoi (length)
+                                       if length != file_length:
+                                               length_match = 0
+                               except:
+                                       pass
+
+               ims_date = 0
+
+               if ims:
+                       ims_date = http_date.parse_http_date (ims.group (1))
+
+               try:
+                       mtime = self.filesystem.stat (path)[stat.ST_MTIME]
+               except:
+                       request.error (404)
+                       return
+
+               if length_match and ims_date:
+                       if mtime <= ims_date:
+                               request.reply_code = 304
+                               request.done()
+                               self.cache_counter.increment()
+                               return
+               try:
+                       file = self.filesystem.open (path, 'rb')
+               except IOError:
+                       request.error (404)
+                       return
+
+               request['Last-Modified'] = http_date.build_http_date (mtime)
+               request['Content-Length'] = file_length
+               self.set_content_type (path, request)
+
+               if request.command == 'get':
+                       request.push (self.default_file_producer (file))
+
+               self.file_counter.increment()
+               request.done()
+
+       def set_content_type (self, path, request):
+               ext = string.lower (get_extension (path))
+               if mime_type_table.content_type_map.has_key (ext):
+                       request['Content-Type'] = mime_type_table.content_type_map[ext]
+               else:
+                       # TODO: test a chunk off the front of the file for 8-bit
+                       # characters, and use application/octet-stream instead.
+                       request['Content-Type'] = 'text/plain'
+
+       def status (self):
+               return producers.simple_producer (
+                       '<li>%s' % status_handler.html_repr (self)
+                       + '<ul>'
+                       + '  <li><b>Total Hits:</b> %s'                 % self.hit_counter
+                       + '  <li><b>Files Delivered:</b> %s'    % self.file_counter
+                       + '  <li><b>Cache Hits:</b> %s'                 % self.cache_counter
+                       + '</ul>'
+                       )
+
+# HTTP/1.0 doesn't say anything about the "; length=nnnn" addition
+# to this header.  I suppose it's purpose is to avoid the overhead
+# of parsing dates...
+IF_MODIFIED_SINCE = re.compile (
+       'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',
+       re.IGNORECASE
+       )
+
+USER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)
+
+CONTENT_TYPE = re.compile (
+       r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\'\(\)+_,./:=?-]+)$)|$)',
+       re.IGNORECASE
+       )
+
+get_header = http_server.get_header
+get_header_match = http_server.get_header_match
+
+def get_extension (path):
+       dirsep = string.rfind (path, '/')
+       dotsep = string.rfind (path, '.')
+       if dotsep > dirsep:
+               return path[dotsep+1:]
+       else:
+               return ''
diff --git a/demo/medusa/dh1024.pem b/demo/medusa/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/medusa/filesys.py b/demo/medusa/filesys.py
new file mode 100644 (file)
index 0000000..003af2e
--- /dev/null
@@ -0,0 +1,466 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#      $Id: filesys.py 299 2005-06-09 17:32:28Z heikki $
+#      Author: Sam Rushing <rushing@nightmare.com>
+#
+# Generic filesystem interface.
+#
+
+# We want to provide a complete wrapper around any and all
+# filesystem operations.
+
+# this class is really just for documentation,
+# identifying the API for a filesystem object.
+
+# opening files for reading, and listing directories, should
+# return a producer.
+
+class abstract_filesystem:
+       def __init__ (self):
+               pass
+
+       def current_directory (self):
+               "Return a string representing the current directory."
+               pass
+
+       def listdir (self, path, long=0):
+               """Return a listing of the directory at 'path' The empty string
+               indicates the current directory.  If 'long' is set, instead
+               return a list of (name, stat_info) tuples
+               """
+               pass
+
+       def open (self, path, mode):
+               "Return an open file object"
+               pass
+
+       def stat (self, path):
+               "Return the equivalent of os.stat() on the given path."
+               pass
+
+       def isdir (self, path):
+               "Does the path represent a directory?"
+               pass
+
+       def isfile (self, path):
+               "Does the path represent a plain file?"
+               pass
+
+       def cwd (self, path):
+               "Change the working directory."
+               pass
+
+       def cdup (self):
+               "Change to the parent of the current directory."
+               pass
+
+
+       def longify (self, path):
+               """Return a 'long' representation of the filename
+               [for the output of the LIST command]"""
+               pass
+
+# standard wrapper around a unix-like filesystem, with a 'false root'
+# capability.
+
+# security considerations: can symbolic links be used to 'escape' the
+# root?  should we allow it?  if not, then we could scan the
+# filesystem on startup, but that would not help if they were added
+# later.  We will probably need to check for symlinks in the cwd method.
+
+# what to do if wd is an invalid directory?
+
+import os
+import stat
+
+import string
+
+def safe_stat (path):
+       try:
+               return (path, os.stat (path))
+       except:
+               return None
+
+import regsub
+import glob
+
+class os_filesystem:
+       path_module = os.path
+
+       # set this to zero if you want to disable pathname globbing.
+       # [we currently don't glob, anyway]
+       do_globbing = 1
+
+       def __init__ (self, root, wd='/'):
+               self.root = root
+               self.wd = wd
+
+       def current_directory (self):
+               return self.wd
+
+       def isfile (self, path):
+               p = self.normalize (self.path_module.join (self.wd, path))
+               return self.path_module.isfile (self.translate(p))
+
+       def isdir (self, path):
+               p = self.normalize (self.path_module.join (self.wd, path))
+               return self.path_module.isdir (self.translate(p))
+
+       def cwd (self, path):
+               p = self.normalize (self.path_module.join (self.wd, path))
+               translated_path = self.translate(p)
+               if not self.path_module.isdir (translated_path):
+                       return 0
+               else:
+                       old_dir = os.getcwd()
+                       # temporarily change to that directory, in order
+                       # to see if we have permission to do so.
+                       try:
+                               can = 0
+                               try:
+                                       os.chdir (translated_path)
+                                       can = 1
+                                       self.wd = p
+                               except:
+                                       pass
+                       finally:
+                               if can:
+                                       os.chdir (old_dir)
+                       return can
+
+       def cdup (self):
+               return self.cwd ('..')
+
+       def listdir (self, path, long=0):
+               p = self.translate (path)
+               # I think we should glob, but limit it to the current
+               # directory only.
+               ld = os.listdir (p)
+               if not long:
+                       return list_producer (ld, 0, None)
+               else:
+                       old_dir = os.getcwd()
+                       try:
+                               os.chdir (p)
+                               # if os.stat fails we ignore that file.
+                               result = filter (None, map (safe_stat, ld))
+                       finally:
+                               os.chdir (old_dir)
+                       return list_producer (result, 1, self.longify)
+
+       # TODO: implement a cache w/timeout for stat()
+       def stat (self, path):
+               p = self.translate (path)
+               return os.stat (p)
+
+       def open (self, path, mode):
+               p = self.translate (path)
+               return open (p, mode)
+
+       def unlink (self, path):
+               p = self.translate (path)
+               return os.unlink (p)
+
+       def mkdir (self, path):
+               p = self.translate (path)
+               return os.mkdir (p)
+
+       def rmdir (self, path):
+               p = self.translate (path)
+               return os.rmdir (p)
+
+       # utility methods
+       def normalize (self, path):
+               # watch for the ever-sneaky '/+' path element
+               path = regsub.gsub ('/+', '/', path)
+               p = self.path_module.normpath (path)
+               # remove 'dangling' cdup's.
+               if len(p) > 2 and p[:3] == '/..':
+                       p = '/'
+               return p
+
+       def translate (self, path):
+               # we need to join together three separate
+               # path components, and do it safely.
+               # <real_root>/<current_directory>/<path>
+               # use the operating system's path separator.
+               path = string.join (string.split (path, '/'), os.sep)
+               p = self.normalize (self.path_module.join (self.wd, path))
+               p = self.normalize (self.path_module.join (self.root, p[1:]))
+               return p
+
+       def longify (self, (path, stat_info)):
+               return unix_longify (path, stat_info)
+
+       def __repr__ (self):
+               return '<unix-style fs root:%s wd:%s>' % (
+                       self.root,
+                       self.wd
+                       )
+
+if os.name == 'posix':
+
+       class unix_filesystem (os_filesystem):
+               pass
+
+       class schizophrenic_unix_filesystem (os_filesystem):
+               PROCESS_UID             = os.getuid()
+               PROCESS_EUID    = os.geteuid()
+               PROCESS_GID             = os.getgid()
+               PROCESS_EGID    = os.getegid()
+
+               def __init__ (self, root, wd='/', persona=(None, None)):
+                       os_filesystem.__init__ (self, root, wd)
+                       self.persona = persona
+
+               def become_persona (self):
+                       if self.persona is not (None, None):
+                               uid, gid = self.persona
+                               # the order of these is important!
+                               os.setegid (gid)
+                               os.seteuid (uid)
+
+               def become_nobody (self):
+                       if self.persona is not (None, None):
+                               os.seteuid (self.PROCESS_UID)
+                               os.setegid (self.PROCESS_GID)
+
+               # cwd, cdup, open, listdir
+               def cwd (self, path):
+                       try:
+                               self.become_persona()
+                               return os_filesystem.cwd (self, path)
+                       finally:
+                               self.become_nobody()
+
+               def cdup (self, path):
+                       try:
+                               self.become_persona()
+                               return os_filesystem.cdup (self)
+                       finally:
+                               self.become_nobody()
+
+               def open (self, filename, mode):
+                       try:
+                               self.become_persona()
+                               return os_filesystem.open (self, filename, mode)
+                       finally:
+                               self.become_nobody()
+
+               def listdir (self, path, long=0):
+                       try:
+                               self.become_persona()
+                               return os_filesystem.listdir (self, path, long)
+                       finally:
+                               self.become_nobody()
+
+# This hasn't been very reliable across different platforms.
+# maybe think about a separate 'directory server'.
+#
+#      import posixpath
+#      import fcntl
+#      import FCNTL
+#      import select
+#      import asyncore
+#
+#      # pipes /bin/ls for directory listings.
+#      class unix_filesystem (os_filesystem):
+#              pass
+#              path_module = posixpath
+#
+#              def listdir (self, path, long=0):
+#                      p = self.translate (path)
+#                      if not long:
+#                              return list_producer (os.listdir (p), 0, None)
+#                      else:
+#                              command = '/bin/ls -l %s' % p
+#                              print 'opening pipe to "%s"' % command
+#                              fd = os.popen (command, 'rt')
+#                              return pipe_channel (fd)
+#
+#      # this is both a dispatcher, _and_ a producer
+#      class pipe_channel (asyncore.file_dispatcher):
+#              buffer_size = 4096
+#
+#              def __init__ (self, fd):
+#                      asyncore.file_dispatcher.__init__ (self, fd)
+#                      self.fd = fd
+#                      self.done = 0
+#                      self.data = ''
+#
+#              def handle_read (self):
+#                      if len (self.data) < self.buffer_size:
+#                              self.data = self.data + self.fd.read (self.buffer_size)
+#                      #print '%s.handle_read() => len(self.data) == %d' % (self, len(self.data))
+#
+#              def handle_expt (self):
+#                      #print '%s.handle_expt()' % self
+#                      self.done = 1
+#
+#              def ready (self):
+#                      #print '%s.ready() => %d' % (self, len(self.data))
+#                      return ((len (self.data) > 0) or self.done)
+#
+#              def more (self):
+#                      if self.data:
+#                              r = self.data
+#                              self.data = ''
+#                      elif self.done:
+#                              self.close()
+#                              self.downstream.finished()
+#                              r = ''
+#                      else:
+#                              r = None
+#                      #print '%s.more() => %s' % (self, (r and len(r)))
+#                      return r
+
+# For the 'real' root, we could obtain a list of drives, and then
+# use that.  Doesn't win32 provide such a 'real' filesystem?
+# [yes, I think something like this "\\.\c\windows"]
+
+class msdos_filesystem (os_filesystem):
+       def longify (self, (path, stat_info)):
+               return msdos_longify (path, stat_info)
+
+# A merged filesystem will let you plug other filesystems together.
+# We really need the equivalent of a 'mount' capability - this seems
+# to be the most general idea.  So you'd use a 'mount' method to place
+# another filesystem somewhere in the hierarchy.
+
+# Note: this is most likely how I will handle ~user directories
+# with the http server.
+
+class merged_filesystem:
+       def __init__ (self, *fsys):
+               pass
+
+# this matches the output of NT's ftp server (when in
+# MSDOS mode) exactly.
+
+def msdos_longify (file, stat_info):
+       if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+               dir = '<DIR>'
+       else:
+               dir = '     '
+       date = msdos_date (stat_info[stat.ST_MTIME])
+       return '%s       %s %8d %s' % (
+               date,
+               dir,
+               stat_info[stat.ST_SIZE],
+               file
+               )
+
+def msdos_date (t):
+       try:
+               info = time.gmtime (t)
+       except:
+               info = time.gmtime (0)
+       # year, month, day, hour, minute, second, ...
+       if info[3] > 11:
+               merid = 'PM'
+               info[3] = info[3] - 12
+       else:
+               merid = 'AM'
+       return '%02d-%02d-%02d  %02d:%02d%s' % (
+               info[1],
+               info[2],
+               info[0]%100,
+               info[3],
+               info[4],
+               merid
+               )
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+mode_table = {
+       '0':'---',
+       '1':'--x',
+       '2':'-w-',
+       '3':'-wx',
+       '4':'r--',
+       '5':'r-x',
+       '6':'rw-',
+       '7':'rwx'
+       }
+
+import time
+
+def unix_longify (file, stat_info):
+       # for now, only pay attention to the lower bits
+       mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
+       mode = string.join (map (lambda x: mode_table[x], mode), '')
+       if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+               dirchar = 'd'
+       else:
+               dirchar = '-'
+       date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
+       return '%s%s %3d %-8d %-8d %8d %s %s' % (
+               dirchar,
+               mode,
+               stat_info[stat.ST_NLINK],
+               stat_info[stat.ST_UID],
+               stat_info[stat.ST_GID],
+               stat_info[stat.ST_SIZE],
+               date,
+               file
+               )
+               
+# Emulate the unix 'ls' command's date field.
+# it has two formats - if the date is more than 180
+# days in the past, then it's like this:
+# Oct 19  1995
+# otherwise, it looks like this:
+# Oct 19 17:33
+
+def ls_date (now, t):
+       try:
+               info = time.gmtime (t)
+       except:
+               info = time.gmtime (0)
+       # 15,600,000 == 86,400 * 180
+       if (now - t) > 15600000:
+               return '%s %2d  %d' % (
+                       months[info[1]-1],
+                       info[2],
+                       info[0]
+                       )
+       else:
+               return '%s %2d %02d:%02d' % (
+                       months[info[1]-1],
+                       info[2],
+                       info[3],
+                       info[4]
+                       )
+
+# ===========================================================================
+# Producers
+# ===========================================================================
+
+class list_producer:
+       def __init__ (self, file_list, long, longify):
+               self.file_list = file_list
+               self.long = long
+               self.longify = longify
+               self.done = 0
+
+       def ready (self):
+               if len(self.file_list):
+                       return 1
+               else:
+                       if not self.done:
+                               self.done = 1
+                       return 0
+               return (len(self.file_list) > 0)
+
+       # this should do a pushd/popd
+       def more (self):
+               if not self.file_list:
+                       return ''
+               else:
+                       # do a few at a time
+                       bunch = self.file_list[:50]
+                       if self.long:
+                               bunch = map (self.longify, bunch)
+                       self.file_list = self.file_list[50:]
+                       return string.joinfields (bunch, '\r\n') + '\r\n'
+
diff --git a/demo/medusa/ftp_server.py b/demo/medusa/ftp_server.py
new file mode 100644 (file)
index 0000000..78363cf
--- /dev/null
@@ -0,0 +1,1127 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1996-2000 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+# An extensible, configurable, asynchronous FTP server.
+# 
+# All socket I/O is non-blocking, however file I/O is currently
+# blocking.  Eventually file I/O may be made non-blocking, too, if it
+# seems necessary.  Currently the only CPU-intensive operation is
+# getting and formatting a directory listing.  [this could be moved
+# into another process/directory server, or another thread?]
+#
+# Only a subset of RFC 959 is implemented, but much of that RFC is
+# vestigial anyway.  I've attempted to include the most commonly-used
+# commands, using the feature set of wu-ftpd as a guide.
+
+import asyncore
+import asynchat
+
+import os
+import regsub
+import socket
+import stat
+import string
+import sys
+import time
+
+# TODO: implement a directory listing cache.  On very-high-load
+# servers this could save a lot of disk abuse, and possibly the
+# work of computing emulated unix ls output.
+
+# Potential security problem with the FTP protocol?  I don't think
+# there's any verification of the origin of a data connection.  Not
+# really a problem for the server (since it doesn't send the port
+# command, except when in PASV mode) But I think a data connection
+# could be spoofed by a program with access to a sniffer - it could
+# watch for a PORT command to go over a command channel, and then
+# connect to that port before the server does.
+
+# Unix user id's:
+# In order to support assuming the id of a particular user,
+# it seems there are two options:
+# 1) fork, and seteuid in the child
+# 2) carefully control the effective uid around filesystem accessing
+#    methods, using try/finally. [this seems to work]
+
+VERSION = '1.1'
+
+from counter import counter
+import producers
+import status_handler
+import logger
+import string
+
+class ftp_channel (asynchat.async_chat):
+
+       # defaults for a reliable __repr__
+       addr = ('unknown','0')
+
+       # unset this in a derived class in order
+       # to enable the commands in 'self.write_commands'
+       read_only = 1
+       write_commands = ['appe','dele','mkd','rmd','rnfr','rnto','stor','stou']
+
+       restart_position = 0
+
+       # comply with (possibly troublesome) RFC959 requirements
+       # This is necessary to correctly run an active data connection
+       # through a firewall that triggers on the source port (expected
+       # to be 'L-1', or 20 in the normal case).
+       bind_local_minus_one = 0
+
+       def __init__ (self, server, conn, addr):
+               self.server = server
+               self.current_mode = 'a'
+               self.addr = addr
+               asynchat.async_chat.__init__ (self, conn)
+               self.set_terminator ('\r\n')
+
+               # client data port.  Defaults to 'the same as the control connection'.
+               self.client_addr = (addr[0], 21)
+
+               self.client_dc = None
+               self.in_buffer = ''
+               self.closing = 0
+               self.passive_acceptor = None
+               self.passive_connection = None
+               self.filesystem = None
+               self.authorized = 0
+               # send the greeting
+               self.respond (
+                       '220 %s FTP server (Medusa Async V%s [experimental]) ready.' % (
+                               self.server.hostname,
+                               VERSION
+                               )
+                       )
+
+#      def __del__ (self):
+#              print 'ftp_channel.__del__()'
+
+       # --------------------------------------------------
+       # async-library methods
+       # --------------------------------------------------
+
+       def handle_expt (self):
+               # this is handled below.  not sure what I could
+               # do here to make that code less kludgish.
+               pass
+
+       def collect_incoming_data (self, data):
+               self.in_buffer = self.in_buffer + data
+               if len(self.in_buffer) > 4096:
+                       # silently truncate really long lines
+                       # (possible denial-of-service attack)
+                       self.in_buffer = ''
+
+       def found_terminator (self):
+
+               line = self.in_buffer
+
+               if not len(line):
+                       return
+
+               sp = string.find (line, ' ')
+               if sp != -1:
+                       line = [line[:sp], line[sp+1:]]
+               else:
+                       line = [line]
+
+               command = string.lower (line[0])
+               # watch especially for 'urgent' abort commands.
+               if string.find (command, 'abor') != -1:
+                       # strip off telnet sync chars and the like...
+                       while command and command[0] not in string.letters:
+                               command = command[1:]
+               fun_name = 'cmd_%s' % command
+               if command != 'pass':
+                       self.log ('<== %s' % repr(self.in_buffer)[1:-1])
+               else:
+                       self.log ('<== %s' % line[0]+' <password>')
+               self.in_buffer = ''
+               if not hasattr (self, fun_name):
+                       self.command_not_understood (line[0])
+                       return
+               fun = getattr (self, fun_name)
+               if (not self.authorized) and (command not in ('user', 'pass', 'help', 'quit')):
+                       self.respond ('530 Please log in with USER and PASS')
+               elif (not self.check_command_authorization (command)):
+                       self.command_not_authorized (command)
+               else:
+                       try:
+                               result = apply (fun, (line,))
+                       except:
+                               self.server.total_exceptions.increment()
+                               (file, fun, line), t,v, tbinfo = asyncore.compact_traceback()
+                               if self.client_dc:
+                                       try:
+                                               self.client_dc.close()
+                                       except:
+                                               pass
+                               self.respond (
+                                       '451 Server Error: %s, %s: file: %s line: %s' % (
+                                               t,v,file,line,
+                                               )
+                                       )
+
+       closed = 0
+       def close (self):
+               if not self.closed:
+                       self.closed = 1
+                       if self.passive_acceptor:
+                               self.passive_acceptor.close()
+                       if self.client_dc:
+                               self.client_dc.close()
+                       self.server.closed_sessions.increment()
+                       asynchat.async_chat.close (self)
+
+       # --------------------------------------------------
+       # filesystem interface functions.
+       # override these to provide access control or perform
+       # other functions.
+       # --------------------------------------------------
+
+       def cwd (self, line):
+               return self.filesystem.cwd (line[1])
+
+       def cdup (self, line):
+               return self.filesystem.cdup()
+
+       def open (self, path, mode):
+               return self.filesystem.open (path, mode)
+
+       # returns a producer
+       def listdir (self, path, long=0):
+               return self.filesystem.listdir (path, long)
+
+       def get_dir_list (self, line, long=0):
+               # we need to scan the command line for arguments to '/bin/ls'...
+               args = line[1:]
+               path_args = []
+               for arg in args:
+                       if arg[0] != '-':
+                               path_args.append (arg)
+                       else:
+                               # ignore arguments
+                               pass
+               if len(path_args) < 1:
+                       dir = '.'
+               else:
+                       dir = path_args[0]
+               return self.listdir (dir, long)
+
+       # --------------------------------------------------
+       # authorization methods
+       # --------------------------------------------------
+
+       def check_command_authorization (self, command):
+               if command in self.write_commands and self.read_only:
+                       return 0
+               else:
+                       return 1
+
+       # --------------------------------------------------
+       # utility methods
+       # --------------------------------------------------
+
+       def log (self, message):
+               self.server.logger.log (
+                       self.addr[0],
+                       '%d %s' % (
+                               self.addr[1], message
+                               )
+                       )
+
+       def respond (self, resp):
+               self.log ('==> %s' % resp)
+               self.push (resp + '\r\n')
+
+       def command_not_understood (self, command):
+               self.respond ("500 '%s': command not understood." % command)
+
+       def command_not_authorized (self, command):
+               self.respond (
+                       "530 You are not authorized to perform the '%s' command" % (
+                               command
+                               )
+                       )
+
+       def make_xmit_channel (self):
+               # In PASV mode, the connection may or may _not_ have been made
+               # yet.  [although in most cases it is... FTP Explorer being
+               # the only exception I've yet seen].  This gets somewhat confusing
+               # because things may happen in any order...
+               pa = self.passive_acceptor
+               if pa:
+                       if pa.ready:
+                               # a connection has already been made.
+                               conn, addr = self.passive_acceptor.ready
+                               cdc = xmit_channel (self, addr)
+                               cdc.set_socket (conn)
+                               cdc.connected = 1
+                               self.passive_acceptor.close()
+                               self.passive_acceptor = None                            
+                       else:
+                               # we're still waiting for a connect to the PASV port.
+                               cdc = xmit_channel (self)
+               else:
+                       # not in PASV mode.
+                       ip, port = self.client_addr
+                       cdc = xmit_channel (self, self.client_addr)
+                       cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+                       if self.bind_local_minus_one:
+                               cdc.bind (('', self.server.port - 1))
+                       try:
+                               cdc.connect ((ip, port))
+                       except socket.error, why:
+                               self.respond ("425 Can't build data connection")
+               self.client_dc = cdc
+
+       # pretty much the same as xmit, but only right on the verge of
+       # being worth a merge.
+       def make_recv_channel (self, fd):
+               pa = self.passive_acceptor
+               if pa:
+                       if pa.ready:
+                               # a connection has already been made.
+                               conn, addr = pa.ready
+                               cdc = recv_channel (self, addr, fd)
+                               cdc.set_socket (conn)
+                               cdc.connected = 1
+                               self.passive_acceptor.close()
+                               self.passive_acceptor = None                            
+                       else:
+                               # we're still waiting for a connect to the PASV port.
+                               cdc = recv_channel (self, None, fd)
+               else:
+                       # not in PASV mode.
+                       ip, port = self.client_addr
+                       cdc = recv_channel (self, self.client_addr, fd)
+                       cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+                       try:
+                               cdc.connect ((ip, port))
+                       except socket.error, why:
+                               self.respond ("425 Can't build data connection")
+               self.client_dc = cdc
+
+       type_map = {
+               'a':'ASCII',
+               'i':'Binary',
+               'e':'EBCDIC',
+               'l':'Binary'
+               }
+
+       type_mode_map = {
+               'a':'t',
+               'i':'b',
+               'e':'b',
+               'l':'b'
+               }
+
+       # --------------------------------------------------
+       # command methods
+       # --------------------------------------------------
+
+       def cmd_type (self, line):
+               'specify data transfer type'
+               # ascii, ebcdic, image, local <byte size>
+               t = string.lower (line[1])
+               # no support for EBCDIC
+               # if t not in ['a','e','i','l']:
+               if t not in ['a','i','l']:
+                       self.command_not_understood (string.join (line))
+               elif t == 'l' and (len(line) > 2 and line[2] != '8'):
+                       self.respond ('504 Byte size must be 8')
+               else:
+                       self.current_mode = t
+                       self.respond ('200 Type set to %s.' % self.type_map[t])
+
+
+       def cmd_quit (self, line):
+               'terminate session'
+               self.respond ('221 Goodbye.')
+               self.close_when_done()
+
+       def cmd_port (self, line):
+               'specify data connection port'
+               info = string.split (line[1], ',')
+               ip = string.join (info[:4], '.')
+               port = string.atoi(info[4])*256 + string.atoi(info[5])
+               # how many data connections at a time?
+               # I'm assuming one for now...
+               # TODO: we should (optionally) verify that the
+               # ip number belongs to the client.  [wu-ftpd does this?]
+               self.client_addr = (ip, port)
+               self.respond ('200 PORT command successful.')
+
+       def new_passive_acceptor (self):
+               # ensure that only one of these exists at a time.
+               if self.passive_acceptor is not None:
+                       self.passive_acceptor.close()
+                       self.passive_acceptor = None
+               self.passive_acceptor = passive_acceptor (self)
+               return self.passive_acceptor
+
+       def cmd_pasv (self, line):
+               'prepare for server-to-server transfer'
+               pc = self.new_passive_acceptor()
+               port = pc.addr[1]
+               ip_addr = pc.control_channel.getsockname()[0]
+               self.respond (
+                       '227 Entering Passive Mode (%s,%d,%d)' % (
+                               string.join (string.split (ip_addr, '.'), ','),
+                               port/256,
+                               port%256
+                               )
+                       )
+               self.client_dc = None
+
+       def cmd_nlst (self, line):
+               'give name list of files in directory'
+               # ncftp adds the -FC argument for the user-visible 'nlist'
+               # command.  We could try to emulate ls flags, but not just yet.
+               if '-FC' in line:
+                       line.remove ('-FC')
+               try:
+                       dir_list_producer = self.get_dir_list (line, 0)
+               except os.error, why:
+                       self.respond ('550 Could not list directory: %s' % repr(why))
+                       return
+               self.respond (
+                       '150 Opening %s mode data connection for file list' % (
+                               self.type_map[self.current_mode]
+                               )
+                       )
+               self.make_xmit_channel()
+               self.client_dc.push_with_producer (dir_list_producer)
+               self.client_dc.close_when_done()
+
+       def cmd_list (self, line):
+               'give list files in a directory'
+               try:
+                       dir_list_producer = self.get_dir_list (line, 1)
+               except os.error, why:
+                       self.respond ('550 Could not list directory: %s' % repr(why))
+                       return
+               self.respond (
+                       '150 Opening %s mode data connection for file list' % (
+                               self.type_map[self.current_mode]
+                               )
+                       )
+               self.make_xmit_channel()
+               self.client_dc.push_with_producer (dir_list_producer)
+               self.client_dc.close_when_done()
+
+       def cmd_cwd (self, line):
+               'change working directory'
+               if self.cwd (line):
+                       self.respond ('250 CWD command successful.')
+               else:
+                       self.respond ('550 No such directory.')                 
+
+       def cmd_cdup (self, line):
+               'change to parent of current working directory'
+               if self.cdup(line):
+                       self.respond ('250 CDUP command successful.')
+               else:
+                       self.respond ('550 No such directory.')
+               
+       def cmd_pwd (self, line):
+               'print the current working directory'
+               self.respond (
+                       '257 "%s" is the current directory.' % (
+                               self.filesystem.current_directory()
+                               )
+                       )
+
+       # modification time
+       # example output:
+       # 213 19960301204320
+       def cmd_mdtm (self, line):
+               'show last modification time of file'
+               filename = line[1]
+               if not self.filesystem.isfile (filename):
+                       self.respond ('550 "%s" is not a file' % filename)
+               else:
+                       mtime = time.gmtime(self.filesystem.stat(filename)[stat.ST_MTIME])
+                       self.respond (
+                               '213 %4d%02d%02d%02d%02d%02d' % (
+                                       mtime[0],
+                                       mtime[1],
+                                       mtime[2],
+                                       mtime[3],
+                                       mtime[4],
+                                       mtime[5]
+                                       )
+                               )
+
+       def cmd_noop (self, line):
+               'do nothing'
+               self.respond ('200 NOOP command successful.')
+
+       def cmd_size (self, line):
+               'return size of file'
+               filename = line[1]
+               if not self.filesystem.isfile (filename):
+                       self.respond ('550 "%s" is not a file' % filename)
+               else:
+                       self.respond (
+                               '213 %d' % (self.filesystem.stat(filename)[stat.ST_SIZE])
+                               )
+
+       def cmd_retr (self, line):
+               'retrieve a file'
+               if len(line) < 2:
+                       self.command_not_understood (string.join (line))
+               else:
+                       file = line[1]
+                       if not self.filesystem.isfile (file):
+                               self.log_info ('checking %s' % file)
+                               self.respond ('550 No such file')
+                       else:
+                               try:
+                                       # FIXME: for some reason, 'rt' isn't working on win95
+                                       mode = 'r'+self.type_mode_map[self.current_mode]
+                                       fd = self.open (file, mode)
+                               except IOError, why:
+                                       self.respond ('553 could not open file for reading: %s' % (repr(why)))
+                                       return
+                               self.respond (
+                                       "150 Opening %s mode data connection for file '%s'" % (
+                                               self.type_map[self.current_mode],
+                                               file
+                                               )
+                                       )
+                               self.make_xmit_channel()
+
+                               if self.restart_position:
+                                       # try to position the file as requested, but
+                                       # give up silently on failure (the 'file object'
+                                       # may not support seek())
+                                       try:
+                                               fd.seek (self.restart_position)
+                                       except:
+                                               pass
+                                       self.restart_position = 0
+
+                               self.client_dc.push_with_producer (
+                                       file_producer (self, self.client_dc, fd)
+                                       )
+                               self.client_dc.close_when_done()
+
+       def cmd_stor (self, line, mode='wb'):
+               'store a file'
+               if len (line) < 2:
+                       self.command_not_understood (string.join (line))
+               else:
+                       if self.restart_position:
+                               restart_position = 0
+                               self.respond ('553 restart on STOR not yet supported')
+                               return
+                       file = line[1]
+                       # todo: handle that type flag
+                       try:
+                               fd = self.open (file, mode)
+                       except IOError, why:
+                               self.respond ('553 could not open file for writing: %s' % (repr(why)))
+                               return
+                       self.respond (
+                               '150 Opening %s connection for %s' % (
+                                       self.type_map[self.current_mode],
+                                       file
+                                       )
+                               )
+                       self.make_recv_channel (fd)
+
+       def cmd_abor (self, line):
+               'abort operation'
+               if self.client_dc:
+                       self.client_dc.close()
+               self.respond ('226 ABOR command successful.')
+
+       def cmd_appe (self, line):
+               'append to a file'
+               return self.cmd_stor (line, 'ab')
+
+       def cmd_dele (self, line):
+               if len (line) != 2:
+                       self.command_not_understood (string.join (line))
+               else:
+                       file = line[1]
+                       if self.filesystem.isfile (file):
+                               try:
+                                       self.filesystem.unlink (file)
+                                       self.respond ('250 DELE command successful.')
+                               except:
+                                       self.respond ('550 error deleting file.')
+                       else:
+                               self.respond ('550 %s: No such file.' % file)
+
+       def cmd_mkd (self, line):
+               if len (line) != 2:
+                       self.command.not_understood (string.join (line))
+               else:
+                       path = line[1]
+                       try:
+                               self.filesystem.mkdir (path)
+                               self.respond ('257 MKD command successful.')
+                       except:
+                               self.respond ('550 error creating directory.')
+
+       def cmd_rmd (self, line):
+               if len (line) != 2:
+                       self.command.not_understood (string.join (line))
+               else:
+                       path = line[1]
+                       try:
+                               self.filesystem.rmdir (path)
+                               self.respond ('250 RMD command successful.')
+                       except:
+                               self.respond ('550 error removing directory.')
+
+       def cmd_user (self, line):
+               'specify user name'
+               if len(line) > 1:
+                       self.user = line[1]
+                       self.respond ('331 Password required.')
+               else:
+                       self.command_not_understood (string.join (line))
+
+       def cmd_pass (self, line):
+               'specify password'
+               if len(line) < 2:
+                       pw = ''
+               else:
+                       pw = line[1]
+               result, message, fs = self.server.authorizer.authorize (self, self.user, pw)
+               if result:
+                       self.respond ('230 %s' % message)
+                       self.filesystem = fs
+                       self.authorized = 1
+                       self.log_info('Successful login: Filesystem=%s' % repr(fs))
+               else:
+                       self.respond ('530 %s' % message)
+
+       def cmd_rest (self, line):
+               'restart incomplete transfer'
+               try:
+                       pos = string.atoi (line[1])
+               except ValueError:
+                       self.command_not_understood (string.join (line))
+               self.restart_position = pos
+               self.respond (
+                       '350 Restarting at %d. Send STORE or RETRIEVE to initiate transfer.' % pos
+                       )
+
+       def cmd_stru (self, line):
+               'obsolete - set file transfer structure'
+               if line[1] in 'fF':
+                       # f == 'file'
+                       self.respond ('200 STRU F Ok')
+               else:
+                       self.respond ('504 Unimplemented STRU type')
+
+       def cmd_mode (self, line):
+               'obsolete - set file transfer mode'
+               if line[1] in 'sS':
+                       # f == 'file'
+                       self.respond ('200 MODE S Ok')
+               else:
+                       self.respond ('502 Unimplemented MODE type')
+
+# The stat command has two personalities.  Normally it returns status
+# information about the current connection.  But if given an argument,
+# it is equivalent to the LIST command, with the data sent over the
+# control connection.  Strange.  But wuftpd, ftpd, and nt's ftp server
+# all support it.
+#
+##     def cmd_stat (self, line):
+##             'return status of server'
+##             pass
+
+       def cmd_syst (self, line):
+               'show operating system type of server system'
+               # Replying to this command is of questionable utility, because
+               # this server does not behave in a predictable way w.r.t. the
+               # output of the LIST command.  We emulate Unix ls output, but
+               # on win32 the pathname can contain drive information at the front
+               # Currently, the combination of ensuring that os.sep == '/'
+               # and removing the leading slash when necessary seems to work.
+               # [cd'ing to another drive also works]
+               #
+               # This is how wuftpd responds, and is probably
+               # the most expected.  The main purpose of this reply is so that
+               # the client knows to expect Unix ls-style LIST output.
+               self.respond ('215 UNIX Type: L8')
+               # one disadvantage to this is that some client programs
+               # assume they can pass args to /bin/ls.
+               # a few typical responses:
+               # 215 UNIX Type: L8 (wuftpd)
+               # 215 Windows_NT version 3.51
+               # 215 VMS MultiNet V3.3
+               # 500 'SYST': command not understood. (SVR4)
+
+       def cmd_help (self, line):
+               'give help information'
+               # find all the methods that match 'cmd_xxxx',
+               # use their docstrings for the help response.
+               attrs = dir(self.__class__)
+               help_lines = []
+               for attr in attrs:
+                       if attr[:4] == 'cmd_':
+                               x = getattr (self, attr)
+                               if type(x) == type(self.cmd_help):
+                                       if x.__doc__:
+                                               help_lines.append ('\t%s\t%s' % (attr[4:], x.__doc__))
+               if help_lines:
+                       self.push ('214-The following commands are recognized\r\n')
+                       self.push_with_producer (producers.lines_producer (help_lines))
+                       self.push ('214\r\n')
+               else:
+                       self.push ('214-\r\n\tHelp Unavailable\r\n214\r\n')
+
+class ftp_server (asyncore.dispatcher):
+       # override this to spawn a different FTP channel class.
+       ftp_channel_class = ftp_channel
+
+       SERVER_IDENT = 'FTP Server (V%s)' % VERSION
+
+       def __init__ (
+               self,
+               authorizer,
+               hostname        =None,
+               ip                      ='',
+               port            =21,
+               resolver        =None,
+               logger_object=logger.file_logger (sys.stdout)
+               ):
+               self.ip = ip
+               self.port = port
+               self.authorizer = authorizer
+
+               if hostname is None:
+                       self.hostname = socket.gethostname()
+               else:
+                       self.hostname = hostname
+
+               # statistics
+               self.total_sessions = counter()
+               self.closed_sessions = counter()
+               self.total_files_out = counter()
+               self.total_files_in = counter()
+               self.total_bytes_out = counter()
+               self.total_bytes_in = counter()
+               self.total_exceptions = counter()
+               #
+               asyncore.dispatcher.__init__ (self)
+               self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+               self.set_reuse_addr()
+               self.bind ((self.ip, self.port))
+               self.listen (5)
+
+               if not logger_object:
+                       logger_object = sys.stdout
+
+               if resolver:
+                       self.logger = logger.resolving_logger (resolver, logger_object)
+               else:
+                       self.logger = logger.unresolving_logger (logger_object)
+
+               self.log_info('FTP server started at %s\n\tAuthorizer:%s\n\tHostname: %s\n\tPort: %d' % (
+                       time.ctime(time.time()),
+                       repr (self.authorizer),
+                       self.hostname,
+                       self.port)
+                       )
+
+       def writable (self):
+               return 0
+
+       def handle_read (self):
+               pass
+
+       def handle_connect (self):
+               pass
+
+       def handle_accept (self):
+               conn, addr = self.accept()
+               self.total_sessions.increment()
+               self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+               self.ftp_channel_class (self, conn, addr)
+
+       # return a producer describing the state of the server
+       def status (self):
+
+               def nice_bytes (n):
+                       return string.join (status_handler.english_bytes (n))
+
+               return producers.lines_producer (
+                       ['<h2>%s</h2>'                          % self.SERVER_IDENT,
+                        '<br>Listening on <b>Host:</b> %s' % self.hostname,
+                        '<b>Port:</b> %d'                      % self.port,
+                        '<br>Sessions',
+                        '<b>Total:</b> %s'                     % self.total_sessions,
+                        '<b>Current:</b> %d'           % (self.total_sessions.as_long() - self.closed_sessions.as_long()),
+                        '<br>Files',
+                        '<b>Sent:</b> %s'                      % self.total_files_out,
+                        '<b>Received:</b> %s'          % self.total_files_in,
+                        '<br>Bytes',
+                        '<b>Sent:</b> %s'                      % nice_bytes (self.total_bytes_out.as_long()),
+                        '<b>Received:</b> %s'          % nice_bytes (self.total_bytes_in.as_long()),
+                        '<br>Exceptions: %s'           % self.total_exceptions,
+                        ]
+                       )
+
+# ======================================================================
+#                                               Data Channel Classes
+# ======================================================================
+
+# This socket accepts a data connection, used when the server has been
+# placed in passive mode.  Although the RFC implies that we ought to
+# be able to use the same acceptor over and over again, this presents
+# a problem: how do we shut it off, so that we are accepting
+# connections only when we expect them?  [we can't]
+#
+# wuftpd, and probably all the other servers, solve this by allowing
+# only one connection to hit this acceptor.  They then close it.  Any
+# subsequent data-connection command will then try for the default
+# port on the client side [which is of course never there].  So the
+# 'always-send-PORT/PASV' behavior seems required.
+#
+# Another note: wuftpd will also be listening on the channel as soon
+# as the PASV command is sent.  It does not wait for a data command
+# first.
+
+# --- we need to queue up a particular behavior:
+#  1) xmit : queue up producer[s]
+#  2) recv : the file object
+#
+# It would be nice if we could make both channels the same.  Hmmm..
+#
+
+class passive_acceptor (asyncore.dispatcher):
+       ready = None
+
+       def __init__ (self, control_channel):
+               # connect_fun (conn, addr)
+               asyncore.dispatcher.__init__ (self)
+               self.control_channel = control_channel
+               self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+               # bind to an address on the interface that the
+               # control connection is coming from.
+               self.bind ((
+                       self.control_channel.getsockname()[0],
+                       0
+                       ))
+               self.addr = self.getsockname()
+               self.listen (1)
+
+#      def __del__ (self):
+#              print 'passive_acceptor.__del__()'
+
+       def log (self, *ignore):
+               pass
+
+       def handle_accept (self):
+               conn, addr = self.accept()
+               dc = self.control_channel.client_dc
+               if dc is not None:
+                       dc.set_socket (conn)
+                       dc.addr = addr
+                       dc.connected = 1
+                       self.control_channel.passive_acceptor = None
+               else:
+                       self.ready = conn, addr
+               self.close()
+
+
+class xmit_channel (asynchat.async_chat):
+
+       # for an ethernet, you want this to be fairly large, in fact, it
+       # _must_ be large for performance comparable to an ftpd.  [64k] we
+       # ought to investigate automatically-sized buffers...
+
+       ac_out_buffer_size = 16384
+       bytes_out = 0
+
+       def __init__ (self, channel, client_addr=None):
+               self.channel = channel
+               self.client_addr = client_addr
+               asynchat.async_chat.__init__ (self)
+               
+#      def __del__ (self):
+#              print 'xmit_channel.__del__()'
+
+       def log (*args):
+               pass
+
+       def readable (self):
+               return not self.connected
+
+       def writable (self):
+               return 1
+
+       def send (self, data):
+               result = asynchat.async_chat.send (self, data)
+               self.bytes_out = self.bytes_out + result
+               return result
+
+       def handle_error (self):
+               # usually this is to catch an unexpected disconnect.
+               self.log_info ('unexpected disconnect on data xmit channel', 'error')
+               try:
+                       self.close()
+               except:
+                       pass
+
+       # TODO: there's a better way to do this.  we need to be able to
+       # put 'events' in the producer fifo.  to do this cleanly we need
+       # to reposition the 'producer' fifo as an 'event' fifo.
+
+       def close (self):
+               c = self.channel
+               s = c.server
+               c.client_dc = None
+               s.total_files_out.increment()
+               s.total_bytes_out.increment (self.bytes_out)
+               if not len(self.producer_fifo):
+                       c.respond ('226 Transfer complete')
+               elif not c.closed:
+                       c.respond ('426 Connection closed; transfer aborted')
+               del c
+               del s
+               del self.channel
+               asynchat.async_chat.close (self)
+
+class recv_channel (asyncore.dispatcher):
+       def __init__ (self, channel, client_addr, fd):
+               self.channel = channel
+               self.client_addr = client_addr
+               self.fd = fd
+               asyncore.dispatcher.__init__ (self)
+               self.bytes_in = counter()
+
+       def log (self, *ignore):
+               pass
+
+       def handle_connect (self):
+               pass
+
+       def writable (self):
+               return 0
+
+       def recv (*args):
+               result = apply (asyncore.dispatcher.recv, args)
+               self = args[0]
+               self.bytes_in.increment(len(result))
+               return result
+
+       buffer_size = 8192
+
+       def handle_read (self):
+               block = self.recv (self.buffer_size)
+               if block:
+                       try:
+                               self.fd.write (block)
+                       except IOError:
+                               self.log_info ('got exception writing block...', 'error')
+
+       def handle_close (self):
+               s = self.channel.server
+               s.total_files_in.increment()
+               s.total_bytes_in.increment(self.bytes_in.as_long())
+               self.fd.close()
+               self.channel.respond ('226 Transfer complete.')
+               self.close()
+
+import filesys
+
+# not much of a doorman! 8^)
+class dummy_authorizer:
+       def __init__ (self, root='/'):
+               self.root = root
+       def authorize (self, channel, username, password):
+               channel.persona = -1, -1
+               channel.read_only = 1
+               return 1, 'Ok.', filesys.os_filesystem (self.root)
+
+class anon_authorizer:
+       def __init__ (self, root='/'):
+               self.root = root
+               
+       def authorize (self, channel, username, password):
+               if username in ('ftp', 'anonymous'):
+                       channel.persona = -1, -1
+                       channel.read_only = 1
+                       return 1, 'Ok.', filesys.os_filesystem (self.root)
+               else:
+                       return 0, 'Password invalid.', None
+
+# ===========================================================================
+# Unix-specific improvements
+# ===========================================================================
+
+if os.name == 'posix':
+
+       class unix_authorizer:
+               # return a trio of (success, reply_string, filesystem)
+               def authorize (self, channel, username, password):
+                       import crypt
+                       import pwd
+                       try:
+                               info = pwd.getpwnam (username)
+                       except KeyError:
+                               return 0, 'No such user.', None
+                       mangled = info[1]
+                       if crypt.crypt (password, mangled[:2]) == mangled:
+                               channel.read_only = 0
+                               fs = filesys.schizophrenic_unix_filesystem (
+                                       '/',
+                                       info[5],
+                                       persona = (info[2], info[3])
+                                       )
+                               return 1, 'Login successful.', fs
+                       else:
+                               return 0, 'Password invalid.', None
+
+               def __repr__ (self):
+                       return '<standard unix authorizer>'
+
+       # simple anonymous ftp support
+       class unix_authorizer_with_anonymous (unix_authorizer):
+               def __init__ (self, root=None, real_users=0):
+                       self.root = root
+                       self.real_users = real_users
+
+               def authorize (self, channel, username, password):
+                       if string.lower(username) in ['anonymous', 'ftp']:
+                               import pwd
+                               try:
+                                       # ok, here we run into lots of confusion.
+                                       # on some os', anon runs under user 'nobody',
+                                       # on others as 'ftp'.  ownership is also critical.
+                                       # need to investigate.
+                                       # linux: new linuxen seem to have nobody's UID=-1,
+                                       #    which is an illegal value.  Use ftp.
+                                       ftp_user_info = pwd.getpwnam ('ftp')
+                                       if string.lower(os.uname()[0]) == 'linux':
+                                               nobody_user_info = pwd.getpwnam ('ftp')
+                                       else:
+                                               nobody_user_info = pwd.getpwnam ('nobody')
+                                       channel.read_only = 1
+                                       if self.root is None:
+                                               self.root = ftp_user_info[5]
+                                       fs = filesys.unix_filesystem (self.root, '/')
+                                       return 1, 'Anonymous Login Successful', fs
+                               except KeyError:
+                                       return 0, 'Anonymous account not set up', None
+                       elif self.real_users:
+                               return unix_authorizer.authorize (
+                                       self,
+                                       channel,
+                                       username,
+                                       password
+                                       )
+                       else:
+                               return 0, 'User logins not allowed', None
+
+class file_producer:
+       block_size = 16384
+       def __init__ (self, server, dc, fd):
+               self.fd = fd
+               self.done = 0
+               
+       def more (self):
+               if self.done:
+                       return ''
+               else:
+                       block = self.fd.read (self.block_size)
+                       if not block:
+                               self.fd.close()
+                               self.done = 1
+                       return block
+
+# usage: ftp_server /PATH/TO/FTP/ROOT PORT
+# for example:
+# $ ftp_server /home/users/ftp 8021
+
+if os.name == 'posix':
+       def test (port='8021'):
+               import sys
+               fs = ftp_server (
+                       unix_authorizer(),
+                       port=string.atoi (port)
+                       )
+               try:
+                       asyncore.loop()
+               except KeyboardInterrupt:
+                       self.log_info('FTP server shutting down. (received SIGINT)', 'warning')
+                       # close everything down on SIGINT.
+                       # of course this should be a cleaner shutdown.
+                       asyncore.close_all()
+
+       if __name__ == '__main__':
+               test (sys.argv[1])
+# not unix
+else:
+       def test ():
+               fs = ftp_server (dummy_authorizer())
+       if __name__ == '__main__':
+               test ()
+
+# this is the command list from the wuftpd man page
+# '*' means we've implemented it.
+# '!' requires write access
+#
+command_documentation = {
+       'abor': 'abort previous command',                                                       #*
+       'acct': 'specify account (ignored)',
+       'allo': 'allocate storage (vacuously)',
+       'appe': 'append to a file',                                                                     #*!
+       'cdup': 'change to parent of current working directory',        #*
+       'cwd':  'change working directory',                                                     #*
+       'dele': 'delete a file',                                                                        #!
+       'help': 'give help information',                                                        #*
+       'list': 'give list files in a directory',                                       #*
+       'mkd':  'make a directory',                                                                     #!
+       'mdtm': 'show last modification time of file',                          #*
+       'mode': 'specify data transfer mode',
+       'nlst': 'give name list of files in directory',                         #*
+       'noop': 'do nothing',                                                                           #*
+       'pass': 'specify password',                                                                     #*
+       'pasv': 'prepare for server-to-server transfer',                        #*
+       'port': 'specify data connection port',                                         #*
+       'pwd':  'print the current working directory',                          #*
+       'quit': 'terminate session',                                                            #*
+       'rest': 'restart incomplete transfer',                                          #*
+       'retr': 'retrieve a file',                                                                      #*
+       'rmd':  'remove a directory',                                                           #!
+       'rnfr': 'specify rename-from file name',                                        #!
+       'rnto': 'specify rename-to file name',                                          #!
+       'site': 'non-standard commands (see next section)',
+       'size': 'return size of file',                                                          #*
+       'stat': 'return status of server',                                                      #*
+       'stor': 'store a file',                                                                         #*!
+       'stou': 'store a file with a unique name',                                      #!
+       'stru': 'specify data transfer structure',
+       'syst': 'show operating system type of server system',          #*
+       'type': 'specify data transfer type',                                           #*
+       'user': 'specify user name',                                                            #*
+       'xcup': 'change to parent of current working directory (deprecated)',
+       'xcwd': 'change working directory (deprecated)',
+       'xmkd': 'make a directory (deprecated)',                                        #!
+       'xpwd': 'print the current working directory (deprecated)',
+       'xrmd': 'remove a directory (deprecated)',                                      #!
+}
+
+
+# debugging aid (linux)
+def get_vm_size ():
+       return string.atoi (string.split(open ('/proc/self/stat').readline())[22])
+
+def print_vm():
+       print 'vm: %8dk' % (get_vm_size()/1024)
diff --git a/demo/medusa/ftps_server.py b/demo/medusa/ftps_server.py
new file mode 100644 (file)
index 0000000..8fee339
--- /dev/null
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server. 
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+    
+    """FTP/TLS server channel for Medusa."""
+
+    def __init__(self, server, ssl_ctx, conn, addr):
+        """Initialise the channel."""
+        self.ssl_ctx = ssl_ctx
+        self.server = server
+        self.current_mode = 'a'
+        self.addr = addr
+        asynchat.async_chat.__init__(self, conn)
+        self.set_terminator('\r\n')
+        self.client_addr = (addr[0], 21)
+        self.client_dc = None
+        self.in_buffer = ''
+        self.closing = 0
+        self.passive_acceptor = None
+        self.passive_connection = None
+        self.filesystem = None
+        self.authorized = 0
+        self._ssl_accepting = 0
+        self._ssl_accepted = 0
+        self._pbsz = None
+        self._prot = None
+        resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+        self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+    def writable(self):
+        return self._ssl_accepting or self._ssl_accepted
+
+    def handle_read(self):
+        """Handle a read event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_read(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def handle_write(self):
+        """Handle a write event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_write(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+    def found_terminator(self):
+        """Dispatch the FTP command."""
+        line = self.in_buffer
+        if not len(line):
+            return
+
+        sp = string.find(line, ' ')
+        if sp != -1:
+            line = [line[:sp], line[sp+1:]]
+        else:
+            line = [line]
+
+        command = string.lower(line[0])
+        if string.find(command, 'stor') != -1:
+            while command and command[0] not in string.letters:
+                command = command[1:]
+        
+        func_name = 'cmd_%s' % command
+        if command != 'pass':
+            self.log('<== %s' % repr(self.in_buffer)[1:-1])
+        else:
+            self.log('<== %s' % line[0]+' <password>')
+
+        self.in_buffer = ''
+        if not hasattr(self, func_name):
+            self.command_not_understood(line[0])
+            return 
+    
+        func = getattr(self, func_name)
+        if not self.check_command_authorization(command):
+            self.command_not_authorized(command)
+        else:
+            try:
+                result = apply(func, (line,))
+            except:
+                self.server.total_exceptions.increment()
+                (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+                if self.client_dc:
+                    try:
+                        self.client_dc_close()
+                    except:
+                        pass
+                resp = '451 Server error: %s, %s: file %s line: %s'
+                self.respond(resp % (t, v, file, line))
+
+    def make_xmit_channel(self):
+        """Create a connection for sending data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+                else:
+                    cdc = ftp_server.xmit_channel(self, addr)
+                    cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+                else:
+                    cdc = ftp_server.xmit_channel(self)
+        else:
+            if self._prot:
+                cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+            else:
+                cdc = ftp_server.xmit_channel(self, self.client_addr)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            if self.bind_local_minus_one:
+                cdc.bind(('', self.server.port - 1))
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def make_recv_channel(self, fd):
+        """Create a connection for receiving data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, addr, fd)
+                cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, None, fd)
+        else:
+            if self._prot:
+                cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+            else:
+                cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def cmd_auth(self, line):
+        """Prepare for TLS operation."""
+        # XXX Handle variations.
+        if line[1] != 'TLS':
+            self.command_not_understood (string.join(line))
+        else:
+            self.respond('234 AUTH TLS successful')
+            self._ssl_accepting = 1
+            self.socket = SSL.Connection(self.ssl_ctx, self.socket)    
+            self.socket.setup_addr(self.addr)
+            self.socket.setup_ssl()
+            self.socket.set_accept_state()
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+
+    def cmd_pbsz(self, line):
+        """Negotiate size of buffer for secure data transfer. For
+        FTP/TLS the only valid value for the parameter is '0'; any 
+        other value is accepted but ignored."""
+        if not (self._ssl_accepting or self._ssl_accepted):
+            return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+        self._pbsz = 1
+        self.respond('200 PBSZ=0 successful.')
+
+    def cmd_prot(self, line):
+        """Negotiate the security level of the data connection.""" 
+        if self._pbsz is None:
+            return self.respond('503 PBSZ must be issued prior to PROT')
+        if line[1] == 'C':
+            self.respond('200 Protection set to Clear')
+            self._pbsz = None
+            self._prot = None
+        elif line[1] == 'P': 
+            self.respond('200 Protection set to Private')
+            self._prot = 1
+        elif line[1] in ('S', 'E'):
+            self.respond('536 PROT %s unsupported' % line[1])
+        else:
+            self.respond('504 PROT %s unsupported' % line[1])
+            
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+    """FTP/TLS server for Medusa."""
+
+    SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+    ftp_channel_class = ftp_tls_channel
+
+    def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+        """Initialise the server."""
+        self.ssl_ctx = ssl_ctx
+        self.ip = ip
+        self.port = port
+        self.authorizer = authz
+
+        if host is None:
+            self.hostname = socket.gethostname()
+        else:
+            self.hostname = host
+
+        self.total_sessions = counter()
+        self.closed_sessions = counter()
+        self.total_files_out = counter()
+        self.total_files_in = counter()
+        self.total_bytes_out = counter()
+        self.total_bytes_in = counter()
+        self.total_exceptions = counter()
+
+        asyncore.dispatcher.__init__(self)
+        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.set_reuse_addr()
+        self.bind((self.ip, self.port))
+        self.listen(5)
+
+        if log_obj is None:
+            log_obj = sys.stdout
+
+        if resolver:
+            self.logger = logger.resolving_logger(resolver, log_obj)
+        else:
+            self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+        l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+        self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+    def handle_accept(self):
+        """Accept a socket and dispatch a channel to handle it."""
+        conn, addr = self.accept()
+        self.total_sessions.increment()
+        self.log_info('Connection from %s:%d' % addr)
+        self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+    """TLS protocol negotiation mixin for FTP/TLS."""
+
+    def tls_init(self, sock, ssl_ctx, client_addr):
+        """Perform TLS protocol negotiation."""
+        self.ssl_ctx = ssl_ctx
+        self.client_addr = client_addr
+        self._ssl_handshaking = 1
+        self._ssl_handshake_ok = 0
+        if sock:
+            self.socket = SSL.Connection(self.ssl_ctx, sock)
+            self.socket.setup_addr(self.client_addr)
+            self.socket.setup_ssl()
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+            self.add_channel()
+        # else the client hasn't connected yet; when that happens,
+        # handle_connect() will be triggered.
+
+    def tls_neg_ok(self):
+        """Return status of TLS protocol negotiation."""
+        if self._ssl_handshaking:
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+        return self._ssl_handshake_ok
+
+    def handle_connect(self):
+        """Handle a data connection that occurs after this instance came 
+        into being. When this handler is triggered, self.socket has been 
+        created and refers to the underlying connected socket."""
+        self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+        self.socket.setup_addr(self.client_addr)
+        self.socket.setup_ssl()
+        self._ssl_handshake_ok = self.socket.accept_ssl()
+        if self._ssl_handshake_ok:
+            self._ssl_handshaking = 0
+        self.add_channel()
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+    """TLS driver for a send-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+        """Initialise the driver."""
+        ftp_server.xmit_channel.__init__(self, channel, client_addr)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def readable(self):
+        """This channel is readable iff TLS negotiation is in progress.
+        (Which implies a connected channel, of course.)"""
+        if not self.connected:
+            return 0
+        else:
+            return self._ssl_handshaking
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress
+        or the application has data to send."""
+        if self._ssl_handshaking:
+            return 1
+        else:
+            return ftp_server.xmit_channel.writable(self)
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_write(self) 
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+    
+    """TLS driver for a receive-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+        """Initialise the driver."""
+        ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress."""
+        return self._ssl_handshaking
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_write(self) 
+
+
diff --git a/demo/medusa/http_date.py b/demo/medusa/http_date.py
new file mode 100644 (file)
index 0000000..c40f70d
--- /dev/null
@@ -0,0 +1,126 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import re
+import string
+import time
+
+def concat (*args):
+       return ''.join (args)   
+
+def join (seq, field=' '):
+       return field.join (seq)
+
+def group (s):
+       return '(' + s + ')'
+
+short_days = ['sun','mon','tue','wed','thu','fri','sat']
+long_days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
+
+short_day_reg = group (join (short_days, '|'))
+long_day_reg = group (join (long_days, '|'))
+
+daymap = {}
+for i in range(7):
+       daymap[short_days[i]] = i
+       daymap[long_days[i]] = i
+
+hms_reg = join (3 * [group('[0-9][0-9]')], ':')
+
+months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
+
+monmap = {}
+for i in range(12):
+       monmap[months[i]] = i+1
+
+months_reg = group (join (months, '|'))
+
+# From draft-ietf-http-v11-spec-07.txt/3.3.1
+#       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+#       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+#       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+
+# rfc822 format
+rfc822_date = join (
+       [concat (short_day_reg,','),    # day
+        group('[0-9][0-9]?'),                  # date
+        months_reg,                                    # month
+        group('[0-9]+'),                               # year
+        hms_reg,                                               # hour minute second
+        'gmt'
+        ],
+       ' '
+       )
+
+rfc822_reg = re.compile (rfc822_date)
+
+def unpack_rfc822 (m):
+       g = m.group
+       a = string.atoi
+       return (
+               a(g(4)),                # year
+               monmap[g(3)],   # month
+               a(g(2)),                # day
+               a(g(5)),                # hour
+               a(g(6)),                # minute
+               a(g(7)),                # second
+               0,
+               0,
+               0
+               )
+
+# rfc850 format
+rfc850_date = join (
+       [concat (long_day_reg,','),
+        join (
+                [group ('[0-9][0-9]?'),
+                 months_reg,
+                 group ('[0-9]+')
+                 ],
+                '-'
+                ),
+        hms_reg,
+        'gmt'
+        ],
+       ' '
+       )
+
+rfc850_reg = re.compile (rfc850_date)
+# they actually unpack the same way
+def unpack_rfc850 (m):
+       g = m.group
+       a = string.atoi
+       return (
+               a(g(4)),                # year
+               monmap[g(3)],   # month
+               a(g(2)),                # day
+               a(g(5)),                # hour
+               a(g(6)),                # minute
+               a(g(7)),                # second
+               0,
+               0,
+               0
+               )
+
+# parsdate.parsedate   - ~700/sec.
+# parse_http_date      - ~1333/sec.
+
+def build_http_date (when):
+       return time.strftime ('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(when))
+
+def parse_http_date (d):
+       d = string.lower (d)
+       tz = time.timezone
+       m = rfc850_reg.match (d)
+       if m and m.end() == len(d):
+               retval = int (time.mktime (unpack_rfc850(m)) - tz)
+       else:
+               m = rfc822_reg.match (d)
+               if m and m.end() == len(d):
+                       retval = int (time.mktime (unpack_rfc822(m)) - tz)
+               else:
+                       return 0
+       # Thanks to Craig Silverstein <csilvers@google.com> for pointing
+       # out the DST discrepancy
+       if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
+               retval = retval + (tz - time.altzone)
+       return retval
diff --git a/demo/medusa/http_server.py b/demo/medusa/http_server.py
new file mode 100644 (file)
index 0000000..2f261ab
--- /dev/null
@@ -0,0 +1,784 @@
+#! /usr/local/bin/python
+# -*- Mode: Python; tab-width: 4 -*-
+#
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1996-2000 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+# python modules
+import os
+import re
+import socket
+import stat
+import string
+import sys
+import time
+
+# async modules
+import asyncore
+import asynchat
+
+# medusa modules
+import http_date
+import producers
+import status_handler
+import logger
+
+VERSION_STRING = '1.1'
+
+from counter import counter
+from urllib import unquote
+
+# ===========================================================================
+#                                                      Request Object
+# ===========================================================================
+
+class http_request:
+
+       # default reply code
+       reply_code = 200
+
+       request_counter = counter()
+
+       # Whether to automatically use chunked encoding when
+       # 
+       #   HTTP version is 1.1
+       #   Content-Length is not set
+       #   Chunked encoding is not already in effect
+       #
+       # If your clients are having trouble, you might want to disable this.
+       use_chunked = 1
+       
+       # by default, this request object ignores user data.
+       collector = None
+
+       def __init__ (self, *args):
+               # unpack information about the request
+               (self.channel, self.request,
+                self.command, self.uri, self.version,
+                self.header) = args
+
+               self.outgoing = fifo()
+               self.reply_headers = {
+                       'Server'        : 'Medusa/%s' % VERSION_STRING,
+                       'Date'          : http_date.build_http_date (time.time())
+                       }
+               self.request_number = http_request.request_counter.increment()
+               self._split_uri = None
+               self._header_cache = {}
+
+       # --------------------------------------------------
+       # reply header management
+       # --------------------------------------------------
+       def __setitem__ (self, key, value):
+               self.reply_headers[key] = value
+
+       def __getitem__ (self, key):
+               return self.reply_headers[key]
+
+       def has_key (self, key):
+               return self.reply_headers.has_key (key)
+
+       def build_reply_header (self):
+               return string.join (
+                       [self.response(self.reply_code)] + map (
+                               lambda x: '%s: %s' % x,
+                               self.reply_headers.items()
+                               ),
+                       '\r\n'
+                       ) + '\r\n\r\n'
+
+       # --------------------------------------------------
+       # split a uri
+       # --------------------------------------------------
+
+       # <path>;<params>?<query>#<fragment>
+       path_regex = re.compile (
+       #      path      params    query   fragment
+               r'([^;?#]*)(;[^?#]*)?(\?[^#]*)?(#.*)?'
+               )
+
+       def split_uri (self):
+               if self._split_uri is None:
+                       m = self.path_regex.match (self.uri)
+                       if m.end() != len(self.uri):
+                               raise ValueError, "Broken URI"
+                       else:
+                               self._split_uri = m.groups()
+               return self._split_uri
+
+       def get_header_with_regex (self, head_reg, group):
+               for line in self.header:
+                       m = head_reg.match (line)
+                       if m.end() == len(line):
+                               return head_reg.group (group)
+               return ''
+
+       def get_header (self, header):
+               header = string.lower (header)
+               hc = self._header_cache
+               if not hc.has_key (header):
+                       h = header + ': '
+                       hl = len(h)
+                       for line in self.header:
+                               if string.lower (line[:hl]) == h:
+                                       r = line[hl:]
+                                       hc[header] = r
+                                       return r
+                       hc[header] = None
+                       return None
+               else:
+                       return hc[header]
+
+       # --------------------------------------------------
+       # user data
+       # --------------------------------------------------
+
+       def collect_incoming_data (self, data):
+               if self.collector:
+                       self.collector.collect_incoming_data (data)
+               else:
+                       self.log_info(
+                               'Dropping %d bytes of incoming request data' % len(data),
+                               'warning'
+                               )
+
+       def found_terminator (self):
+               if self.collector:
+                       self.collector.found_terminator()
+               else:
+                       self.log_info (
+                               'Unexpected end-of-record for incoming request',
+                               'warning'
+                               )
+
+       def push (self, thing):
+               if type(thing) == type(''):
+                       self.outgoing.push (producers.simple_producer (thing))
+               else:
+                       self.outgoing.push (thing)
+
+       def response (self, code=200):
+               message = self.responses[code]
+               self.reply_code = code
+               return 'HTTP/%s %d %s' % (self.version, code, message)
+
+       def error (self, code):
+               self.reply_code = code
+               message = self.responses[code]
+               s = self.DEFAULT_ERROR_MESSAGE % {
+                       'code': code,
+                       'message': message,
+                       }
+               self['Content-Length'] = len(s)
+               self['Content-Type'] = 'text/html'
+               # make an error reply
+               self.push (s)
+               self.done()
+
+       # can also be used for empty replies
+       reply_now = error
+
+       def done (self):
+               "finalize this transaction - send output to the http channel"
+
+               # ----------------------------------------
+               # persistent connection management
+               # ----------------------------------------
+
+               #  --- BUCKLE UP! ----
+
+               connection = string.lower (get_header (CONNECTION, self.header))
+
+               close_it = 0
+               wrap_in_chunking = 0
+
+               if self.version == '1.0':
+                       if connection == 'keep-alive':
+                               if not self.has_key ('Content-Length'):
+                                       close_it = 1
+                               else:
+                                       self['Connection'] = 'Keep-Alive'
+                       else:
+                               close_it = 1
+               elif self.version == '1.1':
+                       if connection == 'close':
+                               close_it = 1
+                       elif not self.has_key ('Content-Length'):
+                               if self.has_key ('Transfer-Encoding'):
+                                       if not self['Transfer-Encoding'] == 'chunked':
+                                               close_it = 1
+                               elif self.use_chunked:
+                                       self['Transfer-Encoding'] = 'chunked'
+                                       wrap_in_chunking = 1
+                               else:
+                                       close_it = 1
+               elif self.version is None:
+                       # Although we don't *really* support http/0.9 (because we'd have to
+                       # use \r\n as a terminator, and it would just yuck up a lot of stuff)
+                       # it's very common for developers to not want to type a version number
+                       # when using telnet to debug a server.
+                       close_it = 1
+                                       
+               outgoing_header = producers.simple_producer (self.build_reply_header())
+
+               if close_it:
+                       self['Connection'] = 'close'
+
+               if wrap_in_chunking:
+                       outgoing_producer = producers.chunked_producer (
+                               producers.composite_producer (self.outgoing)
+                               )
+                       # prepend the header
+                       outgoing_producer = producers.composite_producer (
+                               fifo([outgoing_header, outgoing_producer])
+                               )
+               else:
+                       # prepend the header
+                       self.outgoing.push_front (outgoing_header)
+                       outgoing_producer = producers.composite_producer (self.outgoing)
+
+               # apply a few final transformations to the output
+               self.channel.push_with_producer (
+                       # globbing gives us large packets
+                       producers.globbing_producer (
+                               # hooking lets us log the number of bytes sent
+                               producers.hooked_producer (
+                                       outgoing_producer,
+                                       self.log
+                                       )
+                               )
+                       )
+
+               self.channel.current_request = None
+
+               if close_it:
+                       self.channel.close_when_done()
+
+       def log_date_string (self, when):
+               return time.strftime (
+                       '%d/%b/%Y:%H:%M:%S ',
+                       time.gmtime(when)
+                       ) + tz_for_log
+
+       def log (self, bytes):
+               self.channel.server.logger.log (
+                       self.channel.addr[0],
+                       '%d - - [%s] "%s" %d %d\n' % (
+                               self.channel.addr[1],
+                               self.log_date_string (time.time()),
+                               self.request,
+                               self.reply_code,
+                               bytes
+                               )
+                       )
+
+       responses = {
+               100: "Continue",
+               101: "Switching Protocols",
+               200: "OK",
+               201: "Created",
+               202: "Accepted",
+               203: "Non-Authoritative Information",
+               204: "No Content",
+               205: "Reset Content",
+               206: "Partial Content",
+               300: "Multiple Choices",
+               301: "Moved Permanently",
+               302: "Moved Temporarily",
+               303: "See Other",
+               304: "Not Modified",
+               305: "Use Proxy",
+               400: "Bad Request",
+               401: "Unauthorized",
+               402: "Payment Required",
+               403: "Forbidden",
+               404: "Not Found",
+               405: "Method Not Allowed",
+               406: "Not Acceptable",
+               407: "Proxy Authentication Required",
+               408: "Request Time-out",
+               409: "Conflict",
+               410: "Gone",
+               411: "Length Required",
+               412: "Precondition Failed",
+               413: "Request Entity Too Large",
+               414: "Request-URI Too Large",
+               415: "Unsupported Media Type",
+               500: "Internal Server Error",
+               501: "Not Implemented",
+               502: "Bad Gateway",
+               503: "Service Unavailable",
+               504: "Gateway Time-out",
+               505: "HTTP Version not supported"
+               }
+
+       # Default error message
+       DEFAULT_ERROR_MESSAGE = string.join (
+               ['<head>',
+                '<title>Error response</title>',
+                '</head>',
+                '<body>',
+                '<h1>Error response</h1>',
+                '<p>Error code %(code)d.',
+                '<p>Message: %(message)s.',
+                '</body>',
+                ''
+                ],
+               '\r\n'
+               )
+
+
+# ===========================================================================
+#                                               HTTP Channel Object
+# ===========================================================================
+
+class http_channel (asynchat.async_chat):
+
+       # use a larger default output buffer
+       ac_out_buffer_size = 1<<16
+
+       current_request = None
+       channel_counter = counter()
+
+       def __init__ (self, server, conn, addr):
+               self.channel_number = http_channel.channel_counter.increment()
+               self.request_counter = counter()
+               asynchat.async_chat.__init__ (self, conn)
+               self.server = server
+               self.addr = addr
+               self.set_terminator ('\r\n\r\n')
+               self.in_buffer = ''
+               self.creation_time = int (time.time())
+               self.check_maintenance()
+
+       def __repr__ (self):
+               ar = asynchat.async_chat.__repr__(self)[1:-1]
+               return '<%s channel#: %s requests:%s>' % (
+                       ar,
+                       self.channel_number,
+                       self.request_counter
+                       )
+
+       # Channel Counter, Maintenance Interval...
+       maintenance_interval = 500
+
+       def check_maintenance (self):
+               if not self.channel_number % self.maintenance_interval:
+                       self.maintenance()
+
+       def maintenance (self):
+               self.kill_zombies()
+
+       # 30-minute zombie timeout.  status_handler also knows how to kill zombies.
+       zombie_timeout = 30 * 60
+
+       def kill_zombies (self):
+               now = int (time.time())
+               for channel in asyncore.socket_map.values():
+                       if channel.__class__ == self.__class__:
+                               if (now - channel.creation_time) > channel.zombie_timeout:
+                                       channel.close()
+
+       # --------------------------------------------------
+       # send/recv overrides, good place for instrumentation.
+       # --------------------------------------------------
+       
+       # this information needs to get into the request object,
+       # so that it may log correctly.
+       def send (self, data):
+               result = asynchat.async_chat.send (self, data)
+               self.server.bytes_out.increment (len(data))
+               return result
+
+       def recv (self, buffer_size):
+               try:
+                       result = asynchat.async_chat.recv (self, buffer_size)
+                       self.server.bytes_in.increment (len(result))
+                       return result
+               except MemoryError:
+                       # --- Save a Trip to Your Service Provider ---
+                       # It's possible for a process to eat up all the memory of
+                       # the machine, and put it in an extremely wedged state,
+                       # where medusa keeps running and can't be shut down.  This
+                       # is where MemoryError tends to get thrown, though of
+                       # course it could get thrown elsewhere.
+                       sys.exit ("Out of Memory!")
+
+       def handle_error (self):
+               t, v = sys.exc_info()[:2]
+               if t is SystemExit:
+                       raise t, v
+               else:
+                       asynchat.async_chat.handle_error (self)
+
+       def log (self, *args):
+               pass
+
+       # --------------------------------------------------
+       # async_chat methods
+       # --------------------------------------------------
+
+       def collect_incoming_data (self, data):
+               if self.current_request:
+                       # we are receiving data (probably POST data) for a request
+                       self.current_request.collect_incoming_data (data)
+               else:
+                       # we are receiving header (request) data
+                       self.in_buffer = self.in_buffer + data
+
+       def found_terminator (self):
+               if self.current_request:
+                       self.current_request.found_terminator()
+               else:
+                       header = self.in_buffer
+                       self.in_buffer = ''
+                       lines = string.split (header, '\r\n')
+
+                       # --------------------------------------------------
+                       # crack the request header
+                       # --------------------------------------------------
+
+                       while lines and not lines[0]:
+                               # as per the suggestion of http-1.1 section 4.1, (and
+                               # Eric Parker <eparker@zyvex.com>), ignore a leading
+                               # blank lines (buggy browsers tack it onto the end of
+                               # POST requests)
+                               lines = lines[1:]
+
+                       if not lines:
+                               self.close_when_done()
+                               return
+
+                       request = lines[0]
+
+                       # unquote path if necessary (thanks to Skip Montaro for pointing
+                       # out that we must unquote in piecemeal fashion).
+                       if '%' in request:
+                               request = unquote (request)
+
+                       command, uri, version = crack_request (request)
+                       header = join_headers (lines[1:])
+
+                       r = http_request (self, request, command, uri, version, header)
+                       self.request_counter.increment()
+                       self.server.total_requests.increment()
+
+                       if command is None:
+                               self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
+                               r.error (400)
+                               return
+
+                       # --------------------------------------------------
+                       # handler selection and dispatch
+                       # --------------------------------------------------
+                       for h in self.server.handlers:
+                               if h.match (r):
+                                       try:
+                                               self.current_request = r
+                                               # This isn't used anywhere.
+                                               # r.handler = h # CYCLE
+                                               h.handle_request (r)
+                                       except:
+                                               self.server.exceptions.increment()
+                                               (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
+                                               self.log_info(
+                                                               'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
+                                                               'error')
+                                               try:
+                                                       r.error (500)
+                                               except:
+                                                       pass
+                                       return
+
+                       # no handlers, so complain
+                       r.error (404)
+
+       def writable (self):
+               # this is just the normal async_chat 'writable', here for comparison
+               return self.ac_out_buffer or len(self.producer_fifo)
+
+       def writable_for_proxy (self):
+               # this version of writable supports the idea of a 'stalled' producer
+               # [i.e., it's not ready to produce any output yet] This is needed by
+               # the proxy, which will be waiting for the magic combination of
+               # 1) hostname resolved
+               # 2) connection made
+               # 3) data available.
+               if self.ac_out_buffer:
+                       return 1
+               elif len(self.producer_fifo):
+                       p = self.producer_fifo.first()
+                       if hasattr (p, 'stalled'):
+                               return not p.stalled()
+                       else:
+                               return 1
+
+# ===========================================================================
+#                                               HTTP Server Object
+# ===========================================================================
+
+class http_server (asyncore.dispatcher):
+
+       SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING
+
+       channel_class = http_channel
+
+       def __init__ (self, ip, port, resolver=None, logger_object=None):
+               self.ip = ip
+               self.port = port
+               asyncore.dispatcher.__init__ (self)
+               self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+               self.handlers = []
+
+               if not logger_object:
+                       logger_object = logger.file_logger (sys.stdout)
+
+               self.set_reuse_addr()
+               self.bind ((ip, port))
+
+               # lower this to 5 if your OS complains
+               self.listen (1024)
+
+               host, port = self.socket.getsockname()
+               if not ip:
+                       self.log_info('Computing default hostname', 'warning')
+                       ip = socket.gethostbyname (socket.gethostname())
+               try:
+                       self.server_name = socket.gethostbyaddr (ip)[0]
+               except socket.error:
+                       self.log_info('Cannot do reverse lookup', 'warning')
+                       self.server_name = ip       # use the IP address as the "hostname"
+
+               self.server_port = port
+               self.total_clients = counter()
+               self.total_requests = counter()
+               self.exceptions = counter()
+               self.bytes_out = counter()
+               self.bytes_in  = counter()
+
+               if not logger_object:
+                       logger_object = logger.file_logger (sys.stdout)
+
+               if resolver:
+                       self.logger = logger.resolving_logger (resolver, logger_object)
+               else:
+                       self.logger = logger.unresolving_logger (logger_object)
+
+               self.log_info (
+                       'Medusa (V%s) started at %s'
+                       '\n\tHostname: %s'
+                       '\n\tPort:%d'
+                       '\n' % (
+                               VERSION_STRING,
+                               time.ctime(time.time()),
+                               self.server_name,
+                               port,
+                               )
+                       )
+
+       def writable (self):
+               return 0
+
+       def handle_read (self):
+               pass
+
+       def readable (self):
+               return self.accepting
+
+       def handle_connect (self):
+               pass
+
+       def handle_accept (self):
+               self.total_clients.increment()
+               try:
+                       conn, addr = self.accept()
+               except socket.error:
+                       # linux: on rare occasions we get a bogus socket back from
+                       # accept.  socketmodule.c:makesockaddr complains that the
+                       # address family is unknown.  We don't want the whole server
+                       # to shut down because of this.
+                       self.log_info ('warning: server accept() threw an exception', 'warning')
+                       return
+               except TypeError:
+                       # unpack non-sequence.  this can happen when a read event
+                       # fires on a listening socket, but when we call accept()
+                       # we get EWOULDBLOCK, so dispatcher.accept() returns None.
+                       # Seen on FreeBSD3.
+                       self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
+                       return
+
+               self.channel_class (self, conn, addr)
+
+       def install_handler (self, handler, back=0):
+               if back:
+                       self.handlers.append (handler)
+               else:
+                       self.handlers.insert (0, handler)
+
+       def remove_handler (self, handler):
+               self.handlers.remove (handler)
+
+       def status (self):
+               def nice_bytes (n):
+                       return string.join (status_handler.english_bytes (n))
+
+               handler_stats = filter (None, map (maybe_status, self.handlers))
+               
+               if self.total_clients:
+                       ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
+               else:
+                       ratio = 0.0
+               
+               return producers.composite_producer (
+                       fifo ([producers.lines_producer (
+                               ['<h2>%s</h2>'                                                  % self.SERVER_IDENT,
+                               '<br>Listening on: <b>Host:</b> %s'             % self.server_name,
+                               '<b>Port:</b> %d'                                               % self.port,
+                                '<p><ul>'
+                                '<li>Total <b>Clients:</b> %s'                 % self.total_clients,
+                                '<b>Requests:</b> %s'                                  % self.total_requests,
+                                '<b>Requests/Client:</b> %.1f'                 % (ratio),
+                                '<li>Total <b>Bytes In:</b> %s'        % (nice_bytes (self.bytes_in.as_long())),
+                                '<b>Bytes Out:</b> %s'                         % (nice_bytes (self.bytes_out.as_long())),
+                                '<li>Total <b>Exceptions:</b> %s'              % self.exceptions,
+                                '</ul><p>'
+                                '<b>Extension List</b><ul>',
+                                ])] + handler_stats + [producers.simple_producer('</ul>')]
+                                 )
+                       )
+
+def maybe_status (thing):
+       if hasattr (thing, 'status'):
+               return thing.status()
+       else:
+               return None
+
+CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
+
+# merge multi-line headers
+# [486dx2: ~500/sec]
+def join_headers (headers):
+       r = []
+       for i in range(len(headers)):
+               if headers[i][0] in ' \t':      
+                       r[-1] = r[-1] + headers[i][1:]
+               else:
+                       r.append (headers[i])
+       return r
+
+def get_header (head_reg, lines, group=1):
+       for line in lines:
+               m = head_reg.match (line)
+               if m and m.end() == len(line):
+                       return m.group (group)
+       return ''
+
+def get_header_match (head_reg, lines):
+       for line in lines:
+               m = head_reg.match (line)
+               if m and m.end() == len(line):
+                       return m
+       return ''
+
+REQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')
+
+def crack_request (r):
+       m = REQUEST.match (r)
+       if m.end() == len(r):
+               if m.group(3):
+                       version = m.group(5)
+               else:
+                       version = None
+               return string.lower (m.group(1)), m.group(2), version
+       else:
+               return None, None, None
+
+class fifo:
+       def __init__ (self, list=None):
+               if not list:
+                       self.list = []
+               else:
+                       self.list = list
+               
+       def __len__ (self):
+               return len(self.list)
+
+       def first (self):
+               return self.list[0]
+
+       def push_front (self, object):
+               self.list.insert (0, object)
+
+       def push (self, data):
+               self.list.append (data)
+
+       def pop (self):
+               if self.list:
+                       result = self.list[0]
+                       del self.list[0]
+                       return (1, result)
+               else:
+                       return (0, None)
+
+def compute_timezone_for_log ():
+       if time.daylight:
+               tz = time.altzone
+       else:
+               tz = time.timezone
+       if tz > 0:
+               neg = 1
+       else:
+               neg = 0
+               tz = -tz
+       h, rem = divmod (tz, 3600)
+       m, rem = divmod (rem, 60)
+       if neg:
+               return '-%02d%02d' % (h, m)
+       else:
+               return '+%02d%02d' % (h, m)
+
+# if you run this program over a TZ change boundary, this will be invalid.
+tz_for_log = compute_timezone_for_log()
+
+if __name__ == '__main__':
+       import sys
+       if len(sys.argv) < 2:
+               print 'usage: %s <root> <port>' % (sys.argv[0])
+       else:
+               import monitor
+               import filesys
+               import default_handler
+               import status_handler
+               import ftp_server
+               import chat_server
+               import resolver
+               import logger
+               rs = resolver.caching_resolver ('127.0.0.1')
+               lg = logger.file_logger (sys.stdout)
+               ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
+               fs = filesys.os_filesystem (sys.argv[1])
+               dh = default_handler.default_handler (fs)
+               hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
+               hs.install_handler (dh)
+               ftp = ftp_server.ftp_server (
+                       ftp_server.dummy_authorizer(sys.argv[1]),
+                       port=8021,
+                       resolver=rs,
+                       logger_object=lg
+                       )
+               cs = chat_server.chat_server ('', 7777)
+               sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
+               hs.install_handler (sh)
+               if ('-p' in sys.argv):
+                       def profile_loop ():
+                               try:
+                                       asyncore.loop()
+                               except KeyboardInterrupt:
+                                       pass
+                       import profile
+                       profile.run ('profile_loop()', 'profile.out')
+               else:
+                       asyncore.loop()
diff --git a/demo/medusa/https_server.py b/demo/medusa/https_server.py
new file mode 100644 (file)
index 0000000..1492586
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server. 
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class https_channel(http_server.http_channel):
+
+    def __init__(self, server, conn, addr):
+        http_server.http_channel.__init__(self, server, conn, addr)
+
+    def send(self, data):
+        try:
+            result = self.socket._write_nbio(data)
+            if result <= 0:
+                return 0
+            else:
+                self.server.bytes_out.increment(result)
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), why))
+            return 0
+
+    def recv(self, buffer_size):
+        try:
+            result = self.socket._read_nbio(buffer_size)
+            if result is None:
+                return ''
+            elif result == '':
+                self.close()
+                return ''
+            else:
+                self.server.bytes_in.increment(len(result))
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), why))
+            return ''
+
+
+class https_server(http_server.http_server):
+
+    SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+    channel_class=https_channel
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+        sys.stdout.write(self.SERVER_IDENT + '\n\n')
+        sys.stdout.flush()
+        self.ssl_ctx=ssl_ctx
+        
+    def handle_accept(self):
+        # Cribbed from http_server.
+        self.total_clients.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            # linux: on rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            sys.stderr.write ('warning: server accept() threw an exception\n')
+            return
+
+        # Turn the vanilla socket into an SSL connection.
+        try:
+            ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+            ssl_conn._setup_ssl(addr)
+            ssl_conn.accept_ssl()
+            self.channel_class(self, ssl_conn, addr)
+        except SSL.SSLError:
+            pass
+
+    def writeable(self):
+        return 0
+
diff --git a/demo/medusa/index.html b/demo/medusa/index.html
new file mode 100644 (file)
index 0000000..0f7de19
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+<title>M2Crypto HTTPS Server</title>
+<body>
+<h1>M2Crypto HTTPS Server - It works!</h1>
+</body>
+</html>
diff --git a/demo/medusa/logger.py b/demo/medusa/logger.py
new file mode 100644 (file)
index 0000000..48be665
--- /dev/null
@@ -0,0 +1,262 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import asynchat
+import socket
+import string
+import time         # these three are for the rotating logger
+import os           # |
+import stat         # v
+
+#
+# three types of log:
+# 1) file
+#    with optional flushing.  Also, one that rotates the log.
+# 2) socket
+#    dump output directly to a socket connection. [how do we
+#    keep it open?]
+# 3) syslog
+#    log to syslog via tcp.  this is a per-line protocol.
+#
+
+#
+# The 'standard' interface to a logging object is simply
+# log_object.log (message)
+#
+
+# a file-like object that captures output, and
+# makes sure to flush it always...  this could
+# be connected to:
+#  o   stdio file
+#  o   low-level file
+#  o   socket channel
+#  o   syslog output...
+
+class file_logger:
+                       
+       # pass this either a path or a file object.
+       def __init__ (self, file, flush=1, mode='a'):
+               if type(file) == type(''):
+                       if (file == '-'):
+                               import sys
+                               self.file = sys.stdout
+                       else:
+                               self.file = open (file, mode)
+               else:
+                       self.file = file
+               self.do_flush = flush
+
+       def __repr__ (self):
+               return '<file logger: %s>' % self.file
+
+       def write (self, data):
+               self.file.write (data)
+               self.maybe_flush()
+               
+       def writeline (self, line):
+               self.file.writeline (line)
+               self.maybe_flush()
+               
+       def writelines (self, lines):
+               self.file.writelines (lines)
+               self.maybe_flush()
+
+       def maybe_flush (self):
+               if self.do_flush:
+                       self.file.flush()
+
+       def flush (self):
+               self.file.flush()
+
+       def softspace (self, *args):
+               pass
+
+       def log (self, message):
+               if message[-1] not in ('\r', '\n'):
+                       self.write (message + '\n')
+               else:
+                       self.write (message)
+
+# like a file_logger, but it must be attached to a filename.
+# When the log gets too full, or a certain time has passed,
+# it backs up the log and starts a new one.  Note that backing
+# up the log is done via "mv" because anything else (cp, gzip)
+# would take time, during which medusa would do nothing else.
+
+class rotating_file_logger (file_logger):
+                       
+       # If freq is non-None we back up "daily", "weekly", or "monthly".
+       # Else if maxsize is non-None we back up whenever the log gets
+       # to big.  If both are None we never back up.
+       def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
+               self.filename = file
+               self.mode = mode
+               self.file = open (file, mode)
+               self.freq = freq
+               self.maxsize = maxsize
+               self.rotate_when = self.next_backup(self.freq)
+               self.do_flush = flush
+
+       def __repr__ (self):
+               return '<rotating-file logger: %s>' % self.file
+
+       # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
+       def next_backup (self, freq):
+               (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+               if freq == 'daily':
+                       return time.mktime(yr,mo,day+1, 0,0,0, 0,0,-1)
+               elif freq == 'weekly':
+                       return time.mktime(yr,mo,day-wd+7, 0,0,0, 0,0,-1)  # wd(monday)==0
+               elif freq == 'monthly':
+                       return time.mktime(yr,mo+1,1, 0,0,0, 0,0,-1)
+               else:
+                       return None                  # not a date-based backup
+
+       def maybe_flush (self):              # rotate first if necessary
+               self.maybe_rotate()
+               if self.do_flush:                # from file_logger()
+                       self.file.flush()
+
+       def maybe_rotate (self):
+               if self.freq and time.time() > self.rotate_when:
+                       self.rotate()
+                       self.rotate_when = self.next_backup(self.freq)
+               elif self.maxsize:               # rotate when we get too big
+                       try:
+                               if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
+                                       self.rotate()
+                       except os.error:             # file not found, probably
+                               self.rotate()            # will create a new file
+
+       def rotate (self):
+               (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+               try:
+                       self.file.close()
+                       newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
+                       try:
+                               open(newname, "r").close()      # check if file exists
+                               newname = newname + "-%02d%02d%02d" % (hr, min, sec)
+                       except:                             # YEARMODY is unique
+                               pass
+                       os.rename(self.filename, newname)
+                       self.file = open(self.filename, self.mode)
+               except:
+                       pass
+
+# syslog is a line-oriented log protocol - this class would be
+# appropriate for FTP or HTTP logs, but not for dumping stderr to.
+
+# TODO: a simple safety wrapper that will ensure that the line sent
+# to syslog is reasonable.
+
+# TODO: async version of syslog_client: now, log entries use blocking
+# send()
+
+import m_syslog
+syslog_logger = m_syslog.syslog_client
+
+class syslog_logger (m_syslog.syslog_client):
+       def __init__ (self, address, facility='user'):
+               m_syslog.syslog_client.__init__ (self, address)
+               self.facility = m_syslog.facility_names[facility]
+               self.address=address
+
+       def __repr__ (self):
+               return '<syslog logger address=%s>' % (repr(self.address))
+
+       def log (self, message):
+               m_syslog.syslog_client.log (
+                       self,
+                       message,
+                       facility=self.facility,
+                       priority=m_syslog.LOG_INFO
+                       )
+
+# log to a stream socket, asynchronously
+
+class socket_logger (asynchat.async_chat):
+
+       def __init__ (self, address):
+
+               if type(address) == type(''):
+                       self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
+               else:
+                       self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+               self.connect (address)
+               self.address = address
+               
+       def __repr__ (self):
+               return '<socket logger: address=%s>' % (self.address)
+
+       def log (self, message):
+               if message[-2:] != '\r\n':
+                       self.socket.push (message + '\r\n')
+               else:
+                       self.socket.push (message)
+
+# log to multiple places
+class multi_logger:
+       def __init__ (self, loggers):
+               self.loggers = loggers
+
+       def __repr__ (self):
+               return '<multi logger: %s>' % (repr(self.loggers))
+
+       def log (self, message):
+               for logger in self.loggers:
+                       logger.log (message)
+
+class resolving_logger:
+       """Feed (ip, message) combinations into this logger to get a
+       resolved hostname in front of the message.  The message will not
+       be logged until the PTR request finishes (or fails)."""
+
+       def __init__ (self, resolver, logger):
+               self.resolver = resolver
+               self.logger = logger
+
+       class logger_thunk:
+               def __init__ (self, message, logger):
+                       self.message = message
+                       self.logger = logger
+
+               def __call__ (self, host, ttl, answer):
+                       if not answer:
+                               answer = host
+                       self.logger.log ('%s:%s' % (answer, self.message))
+
+       def log (self, ip, message):
+               self.resolver.resolve_ptr (
+                       ip,
+                       self.logger_thunk (
+                               message,
+                               self.logger
+                               )
+                       )
+
+class unresolving_logger:
+       "Just in case you don't want to resolve"
+       def __init__ (self, logger):
+               self.logger = logger
+
+       def log (self, ip, message):
+               self.logger.log ('%s:%s' % (ip, message))
+
+
+def strip_eol (line):
+       while line and line[-1] in '\r\n':
+               line = line[:-1]
+       return line
+
+class tail_logger:
+       "Keep track of the last <size> log messages"
+       def __init__ (self, logger, size=500):
+               self.size = size
+               self.logger = logger
+               self.messages = []
+
+       def log (self, message):
+               self.messages.append (strip_eol (message))
+               if len (self.messages) > self.size:
+                       del self.messages[0]
+               self.logger.log (message)
diff --git a/demo/medusa/m_syslog.py b/demo/medusa/m_syslog.py
new file mode 100644 (file)
index 0000000..bb376db
--- /dev/null
@@ -0,0 +1,177 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# ======================================================================
+# Copyright 1997 by Sam Rushing
+# 
+#                         All Rights Reserved
+# 
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+# 
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""socket interface to unix syslog.
+On Unix, there are usually two ways of getting to syslog: via a
+local unix-domain socket, or via the TCP service.
+
+Usually "/dev/log" is the unix domain socket.  This may be different
+for other systems.
+
+>>> my_client = syslog_client ('/dev/log')
+
+Otherwise, just use the UDP version, port 514.
+
+>>> my_client = syslog_client (('my_log_host', 514))
+
+On win32, you will have to use the UDP version.  Note that
+you can use this to log to other hosts (and indeed, multiple
+hosts).
+
+This module is not a drop-in replacement for the python
+<syslog> extension module - the interface is different.
+
+Usage:
+
+>>> c = syslog_client()
+>>> c = syslog_client ('/strange/non_standard_log_location')
+>>> c = syslog_client (('other_host.com', 514))
+>>> c.log ('testing', facility='local0', priority='debug')
+
+"""
+
+# TODO: support named-pipe syslog.
+# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]
+
+# from <linux/sys/syslog.h>:
+# ===========================================================================
+# priorities/facilities are encoded into a single 32-bit quantity, where the
+# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
+# (0-big number).  Both the priorities and the facilities map roughly
+# one-to-one to strings in the syslogd(8) source code.  This mapping is
+# included in this file.
+#
+# priorities (these are ordered)
+
+LOG_EMERG              = 0             #  system is unusable 
+LOG_ALERT              = 1             #  action must be taken immediately 
+LOG_CRIT               = 2             #  critical conditions 
+LOG_ERR                        = 3             #  error conditions 
+LOG_WARNING            = 4             #  warning conditions 
+LOG_NOTICE             = 5             #  normal but significant condition 
+LOG_INFO               = 6             #  informational 
+LOG_DEBUG              = 7             #  debug-level messages 
+
+#  facility codes 
+LOG_KERN               = 0             #  kernel messages 
+LOG_USER               = 1             #  random user-level messages 
+LOG_MAIL               = 2             #  mail system 
+LOG_DAEMON             = 3             #  system daemons 
+LOG_AUTH               = 4             #  security/authorization messages 
+LOG_SYSLOG             = 5             #  messages generated internally by syslogd 
+LOG_LPR                        = 6             #  line printer subsystem 
+LOG_NEWS               = 7             #  network news subsystem 
+LOG_UUCP               = 8             #  UUCP subsystem 
+LOG_CRON               = 9             #  clock daemon 
+LOG_AUTHPRIV   = 10    #  security/authorization messages (private) 
+
+#  other codes through 15 reserved for system use 
+LOG_LOCAL0             = 16            #  reserved for local use 
+LOG_LOCAL1             = 17            #  reserved for local use 
+LOG_LOCAL2             = 18            #  reserved for local use 
+LOG_LOCAL3             = 19            #  reserved for local use 
+LOG_LOCAL4             = 20            #  reserved for local use 
+LOG_LOCAL5             = 21            #  reserved for local use 
+LOG_LOCAL6             = 22            #  reserved for local use 
+LOG_LOCAL7             = 23            #  reserved for local use 
+
+priority_names = {
+       "alert":        LOG_ALERT,
+       "crit":         LOG_CRIT,
+       "debug":        LOG_DEBUG,
+       "emerg":        LOG_EMERG,
+       "err":          LOG_ERR,
+       "error":        LOG_ERR,                #  DEPRECATED 
+       "info":         LOG_INFO,
+       "notice":       LOG_NOTICE,
+       "panic":        LOG_EMERG,              #  DEPRECATED 
+       "warn":         LOG_WARNING,            #  DEPRECATED 
+       "warning":      LOG_WARNING,
+       }
+
+facility_names = {
+       "auth":         LOG_AUTH,
+       "authpriv":     LOG_AUTHPRIV,
+       "cron":         LOG_CRON,
+       "daemon":       LOG_DAEMON,
+       "kern":         LOG_KERN,
+       "lpr":          LOG_LPR,
+       "mail":         LOG_MAIL,
+       "news":         LOG_NEWS,
+       "security":     LOG_AUTH,               #  DEPRECATED 
+       "syslog":       LOG_SYSLOG,
+       "user":         LOG_USER,
+       "uucp":         LOG_UUCP,
+       "local0":       LOG_LOCAL0,
+       "local1":       LOG_LOCAL1,
+       "local2":       LOG_LOCAL2,
+       "local3":       LOG_LOCAL3,
+       "local4":       LOG_LOCAL4,
+       "local5":       LOG_LOCAL5,
+       "local6":       LOG_LOCAL6,
+       "local7":       LOG_LOCAL7,
+       }
+
+import socket
+
+class syslog_client:
+       def __init__ (self, address='/dev/log'):
+               self.address = address
+               if type (address) == type(''):
+                       self.socket = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
+                       self.socket.connect (address)
+                       self.unix = 1
+               else:
+                       self.socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
+                       self.unix = 0
+
+       # curious: when talking to the unix-domain '/dev/log' socket, a
+       #   zero-terminator seems to be required.  this string is placed
+       #   into a class variable so that it can be overridden if
+       #   necessary.
+
+       log_format_string = '<%d>%s\000'
+
+       def log (self, message, facility=LOG_USER, priority=LOG_INFO):
+               message = self.log_format_string % (
+                       self.encode_priority (facility, priority),
+                       message
+                       )
+               if self.unix:
+                       self.socket.send (message)
+               else:
+                       self.socket.sendto (message, self.address)
+
+       def encode_priority (self, facility, priority):
+               if type(facility) == type(''):
+                       facility = facility_names[facility]
+               if type(priority) == type(''):
+                       priority = priority_names[priority]                     
+               return (facility<<3) | priority
+
+       def close (self):
+               if self.unix:
+                       self.socket.close()
+
diff --git a/demo/medusa/medusa_gif.py b/demo/medusa/medusa_gif.py
new file mode 100644 (file)
index 0000000..005644a
--- /dev/null
@@ -0,0 +1,8 @@
+# -*- Mode: Python -*-
+
+# the medusa icon as a python source file.
+
+width = 97
+height = 61
+
+data = 'GIF89aa\000=\000\204\000\000\000\000\000\255\255\255\245\245\245ssskkkccc111)))\326\326\326!!!\316\316\316\300\300\300\204\204\000\224\224\224\214\214\214\200\200\200RRR\377\377\377JJJ\367\367\367BBB\347\347\347\000\204\000\020\020\020\265\265\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004\001\000\000\021\000,\000\000\000\000a\000=\000\000\005\376`$\216di\236h\252\256l\353\276p,\317tm\337x\256\357|m\001@\240E\305\000\364\2164\206R)$\005\201\214\007r\012{X\255\312a\004\260\\>\026\3240\353)\224n\001W+X\334\373\231~\344.\303b\216\024\027x<\273\307\255G,rJiWN\014{S}k"?ti\013EdPQ\207G@_%\000\026yy\\\201\202\227\224<\221Fs$pOjWz\241<r@vO\236\231\233k\247M\2544\203F\177\235\236L#\247\256Z\270,\266BxJ[\276\256A]iE\304\305\262\273E\313\201\275i#\\\303\321\'h\203V\\\177\326\276\216\220P~\335\230_\264\013\342\275\344KF\233\360Q\212\352\246\000\367\274s\361\236\334\347T\341;\341\246\2202\177\3142\211`\242o\325@S\202\264\031\252\207\260\323\256\205\311\036\236\270\002\'\013\302\177\274H\010\324X\002\0176\212\037\376\321\360\032\226\207\244\2674(+^\202\346r\205J\0211\375\241Y#\256f\0127\315>\272\002\325\307g\012(\007\205\312#j\317(\012A\200\224.\241\003\346GS\247\033\245\344\264\366\015L\'PXQl]\266\263\243\232\260?\245\316\371\362\225\035\332\243J\273\332Q\263\357-D\241T\327\270\265\013W&\330\010u\371b\322IW0\214\261]\003\033Va\365Z#\207\213a\030k\2647\262\014p\354\024[n\321N\363\346\317\003\037P\000\235C\302\000\3228(\244\363YaA\005\022\255_\237@\260\000A\212\326\256qbp\321\332\266\011\334=T\023\010"!B\005\003A\010\224\020\220 H\002\337#\020 O\276E\357h\221\327\003\\\000b@v\004\351A.h\365\354\342B\002\011\257\025\\ \220\340\301\353\006\000\024\214\200pA\300\353\012\364\241k/\340\033C\202\003\000\310fZ\011\003V\240R\005\007\354\376\026A\000\000\360\'\202\177\024\004\210\003\000\305\215\360\000\000\015\220\240\332\203\027@\'\202\004\025VpA\000%\210x\321\206\032J\341\316\010\262\211H"l\333\341\200\200>"]P\002\212\011\010`\002\0066FP\200\001\'\024p]\004\027(8B\221\306]\000\201w>\002iB\001\007\340\260"v7J1\343(\257\020\251\243\011\242i\263\017\215\337\035\220\200\221\365m4d\015\016D\251\341iN\354\346Ng\253\200I\240\031\35609\245\2057\311I\302\2007t\231"&`\314\310\244\011e\226(\236\010w\212\300\234\011\012HX(\214\253\311@\001\233^\222pg{% \340\035\224&H\000\246\201\362\215`@\001"L\340\004\030\234\022\250\'\015(V:\302\235\030\240q\337\205\224\212h@\177\006\000\250\210\004\007\310\207\337\005\257-P\346\257\367]p\353\203\271\256:\203\236\211F\340\247\010\3329g\244\010\307*=A\000\203\260y\012\304s#\014\007D\207,N\007\304\265\027\021C\233\207%B\366[m\353\006\006\034j\360\306+\357\274a\204\000\000;'
diff --git a/demo/medusa/mime_type_table.py b/demo/medusa/mime_type_table.py
new file mode 100644 (file)
index 0000000..dbc64a4
--- /dev/null
@@ -0,0 +1,113 @@
+# -*- Python -*-
+# Converted by ./convert_mime_type_table.py from:
+# /usr/src2/apache_1.2b6/conf/mime.types
+#
+content_type_map = \
+  {
+        'ai':  'application/postscript',
+       'aif':  'audio/x-aiff',
+      'aifc':  'audio/x-aiff',
+      'aiff':  'audio/x-aiff',
+        'au':  'audio/basic',
+       'avi':  'video/x-msvideo',
+     'bcpio':  'application/x-bcpio',
+       'bin':  'application/octet-stream',
+       'cdf':  'application/x-netcdf',
+     'class':  'application/octet-stream',
+      'cpio':  'application/x-cpio',
+       'cpt':  'application/mac-compactpro',
+       'csh':  'application/x-csh',
+       'dcr':  'application/x-director',
+       'dir':  'application/x-director',
+       'dms':  'application/octet-stream',
+       'doc':  'application/msword',
+       'dvi':  'application/x-dvi',
+       'dxr':  'application/x-director',
+       'eps':  'application/postscript',
+       'etx':  'text/x-setext',
+       'exe':  'application/octet-stream',
+       'gif':  'image/gif',
+      'gtar':  'application/x-gtar',
+        'gz':  'application/x-gzip',
+       'hdf':  'application/x-hdf',
+       'hqx':  'application/mac-binhex40',
+       'htm':  'text/html',
+      'html':  'text/html',
+       'ice':  'x-conference/x-cooltalk',
+       'ief':  'image/ief',
+       'jpe':  'image/jpeg',
+      'jpeg':  'image/jpeg',
+       'jpg':  'image/jpeg',
+       'kar':  'audio/midi',
+     'latex':  'application/x-latex',
+       'lha':  'application/octet-stream',
+       'lzh':  'application/octet-stream',
+       'man':  'application/x-troff-man',
+        'me':  'application/x-troff-me',
+       'mid':  'audio/midi',
+      'midi':  'audio/midi',
+       'mif':  'application/x-mif',
+       'mov':  'video/quicktime',
+     'movie':  'video/x-sgi-movie',
+       'mp2':  'audio/mpeg',
+       'mpe':  'video/mpeg',
+      'mpeg':  'video/mpeg',
+       'mpg':  'video/mpeg',
+      'mpga':  'audio/mpeg',
+       'mp3':  'audio/mpeg',
+        'ms':  'application/x-troff-ms',
+        'nc':  'application/x-netcdf',
+       'oda':  'application/oda',
+       'pbm':  'image/x-portable-bitmap',
+       'pdb':  'chemical/x-pdb',
+       'pdf':  'application/pdf',
+       'pgm':  'image/x-portable-graymap',
+       'png':  'image/png',
+       'pnm':  'image/x-portable-anymap',
+       'ppm':  'image/x-portable-pixmap',
+       'ppt':  'application/powerpoint',
+        'ps':  'application/postscript',
+        'qt':  'video/quicktime',
+        'ra':  'audio/x-realaudio',
+       'ram':  'audio/x-pn-realaudio',
+       'ras':  'image/x-cmu-raster',
+       'rgb':  'image/x-rgb',
+      'roff':  'application/x-troff',
+       'rpm':  'audio/x-pn-realaudio-plugin',
+       'rtf':  'application/rtf',
+       'rtx':  'text/richtext',
+       'sgm':  'text/x-sgml',
+      'sgml':  'text/x-sgml',
+        'sh':  'application/x-sh',
+      'shar':  'application/x-shar',
+       'sit':  'application/x-stuffit',
+       'skd':  'application/x-koan',
+       'skm':  'application/x-koan',
+       'skp':  'application/x-koan',
+       'skt':  'application/x-koan',
+       'snd':  'audio/basic',
+       'src':  'application/x-wais-source',
+   'sv4cpio':  'application/x-sv4cpio',
+    'sv4crc':  'application/x-sv4crc',
+         't':  'application/x-troff',
+       'tar':  'application/x-tar',
+       'tcl':  'application/x-tcl',
+       'tex':  'application/x-tex',
+      'texi':  'application/x-texinfo',
+   'texinfo':  'application/x-texinfo',
+       'tif':  'image/tiff',
+      'tiff':  'image/tiff',
+        'tr':  'application/x-troff',
+       'tsv':  'text/tab-separated-values',
+       'txt':  'text/plain',
+     'ustar':  'application/x-ustar',
+       'vcd':  'application/x-cdlink',
+      'vrml':  'x-world/x-vrml',
+       'wav':  'audio/x-wav',
+       'wrl':  'x-world/x-vrml',
+       'xbm':  'image/x-xbitmap',
+       'xpm':  'image/x-xpixmap',
+       'xwd':  'image/x-xwindowdump',
+       'xyz':  'chemical/x-pdb',
+       'zip':  'application/zip',
+  }
diff --git a/demo/medusa/poison_handler.py b/demo/medusa/poison_handler.py
new file mode 100644 (file)
index 0000000..acc78ab
--- /dev/null
@@ -0,0 +1,69 @@
+
+import string
+import whrandom
+
+RESP_HEAD="""\
+<HTML><BODY BGCOLOR=\"#ffffff\">
+"""
+
+RESP_MIDDLE="""
+<h2>M2Crypto https server demonstration</h2>
+
+This web page is generated by the "poison" http request handler. 
+<br>
+The links just go on and on and on...
+<br><br>
+"""
+
+RESP_TAIL="""
+</BODY></HTML>
+"""
+
+charset='012345678/90ABCDEFGHIJKLM/NOPQRSTUVWXYZabcd/efghijklmnopqrs/tuvwxyz'
+numchar=len(charset)
+
+def makepage(numlinks):
+
+    title='<title>'
+    for u in range(whrandom.randint(3, 15)):
+        pick=whrandom.randint(0, numchar-1)
+        title=title+charset[pick]
+    title=title+'</title>'
+
+    url='\r\n'
+    numlinks=whrandom.randint(2, numlinks)
+    for i in range(numlinks): 
+        url=url+'<a href="/poison/'
+        for u in range(whrandom.randint(3, 15)):
+            pick=whrandom.randint(0, numchar-1)
+            ch=charset[pick]
+            if ch=='/' and url[-1]=='/':
+                ch=charset[pick+1]
+            url=url+ch
+        url=url+'/">'
+        for u in range(whrandom.randint(3, 15)):
+            pick=whrandom.randint(0, numchar-1)
+            url=url+charset[pick]
+        url=url+'</a><br>\r\n'
+
+    url=RESP_HEAD+title+RESP_MIDDLE+url+RESP_TAIL
+    return url
+
+
+class poison_handler:
+    """This is a clone of webpoison - every URL returns a page of URLs, each of which 
+    returns a page of URLs, each of _which_ returns a page of URLs, ad infinitum.
+    The objective is to sucker address-harvesting bots run by spammers."""
+
+    def __init__(self, numlinks=10):
+        self.numlinks = numlinks
+        self.poison_level = 0
+
+    def match(self, request):
+        return  (request.uri[:7] == '/poison')
+
+    def handle_request(self, request):
+        if request.command == 'get':
+            request.push(makepage(self.numlinks))
+        request.done()
+
diff --git a/demo/medusa/producers.py b/demo/medusa/producers.py
new file mode 100644 (file)
index 0000000..2138258
--- /dev/null
@@ -0,0 +1,329 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import string
+
+"""
+A collection of producers.
+Each producer implements a particular feature:  They can be combined
+in various ways to get interesting and useful behaviors.
+
+For example, you can feed dynamically-produced output into the compressing
+producer, then wrap this with the 'chunked' transfer-encoding producer.
+"""
+
+class simple_producer:
+       "producer for a string"
+       def __init__ (self, data, buffer_size=1024):
+               self.data = data
+               self.buffer_size = buffer_size
+
+       def more (self):
+               if len (self.data) > self.buffer_size:
+                       result = self.data[:self.buffer_size]
+                       self.data = self.data[self.buffer_size:]
+                       return result
+               else:
+                       result = self.data
+                       self.data = ''
+                       return result
+
+class scanning_producer:
+       "like simple_producer, but more efficient for large strings"
+       def __init__ (self, data, buffer_size=1024):
+               self.data = data
+               self.buffer_size = buffer_size
+               self.pos = 0
+
+       def more (self):
+               if self.pos < len(self.data):
+                       lp = self.pos
+                       rp = min (
+                               len(self.data),
+                               self.pos + self.buffer_size
+                               )
+                       result = self.data[lp:rp]
+                       self.pos = self.pos + len(result)
+                       return result
+               else:
+                       return ''
+
+class lines_producer:
+       "producer for a list of lines"
+
+       def __init__ (self, lines):
+               self.lines = lines
+
+       def ready (self):
+               return len(self.lines)
+
+       def more (self):
+               if self.lines:
+                       chunk = self.lines[:50]
+                       self.lines = self.lines[50:]
+                       return string.join (chunk, '\r\n') + '\r\n'
+               else:
+                       return ''
+
+class buffer_list_producer:
+       "producer for a list of buffers"
+
+       # i.e., data == string.join (buffers, '')
+       
+       def __init__ (self, buffers):
+
+               self.index = 0
+               self.buffers = buffers
+
+       def more (self):
+               if self.index >= len(self.buffers):
+                       return ''
+               else:
+                       data = self.buffers[self.index]
+                       self.index = self.index + 1
+                       return data
+
+class file_producer:
+       "producer wrapper for file[-like] objects"
+
+       # match http_channel's outgoing buffer size
+       out_buffer_size = 1<<16
+
+       def __init__ (self, file):
+               self.done = 0
+               self.file = file
+
+       def more (self):
+               if self.done:
+                       return ''
+               else:
+                       data = self.file.read (self.out_buffer_size)
+                       if not data:
+                               self.file.close()
+                               del self.file
+                               self.done = 1
+                               return ''
+                       else:
+                               return data
+
+# A simple output producer.  This one does not [yet] have
+# the safety feature builtin to the monitor channel:  runaway
+# output will not be caught.
+
+# don't try to print from within any of the methods
+# of this object.
+
+class output_producer:
+       "Acts like an output file; suitable for capturing sys.stdout"
+       def __init__ (self):
+               self.data = ''
+                       
+       def write (self, data):
+               lines = string.splitfields (data, '\n')
+               data = string.join (lines, '\r\n')
+               self.data = self.data + data
+               
+       def writeline (self, line):
+               self.data = self.data + line + '\r\n'
+               
+       def writelines (self, lines):
+               self.data = self.data + string.joinfields (
+                       lines,
+                       '\r\n'
+                       ) + '\r\n'
+
+       def ready (self):
+               return (len (self.data) > 0)
+
+       def flush (self):
+               pass
+
+       def softspace (self, *args):
+               pass
+
+       def more (self):
+               if self.data:
+                       result = self.data[:512]
+                       self.data = self.data[512:]
+                       return result
+               else:
+                       return ''
+
+class composite_producer:
+       "combine a fifo of producers into one"
+       def __init__ (self, producers):
+               self.producers = producers
+
+       def more (self):
+               while len(self.producers):
+                       p = self.producers.first()
+                       d = p.more()
+                       if d:
+                               return d
+                       else:
+                               self.producers.pop()
+               else:
+                       return ''
+
+
+class globbing_producer:
+       """
+       'glob' the output from a producer into a particular buffer size.
+       helps reduce the number of calls to send().  [this appears to
+       gain about 30% performance on requests to a single channel]
+       """
+
+       def __init__ (self, producer, buffer_size=1<<16):
+               self.producer = producer
+               self.buffer = ''
+               self.buffer_size = buffer_size
+
+       def more (self):
+               while len(self.buffer) < self.buffer_size:
+                       data = self.producer.more()
+                       if data:
+                               self.buffer = self.buffer + data
+                       else:
+                               break
+               r = self.buffer
+               self.buffer = ''
+               return r
+
+
+class hooked_producer:
+       """
+       A producer that will call <function> when it empties,.
+       with an argument of the number of bytes produced.  Useful
+       for logging/instrumentation purposes.
+       """
+
+       def __init__ (self, producer, function):
+               self.producer = producer
+               self.function = function
+               self.bytes = 0
+
+       def more (self):
+               if self.producer:
+                       result = self.producer.more()
+                       if not result:
+                               self.producer = None
+                               self.function (self.bytes)
+                       else:
+                               self.bytes = self.bytes + len(result)
+                       return result
+               else:
+                       return ''
+
+# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
+# correct.  In the face of Strange Files, it is conceivable that
+# reading a 'file' may produce an amount of data not matching that
+# reported by os.stat() [text/binary mode issues, perhaps the file is
+# being appended to, etc..]  This makes the chunked encoding a True
+# Blessing, and it really ought to be used even with normal files.
+# How beautifully it blends with the concept of the producer.
+
+class chunked_producer:
+       """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+       Here is a sample usage:
+               request['Transfer-Encoding'] = 'chunked'
+               request.push (
+                       producers.chunked_producer (your_producer)
+                       )
+               request.done()
+       """
+
+       def __init__ (self, producer, footers=None):
+               self.producer = producer
+               self.footers = footers
+
+       def more (self):
+               if self.producer:
+                       data = self.producer.more()
+                       if data:
+                               return '%x\r\n%s\r\n' % (len(data), data)
+                       else:
+                               self.producer = None
+                               if self.footers:
+                                       return string.join (
+                                               ['0'] + self.footers,
+                                               '\r\n'
+                                               ) + '\r\n\r\n'
+                               else:
+                                       return '0\r\n\r\n'
+               else:
+                       return ''
+
+# Unfortunately this isn't very useful right now (Aug 97), because
+# apparently the browsers don't do on-the-fly decompression.  Which
+# is sad, because this could _really_ speed things up, especially for
+# low-bandwidth clients (i.e., most everyone).
+
+try:
+       import zlib
+except ImportError:
+       zlib = None
+
+class compressed_producer:
+       """
+       Compress another producer on-the-fly, using ZLIB
+       [Unfortunately, none of the current browsers seem to support this]
+       """
+
+       # Note: It's not very efficient to have the server repeatedly
+       # compressing your outgoing files: compress them ahead of time, or
+       # use a compress-once-and-store scheme.  However, if you have low
+       # bandwidth and low traffic, this may make more sense than
+       # maintaining your source files compressed.
+       #
+       # Can also be used for compressing dynamically-produced output.
+
+       def __init__ (self, producer, level=5):
+               self.producer = producer
+               self.compressor = zlib.compressobj (level)
+
+       def more (self):
+               if self.producer:
+                       cdata = ''
+                       # feed until we get some output
+                       while not cdata:
+                               data = self.producer.more()
+                               if not data:
+                                       self.producer = None
+                                       return self.compressor.flush()
+                               else:
+                                       cdata = self.compressor.compress (data)
+                       return cdata
+               else:
+                       return ''
+
+class escaping_producer:
+
+       "A producer that escapes a sequence of characters"
+       " Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc..."
+
+       def __init__ (self, producer, esc_from='\r\n.', esc_to='\r\n..'):
+               self.producer = producer
+               self.esc_from = esc_from
+               self.esc_to = esc_to
+               self.buffer = ''
+               from asynchat import find_prefix_at_end
+               self.find_prefix_at_end = find_prefix_at_end
+
+       def more (self):
+               esc_from = self.esc_from
+               esc_to   = self.esc_to
+
+               buffer = self.buffer + self.producer.more()
+
+               if buffer:
+                       buffer = string.replace (buffer, esc_from, esc_to)
+                       i = self.find_prefix_at_end (buffer, esc_from)
+                       if i:
+                               # we found a prefix
+                               self.buffer = buffer[-i:]
+                               return buffer[:-i]
+                       else:
+                               # no prefix, return it all
+                               self.buffer = ''
+                               return buffer
+               else:
+                       return buffer
diff --git a/demo/medusa/put_handler.py b/demo/medusa/put_handler.py
new file mode 100644 (file)
index 0000000..488fb42
--- /dev/null
@@ -0,0 +1,113 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1996-2000 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+import re
+import string
+
+import default_handler
+unquote                = default_handler.unquote
+get_header     = default_handler.get_header
+
+last_request = None
+
+class put_handler:
+       def __init__ (self, filesystem, uri_regex):
+               self.filesystem = filesystem
+               if type (uri_regex) == type(''):
+                       self.uri_regex = re.compile (uri_regex)
+               else:
+                       self.uri_regex = uri_regex
+
+       def match (self, request):
+               uri = request.uri
+               if request.command == 'put':
+                       m = self.uri_regex.match (uri)
+                       if m and m.end() == len(uri):
+                               return 1
+               return 0
+
+       def handle_request (self, request):
+
+               path, params, query, fragment = request.split_uri()
+
+               # strip off leading slashes
+               while path and path[0] == '/':
+                       path = path[1:]
+
+               if '%' in path:
+                       path = unquote (path)
+
+               # make sure there's a content-length header
+               cl = get_header (CONTENT_LENGTH, request.header)
+               if not cl:
+                       request.error (411)
+                       return
+               else:
+                       cl = string.atoi (cl)
+
+               # don't let the try to overwrite a directory
+               if self.filesystem.isdir (path):
+                       request.error (405)
+                       return
+
+               is_update = self.filesystem.isfile (path)
+
+               try:
+                       output_file = self.filesystem.open (path, 'wb')
+               except:
+                       request.error (405)
+                       return
+               
+               request.collector = put_collector (output_file, cl, request, is_update)
+
+               # no terminator while receiving PUT data
+               request.channel.set_terminator (None)
+
+               # don't respond yet, wait until we've received the data...
+               
+class put_collector:
+       def __init__ (self, file, length, request, is_update):
+               self.file               = file
+               self.length             = length
+               self.request    = request
+               self.is_update  = is_update
+               self.bytes_in   = 0
+
+       def collect_incoming_data (self, data):
+               ld = len(data)
+               bi = self.bytes_in
+               if (bi + ld) >= self.length:
+                       # last bit of data
+                       chunk = self.length - bi
+                       self.file.write (data[:chunk])
+                       self.file.close()
+
+                       if chunk != ld:
+                               print 'orphaned %d bytes: <%s>' % (ld - chunk, repr(data[chunk:]))
+
+                       # do some housekeeping
+                       r = self.request
+                       ch = r.channel
+                       ch.current_request = None
+                       # set the terminator back to the default
+                       ch.set_terminator ('\r\n\r\n')
+                       if self.is_update:
+                               r.reply_code = 204 # No content
+                               r.done()
+                       else:
+                               r.reply_now (201) # Created
+                       # avoid circular reference
+                       del self.request
+               else:
+                       self.file.write (data)
+                       self.bytes_in = self.bytes_in + ld
+
+       def found_terminator (self):
+               # shouldn't be called
+               pass
+
+CONTENT_LENGTH = re.compile ('Content-Length: ([0-9]+)', re.IGNORECASE)
diff --git a/demo/medusa/redirecting_handler.py b/demo/medusa/redirecting_handler.py
new file mode 100644 (file)
index 0000000..e6a1e90
--- /dev/null
@@ -0,0 +1,44 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+#      Author: Sam Rushing <rushing@nightmare.com>
+#      Copyright 1996-2000 by Sam Rushing
+#                                               All Rights Reserved.
+#
+
+import re
+import counter
+
+class redirecting_handler:
+
+       def __init__ (self, pattern, redirect, regex_flag=re.IGNORECASE):
+               self.pattern = pattern
+               self.redirect = redirect
+               self.patreg = re.compile (pattern, regex_flag)
+               self.hits = counter.counter()
+
+       def match (self, request):
+               m = self.patref.match (request.uri)
+               return (m and (m.end() == len(request.uri)))
+                       
+       def handle_request (self, request):
+               self.hits.increment()
+               m = self.patreg.match (request.uri)
+               part = m.group(1)
+
+               request['Location'] = self.redirect % part
+               request.error (302) # moved temporarily
+
+       def __repr__ (self):
+               return '<Redirecting Handler at %08x [%s => %s]>' % (
+                       id(self),
+                       repr(self.pattern),
+                       repr(self.redirect)
+                       )
+
+       def status (self):
+               import producers
+               return producers.simple_producer (
+                       '<li> Redirecting Handler %s => %s <b>Hits</b>: %s' % (
+                               self.pattern, self.redirect, self.hits
+                               )
+                       )
diff --git a/demo/medusa/server.pem b/demo/medusa/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa/status_handler.py b/demo/medusa/status_handler.py
new file mode 100644 (file)
index 0000000..2e6223e
--- /dev/null
@@ -0,0 +1,282 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+VERSION_STRING = "$Id: status_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+#                      
+# medusa status extension
+#
+
+import string
+import time
+import re
+
+import asyncore
+import http_server
+import medusa_gif
+import producers
+from counter import counter
+
+START_TIME = long(time.time())
+
+class status_extension:
+       hit_counter = counter()
+
+       def __init__ (self, objects, statusdir='/status', allow_emergency_debug=0):
+               self.objects = objects
+               self.statusdir = statusdir
+               self.allow_emergency_debug = allow_emergency_debug
+               # We use /status instead of statusdir here because it's too
+               # hard to pass statusdir to the logger, who makes the HREF
+               # to the object dir.  We don't need the security-through-
+               # obscurity here in any case, because the id is obscurity enough
+               self.hyper_regex = re.compile('/status/object/([0-9]+)/.*')
+               self.hyper_objects = []
+               for object in objects:
+                       self.register_hyper_object (object)
+
+       def __repr__ (self):
+               return '<Status Extension (%s hits) at %x>' % (
+                       self.hit_counter,
+                       id(self)
+                       )
+
+       def match (self, request):
+               path, params, query, fragment = request.split_uri()
+               # For reasons explained above, we don't use statusdir for /object
+               return (path[:len(self.statusdir)] == self.statusdir or
+                               path[:len("/status/object/")] == '/status/object/')
+
+       # Possible Targets:
+       # /status
+       # /status/channel_list
+       # /status/medusa.gif
+
+       # can we have 'clickable' objects?
+       # [yes, we can use id(x) and do a linear search]
+
+       # Dynamic producers:
+       # HTTP/1.0: we must close the channel, because it's dynamic output
+       # HTTP/1.1: we can use the chunked transfer-encoding, and leave
+       #   it open.
+
+       def handle_request (self, request):
+               [path, params, query, fragment] = request.split_uri()
+               self.hit_counter.increment()
+               if path == self.statusdir:          # and not a subdirectory
+                       up_time = string.join (english_time (long(time.time()) - START_TIME))
+                       request['Content-Type'] = 'text/html'
+                       request.push (
+                               '<html>'
+                               '<title>Medusa Status Reports</title>'
+                               '<body bgcolor="#ffffff">'
+                               '<h1>Medusa Status Reports</h1>'
+                               '<b>Up:</b> %s' % up_time
+                               )
+                       for i in range(len(self.objects)):
+                               request.push (self.objects[i].status())
+                               request.push ('<hr>\r\n')
+                       request.push (
+                               '<p><a href="%s/channel_list">Channel List</a>'
+                               '<hr>'
+                               '<img src="%s/medusa.gif" align=right width=%d height=%d>'
+                               '</body></html>' % (
+                                       self.statusdir,
+                                       self.statusdir,
+                                       medusa_gif.width,
+                                       medusa_gif.height
+                                       )
+                               )
+                       request.done()
+               elif path == self.statusdir + '/channel_list':
+                       request['Content-Type'] = 'text/html'
+                       request.push ('<html><body>')
+                       request.push(channel_list_producer(self.statusdir))
+                       request.push (
+                               '<hr>'
+                               '<img src="%s/medusa.gif" align=right width=%d height=%d>' % (
+                                       self.statusdir,
+                                       medusa_gif.width, 
+                                       medusa_gif.height
+                                       ) +
+                               '</body></html>'
+                               )
+                       request.done()
+
+               elif path == self.statusdir + '/medusa.gif':
+                       request['Content-Type'] = 'image/gif'
+                       request['Content-Length'] = len(medusa_gif.data)
+                       request.push (medusa_gif.data)
+                       request.done()
+
+               elif path == self.statusdir + '/close_zombies':
+                       message = (
+                               '<h2>Closing all zombie http client connections...</h2>'
+                               '<p><a href="%s">Back to the status page</a>' % self.statusdir
+                               )
+                       request['Content-Type'] = 'text/html'
+                       request['Content-Length'] = len (message)
+                       request.push (message)
+                       now = int (time.time())
+                       for channel in asyncore.socket_map.keys():
+                               if channel.__class__ == http_server.http_channel:
+                                       if channel != request.channel:
+                                               if (now - channel.creation_time) > channel.zombie_timeout:
+                                                       channel.close()
+                       request.done()
+
+               # Emergency Debug Mode
+               # If a server is running away from you, don't KILL it!
+               # Move all the AF_INET server ports and perform an autopsy...
+               # [disabled by default to protect the innocent]
+               elif self.allow_emergency_debug and path == self.statusdir + '/emergency_debug':
+                       request.push ('<html>Moving All Servers...</html>')
+                       request.done()
+                       for channel in asyncore.socket_map.keys():
+                               if channel.accepting:
+                                       if type(channel.addr) is type(()):
+                                               ip, port = channel.addr
+                                               channel.socket.close()
+                                               channel.del_channel()
+                                               channel.addr = (ip, port+10000)
+                                               fam, typ = channel.family_and_type
+                                               channel.create_socket (fam, typ)
+                                               channel.set_reuse_addr()
+                                               channel.bind (channel.addr)
+                                               channel.listen(5)
+
+               else:
+                       m = self.hyper_regex.match (path)
+                       if m:
+                               oid = string.atoi (m.group (1))
+                               for object in self.hyper_objects:
+                                       if id (object) == oid:
+                                               if hasattr (object, 'hyper_respond'):
+                                                       object.hyper_respond (self, path, request)
+                       else:
+                               request.error (404)
+                               return
+
+       def status (self):
+               return producers.simple_producer (
+                       '<li>Status Extension <b>Hits</b> : %s' % self.hit_counter
+                       )
+
+       def register_hyper_object (self, object):
+               if not object in self.hyper_objects:
+                       self.hyper_objects.append (object)
+
+import logger
+
+class logger_for_status (logger.tail_logger):
+
+       def status (self):
+               return 'Last %d log entries for: %s' % (
+                       len (self.messages),
+                       html_repr (self)
+                       )
+
+       def hyper_respond (self, sh, path, request):
+               request['Content-Type'] = 'text/plain'
+               messages = self.messages[:]
+               messages.reverse()
+               request.push (lines_producer (messages))
+               request.done()
+
+class lines_producer:
+       def __init__ (self, lines):
+               self.lines = lines
+
+       def ready (self):
+               return len(self.lines)
+
+       def more (self):
+               if self.lines:
+                       chunk = self.lines[:50]
+                       self.lines = self.lines[50:]
+                       return string.join (chunk, '\r\n') + '\r\n'
+               else:
+                       return ''
+
+class channel_list_producer (lines_producer):
+       def __init__ (self, statusdir):
+               channel_reprs = map (
+                       lambda x: '&lt;' + repr(x)[1:-1] + '&gt;',
+                       asyncore.socket_map.values()
+                       )
+               channel_reprs.sort()
+               lines_producer.__init__ (
+                       self,
+                       ['<h1>Active Channel List</h1>',
+                        '<pre>'
+                        ] + channel_reprs + [
+                                '</pre>',
+                                '<p><a href="%s">Status Report</a>' % statusdir
+                                ]
+                       )
+
+
+# this really needs a full-blown quoter...
+def sanitize (s):
+       if '<' in s:
+               s = string.join (string.split (s, '<'), '&lt;')
+       if '>' in s:
+               s = string.join (string.split (s, '>'), '&gt;')
+       return s
+
+def html_repr (object):
+       so = sanitize (repr (object))
+       if hasattr (object, 'hyper_respond'):
+               return '<a href="/status/object/%d/">%s</a>' % (id (object), so)
+       else:
+               return so
+
+def html_reprs (list, front='', back=''):
+       reprs = map (
+               lambda x,f=front,b=back: '%s%s%s' % (f,x,b),
+               map (lambda x: sanitize (html_repr(x)), list)
+               )
+       reprs.sort()
+       return reprs
+
+# for example, tera, giga, mega, kilo
+# p_d (n, (1024, 1024, 1024, 1024))
+# smallest divider goes first - for example
+# minutes, hours, days
+# p_d (n, (60, 60, 24))
+
+def progressive_divide (n, parts):
+       result = []
+       for part in parts:
+               n, rem = divmod (n, part)
+               result.append (rem)
+       result.append (n)
+       return result
+
+# b,k,m,g,t
+def split_by_units (n, units, dividers, format_string):
+       divs = progressive_divide (n, dividers)
+       result = []
+       for i in range(len(units)):
+               if divs[i]:
+                       result.append (format_string % (divs[i], units[i]))
+       result.reverse()
+       if not result:
+               return [format_string % (0, units[0])]
+       else:
+               return result
+
+def english_bytes (n):
+       return split_by_units (
+               n,
+               ('','K','M','G','T'),
+               (1024, 1024, 1024, 1024, 1024),
+               '%d %sB'
+               )
+
+def english_time (n):
+       return split_by_units (
+               n,
+               ('secs', 'mins', 'hours', 'days', 'weeks', 'years'),
+               (         60,     60,      24,     7,       52),
+               '%d %s'
+               )
diff --git a/demo/medusa/virtual_handler.py b/demo/medusa/virtual_handler.py
new file mode 100644 (file)
index 0000000..96f19df
--- /dev/null
@@ -0,0 +1,60 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import socket
+import default_handler
+import re
+
+HOST = re.compile ('Host: ([^:/]+).*', re.IGNORECASE)
+
+get_header = default_handler.get_header
+
+class virtual_handler:
+
+       """HTTP request handler for an HTTP/1.0-style virtual host.  Each
+       Virtual host must have a different IP"""
+
+       def __init__ (self, handler, hostname):
+               self.handler = handler
+               self.hostname = hostname
+               try:
+                       self.ip = socket.gethostbyname (hostname)
+               except socket.error:
+                       raise ValueError, "Virtual Hostname %s does not appear to be registered in the DNS" % hostname
+
+       def match (self, request):
+               if (request.channel.addr[0] == self.ip):
+                       return 1
+               else:
+                       return 0
+
+       def handle_request (self, request):
+               return self.handler.handle_request (request)
+
+       def __repr__ (self):
+               return '<virtual request handler for %s>' % self.hostname
+
+
+class virtual_handler_with_host:
+
+       """HTTP request handler for HTTP/1.1-style virtual hosts.  This
+       matches by checking the value of the 'Host' header in the request.
+       You actually don't _have_ to support HTTP/1.1 to use this, since
+       many browsers now send the 'Host' header.  This is a Good Thing."""
+
+       def __init__ (self, handler, hostname):
+               self.handler = handler
+               self.hostname = hostname
+
+       def match (self, request):
+               host = get_header (HOST, request.header)
+               if host == self.hostname:
+                       return 1
+               else:
+                       return 0
+               
+       def handle_request (self, request):
+               return self.handler.handle_request (request)
+
+       def __repr__ (self):
+               return '<virtual request handler for %s>' % self.hostname
+
diff --git a/demo/medusa/xmlrpc_handler.py b/demo/medusa/xmlrpc_handler.py
new file mode 100644 (file)
index 0000000..d56baf2
--- /dev/null
@@ -0,0 +1,104 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# See http://www.xml-rpc.com/
+#     http://www.pythonware.com/products/xmlrpc/
+
+# Based on "xmlrpcserver.py" by Fredrik Lundh (fredrik@pythonware.com)
+
+VERSION = "$Id: xmlrpc_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+import http_server
+import xmlrpclib
+
+import string
+import sys
+
+class xmlrpc_handler:
+
+       def match (self, request):
+               # Note: /RPC2 is not required by the spec, so you may override this method.
+               if request.uri[:5] == '/RPC2':
+                       return 1
+               else:
+                       return 0
+
+       def handle_request (self, request):
+               [path, params, query, fragment] = request.split_uri()
+               
+               if request.command in ('post', 'put'):
+                       request.collector = collector (self, request)
+               else:
+                       request.error (400)
+
+       def continue_request (self, data, request):
+               params, method = xmlrpclib.loads (data)
+               try:
+                       # generate response
+                       try:
+                               response = self.call (method, params)
+                               if type(response) != type(()):
+                                       response = (response,)
+                       except:
+                               # report exception back to server
+                               response = xmlrpclib.dumps (
+                                       xmlrpclib.Fault (1, "%s:%s" % (sys.exc_type, sys.exc_value))
+                                       )
+                       else:
+                               response = xmlrpclib.dumps (response, methodresponse=1)
+               except:
+                       # internal error, report as HTTP server error
+                       request.error (500)
+               else:
+                       # got a valid XML RPC response
+                       request['Content-Type'] = 'text/xml'
+                       request.push (response)
+                       request.done()
+
+       def call (self, method, params):
+               # override this method to implement RPC methods
+               raise "NotYetImplemented"
+
+class collector:
+
+       "gathers input for POST and PUT requests"
+
+       def __init__ (self, handler, request):
+
+               self.handler = handler
+               self.request = request
+               self.data = ''
+
+               # make sure there's a content-length header
+               cl = request.get_header ('content-length')
+
+               if not cl:
+                       request.error (411)
+               else:
+                       cl = string.atoi (cl)
+                       # using a 'numeric' terminator
+                       self.request.channel.set_terminator (cl)
+
+       def collect_incoming_data (self, data):
+               self.data = self.data + data
+
+       def found_terminator (self):
+               # set the terminator back to the default
+               self.request.channel.set_terminator ('\r\n\r\n')
+               self.handler.continue_request (self.data, self.request)
+
+if __name__ == '__main__':
+
+       class rpc_demo (xmlrpc_handler):
+               
+               def call (self, method, params):
+                       print 'method="%s" params=%s' % (method, params)
+                       return "Sure, that works"
+
+       import asyncore
+       import http_server
+
+       hs = http_server.http_server ('', 8000)
+       rpc = rpc_demo()
+       hs.install_handler (rpc)
+       
+       asyncore.loop()
diff --git a/demo/medusa054/00_README b/demo/medusa054/00_README
new file mode 100644 (file)
index 0000000..5701364
--- /dev/null
@@ -0,0 +1,30 @@
+
+ 21 Mar 2004
+-------------
+
+M2Crypto HTTPS and FTP/TLS servers
+
+All the files in this directory are from Medusa 0.54, except for the
+following:
+
+- 00_README (this file)
+- server.pem, the server's certificate
+- ca.pem, my CA certificate
+- https_server.py
+- ftps_server.py
+- START.py
+- START_xmlrpc.py
+- index.html, a sample HTML file
+- poison_handler.py, a webpoison clone
+
+By default, http_server listens on port 39080 and https_server port
+39443. Document root is current directory, and serves up index.html.
+
+The xmlrpc server is accessible below '/RPC2'.
+
+The FTP/TLS server listens on port 39021 by default. I've only tested it with
+the 'anonymous' authentication handler.
+
+Medusa files are copyright Sam Rushing. Recent versions are maintained
+by Andrew Kuchling. My files are copyright me.
+
diff --git a/demo/medusa054/START.py b/demo/medusa054/START.py
new file mode 100644 (file)
index 0000000..fb08ab3
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa 
+import asyncore
+import default_handler
+import filesys
+import ftp_server
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+import ftps_server
+from M2Crypto import Rand, SSL, threading
+
+HTTP_PORT=39080
+HTTPS_PORT=39443
+FTP_PORT = 39021
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1) 
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+ssl_ctx.load_verify_locations('ca.pem', '')
+ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:39443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+#fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk130/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+#class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+#    def call (self, method, params):
+#        print 'method="%s" params=%s' % (method, params)
+#        return "Sure, that works"
+#rpch = rpc_demo()
+#hs.install_handler(rpch)
+#hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+fauthz = ftp_server.anon_authorizer('/usr/local/pkg/apache/htdocs')
+ftps = ftps_server.ftp_tls_server(fauthz, ssl_ctx, port=FTP_PORT)
+
+sh=status_handler.status_extension([hs, hss, ftps])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa054/START_xmlrpc.py b/demo/medusa054/START_xmlrpc.py
new file mode 100644 (file)
index 0000000..65b6d67
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa 
+import asyncore
+import default_handler
+import filesys
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+from M2Crypto import Rand, SSL
+
+# XMLrpc
+import xmlrpc_handler
+
+
+HTTP_PORT=39080
+HTTPS_PORT=39443
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1) 
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+#ssl_ctx.load_verify_location('ca.pem')
+#ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+#ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+#fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk118/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+# Cribbed from xmlrpc_handler.py.
+# This is where you implement your RPC functionality.
+class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+    def call (self, method, params):
+        print 'method="%s" params=%s' % (method, params)
+        return "Sure, that works"
+
+rpch = rpc_demo()
+hs.install_handler(rpch)
+hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+sh=status_handler.status_extension([hss])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa054/ca.pem b/demo/medusa054/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/medusa054/counter.py b/demo/medusa054/counter.py
new file mode 100644 (file)
index 0000000..9dae67c
--- /dev/null
@@ -0,0 +1,51 @@
+# -*- Mode: Python -*-
+
+# It is tempting to add an __int__ method to this class, but it's not
+# a good idea.  This class tries to gracefully handle integer
+# overflow, and to hide this detail from both the programmer and the
+# user.  Note that the __str__ method can be relied on for printing out
+# the value of a counter:
+#
+# >>> print 'Total Client: %s' % self.total_clients
+#
+# If you need to do arithmetic with the value, then use the 'as_long'
+# method, the use of long arithmetic is a reminder that the counter
+# will overflow.
+
+class counter:
+    "general-purpose counter"
+
+    def __init__ (self, initial_value=0):
+        self.value = initial_value
+
+    def increment (self, delta=1):
+        result = self.value
+        try:
+            self.value = self.value + delta
+        except OverflowError:
+            self.value = long(self.value) + delta
+        return result
+
+    def decrement (self, delta=1):
+        result = self.value
+        try:
+            self.value = self.value - delta
+        except OverflowError:
+            self.value = long(self.value) - delta
+        return result
+
+    def as_long (self):
+        return long(self.value)
+
+    def __nonzero__ (self):
+        return self.value != 0
+
+    def __repr__ (self):
+        return '<counter value=%s at %x>' % (self.value, id(self))
+
+    def __str__ (self):
+        s = str(long(self.value))
+        if s[-1:] == 'L':
+            s = s[:-1]
+        return s
+
diff --git a/demo/medusa054/default_handler.py b/demo/medusa054/default_handler.py
new file mode 100644 (file)
index 0000000..9d5e9d7
--- /dev/null
@@ -0,0 +1,213 @@
+# -*- Mode: Python -*-
+#
+#       Author: Sam Rushing <rushing@nightmare.com>
+#       Copyright 1997 by Sam Rushing
+#                                                All Rights Reserved.
+#
+
+# standard python modules
+import mimetypes
+import re
+import stat
+import string
+
+# medusa modules
+import http_date
+import http_server
+import status_handler
+import producers
+
+unquote = http_server.unquote
+
+# This is the 'default' handler.  it implements the base set of
+# features expected of a simple file-delivering HTTP server.  file
+# services are provided through a 'filesystem' object, the very same
+# one used by the FTP server.
+#
+# You can replace or modify this handler if you want a non-standard
+# HTTP server.  You can also derive your own handler classes from
+# it.
+#
+# support for handling POST requests is available in the derived
+# class <default_with_post_handler>, defined below.
+#
+
+from counter import counter
+
+class default_handler:
+
+    valid_commands = ['GET', 'HEAD']
+
+    IDENT = 'Default HTTP Request Handler'
+
+    # Pathnames that are tried when a URI resolves to a directory name
+    directory_defaults = [
+            'index.html',
+            'default.html'
+            ]
+
+    default_file_producer = producers.file_producer
+
+    def __init__ (self, filesystem):
+        self.filesystem = filesystem
+        # count total hits
+        self.hit_counter = counter()
+        # count file deliveries
+        self.file_counter = counter()
+        # count cache hits
+        self.cache_counter = counter()
+
+    hit_counter = 0
+
+    def __repr__ (self):
+        return '<%s (%s hits) at %x>' % (
+                self.IDENT,
+                self.hit_counter,
+                id (self)
+                )
+
+    # always match, since this is a default
+    def match (self, request):
+        return 1
+
+    # handle a file request, with caching.
+
+    def handle_request (self, request):
+
+        if request.command not in self.valid_commands:
+            request.error (400) # bad request
+            return
+
+        self.hit_counter.increment()
+
+        path, params, query, fragment = request.split_uri()
+
+        if '%' in path:
+            path = unquote (path)
+
+        # strip off all leading slashes
+        while path and path[0] == '/':
+            path = path[1:]
+
+        if self.filesystem.isdir (path):
+            if path and path[-1] != '/':
+                request['Location'] = 'http://%s/%s/' % (
+                        request.channel.server.server_name,
+                        path
+                        )
+                request.error (301)
+                return
+
+            # we could also generate a directory listing here,
+            # may want to move this into another method for that
+            # purpose
+            found = 0
+            if path and path[-1] != '/':
+                path = path + '/'
+            for default in self.directory_defaults:
+                p = path + default
+                if self.filesystem.isfile (p):
+                    path = p
+                    found = 1
+                    break
+            if not found:
+                request.error (404) # Not Found
+                return
+
+        elif not self.filesystem.isfile (path):
+            request.error (404) # Not Found
+            return
+
+        file_length = self.filesystem.stat (path)[stat.ST_SIZE]
+
+        ims = get_header_match (IF_MODIFIED_SINCE, request.header)
+
+        length_match = 1
+        if ims:
+            length = ims.group (4)
+            if length:
+                try:
+                    length = string.atoi (length)
+                    if length != file_length:
+                        length_match = 0
+                except:
+                    pass
+
+        ims_date = 0
+
+        if ims:
+            ims_date = http_date.parse_http_date (ims.group (1))
+
+        try:
+            mtime = self.filesystem.stat (path)[stat.ST_MTIME]
+        except:
+            request.error (404)
+            return
+
+        if length_match and ims_date:
+            if mtime <= ims_date:
+                request.reply_code = 304
+                request.done()
+                self.cache_counter.increment()
+                return
+        try:
+            file = self.filesystem.open (path, 'rb')
+        except IOError:
+            request.error (404)
+            return
+
+        request['Last-Modified'] = http_date.build_http_date (mtime)
+        request['Content-Length'] = file_length
+        self.set_content_type (path, request)
+
+        if request.command == 'GET':
+            request.push (self.default_file_producer (file))
+
+        self.file_counter.increment()
+        request.done()
+
+    def set_content_type (self, path, request):
+        ext = string.lower (get_extension (path))
+        typ, encoding = mimetypes.guess_type(path)
+        if typ is not None:
+            request['Content-Type'] = typ
+        else:
+            # TODO: test a chunk off the front of the file for 8-bit
+            # characters, and use application/octet-stream instead.
+            request['Content-Type'] = 'text/plain'
+
+    def status (self):
+        return producers.simple_producer (
+                '<li>%s' % status_handler.html_repr (self)
+                + '<ul>'
+                + '  <li><b>Total Hits:</b> %s'                 % self.hit_counter
+                + '  <li><b>Files Delivered:</b> %s'    % self.file_counter
+                + '  <li><b>Cache Hits:</b> %s'                 % self.cache_counter
+                + '</ul>'
+                )
+
+# HTTP/1.0 doesn't say anything about the "; length=nnnn" addition
+# to this header.  I suppose its purpose is to avoid the overhead
+# of parsing dates...
+IF_MODIFIED_SINCE = re.compile (
+        'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',
+        re.IGNORECASE
+        )
+
+USER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)
+
+CONTENT_TYPE = re.compile (
+        r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\'\(\)+_,./:=?-]+)$)|$)',
+        re.IGNORECASE
+        )
+
+get_header = http_server.get_header
+get_header_match = http_server.get_header_match
+
+def get_extension (path):
+    dirsep = string.rfind (path, '/')
+    dotsep = string.rfind (path, '.')
+    if dotsep > dirsep:
+        return path[dotsep+1:]
+    else:
+        return ''
diff --git a/demo/medusa054/dh1024.pem b/demo/medusa054/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/medusa054/filesys.py b/demo/medusa054/filesys.py
new file mode 100644 (file)
index 0000000..1affd8f
--- /dev/null
@@ -0,0 +1,394 @@
+# -*- Mode: Python -*-
+#       $Id: filesys.py 299 2005-06-09 17:32:28Z heikki $
+#       Author: Sam Rushing <rushing@nightmare.com>
+#
+# Generic filesystem interface.
+#
+
+# We want to provide a complete wrapper around any and all
+# filesystem operations.
+
+# this class is really just for documentation,
+# identifying the API for a filesystem object.
+
+# opening files for reading, and listing directories, should
+# return a producer.
+
+class abstract_filesystem:
+    def __init__ (self):
+        pass
+
+    def current_directory (self):
+        "Return a string representing the current directory."
+        pass
+
+    def listdir (self, path, long=0):
+        """Return a listing of the directory at 'path' The empty string
+        indicates the current directory.  If 'long' is set, instead
+        return a list of (name, stat_info) tuples
+        """
+        pass
+
+    def open (self, path, mode):
+        "Return an open file object"
+        pass
+
+    def stat (self, path):
+        "Return the equivalent of os.stat() on the given path."
+        pass
+
+    def isdir (self, path):
+        "Does the path represent a directory?"
+        pass
+
+    def isfile (self, path):
+        "Does the path represent a plain file?"
+        pass
+
+    def cwd (self, path):
+        "Change the working directory."
+        pass
+
+    def cdup (self):
+        "Change to the parent of the current directory."
+        pass
+
+
+    def longify (self, path):
+        """Return a 'long' representation of the filename
+        [for the output of the LIST command]"""
+        pass
+
+# standard wrapper around a unix-like filesystem, with a 'false root'
+# capability.
+
+# security considerations: can symbolic links be used to 'escape' the
+# root?  should we allow it?  if not, then we could scan the
+# filesystem on startup, but that would not help if they were added
+# later.  We will probably need to check for symlinks in the cwd method.
+
+# what to do if wd is an invalid directory?
+
+import os
+import stat
+import re
+import string
+
+def safe_stat (path):
+    try:
+        return (path, os.stat (path))
+    except:
+        return None
+
+import glob
+
+class os_filesystem:
+    path_module = os.path
+
+    # set this to zero if you want to disable pathname globbing.
+    # [we currently don't glob, anyway]
+    do_globbing = 1
+
+    def __init__ (self, root, wd='/'):
+        self.root = root
+        self.wd = wd
+
+    def current_directory (self):
+        return self.wd
+
+    def isfile (self, path):
+        p = self.normalize (self.path_module.join (self.wd, path))
+        return self.path_module.isfile (self.translate(p))
+
+    def isdir (self, path):
+        p = self.normalize (self.path_module.join (self.wd, path))
+        return self.path_module.isdir (self.translate(p))
+
+    def cwd (self, path):
+        p = self.normalize (self.path_module.join (self.wd, path))
+        translated_path = self.translate(p)
+        if not self.path_module.isdir (translated_path):
+            return 0
+        else:
+            old_dir = os.getcwd()
+            # temporarily change to that directory, in order
+            # to see if we have permission to do so.
+            try:
+                can = 0
+                try:
+                    os.chdir (translated_path)
+                    can = 1
+                    self.wd = p
+                except:
+                    pass
+            finally:
+                if can:
+                    os.chdir (old_dir)
+            return can
+
+    def cdup (self):
+        return self.cwd ('..')
+
+    def listdir (self, path, long=0):
+        p = self.translate (path)
+        # I think we should glob, but limit it to the current
+        # directory only.
+        ld = os.listdir (p)
+        if not long:
+            return list_producer (ld, None)
+        else:
+            old_dir = os.getcwd()
+            try:
+                os.chdir (p)
+                # if os.stat fails we ignore that file.
+                result = filter (None, map (safe_stat, ld))
+            finally:
+                os.chdir (old_dir)
+            return list_producer (result, self.longify)
+
+    # TODO: implement a cache w/timeout for stat()
+    def stat (self, path):
+        p = self.translate (path)
+        return os.stat (p)
+
+    def open (self, path, mode):
+        p = self.translate (path)
+        return open (p, mode)
+
+    def unlink (self, path):
+        p = self.translate (path)
+        return os.unlink (p)
+
+    def mkdir (self, path):
+        p = self.translate (path)
+        return os.mkdir (p)
+
+    def rmdir (self, path):
+        p = self.translate (path)
+        return os.rmdir (p)
+
+    # utility methods
+    def normalize (self, path):
+        # watch for the ever-sneaky '/+' path element
+        path = re.sub('/+', '/', path)
+        p = self.path_module.normpath (path)
+        # remove 'dangling' cdup's.
+        if len(p) > 2 and p[:3] == '/..':
+            p = '/'
+        return p
+
+    def translate (self, path):
+        # we need to join together three separate
+        # path components, and do it safely.
+        # <real_root>/<current_directory>/<path>
+        # use the operating system's path separator.
+        path = string.join (string.split (path, '/'), os.sep)
+        p = self.normalize (self.path_module.join (self.wd, path))
+        p = self.normalize (self.path_module.join (self.root, p[1:]))
+        return p
+
+    def longify (self, (path, stat_info)):
+        return unix_longify (path, stat_info)
+
+    def __repr__ (self):
+        return '<unix-style fs root:%s wd:%s>' % (
+                self.root,
+                self.wd
+                )
+
+if os.name == 'posix':
+
+    class unix_filesystem (os_filesystem):
+        pass
+
+    class schizophrenic_unix_filesystem (os_filesystem):
+        PROCESS_UID             = os.getuid()
+        PROCESS_EUID    = os.geteuid()
+        PROCESS_GID             = os.getgid()
+        PROCESS_EGID    = os.getegid()
+
+        def __init__ (self, root, wd='/', persona=(None, None)):
+            os_filesystem.__init__ (self, root, wd)
+            self.persona = persona
+
+        def become_persona (self):
+            if self.persona is not (None, None):
+                uid, gid = self.persona
+                # the order of these is important!
+                os.setegid (gid)
+                os.seteuid (uid)
+
+        def become_nobody (self):
+            if self.persona is not (None, None):
+                os.seteuid (self.PROCESS_UID)
+                os.setegid (self.PROCESS_GID)
+
+        # cwd, cdup, open, listdir
+        def cwd (self, path):
+            try:
+                self.become_persona()
+                return os_filesystem.cwd (self, path)
+            finally:
+                self.become_nobody()
+
+        def cdup (self, path):
+            try:
+                self.become_persona()
+                return os_filesystem.cdup (self)
+            finally:
+                self.become_nobody()
+
+        def open (self, filename, mode):
+            try:
+                self.become_persona()
+                return os_filesystem.open (self, filename, mode)
+            finally:
+                self.become_nobody()
+
+        def listdir (self, path, long=0):
+            try:
+                self.become_persona()
+                return os_filesystem.listdir (self, path, long)
+            finally:
+                self.become_nobody()
+
+# For the 'real' root, we could obtain a list of drives, and then
+# use that.  Doesn't win32 provide such a 'real' filesystem?
+# [yes, I think something like this "\\.\c\windows"]
+
+class msdos_filesystem (os_filesystem):
+    def longify (self, (path, stat_info)):
+        return msdos_longify (path, stat_info)
+
+# A merged filesystem will let you plug other filesystems together.
+# We really need the equivalent of a 'mount' capability - this seems
+# to be the most general idea.  So you'd use a 'mount' method to place
+# another filesystem somewhere in the hierarchy.
+
+# Note: this is most likely how I will handle ~user directories
+# with the http server.
+
+class merged_filesystem:
+    def __init__ (self, *fsys):
+        pass
+
+# this matches the output of NT's ftp server (when in
+# MSDOS mode) exactly.
+
+def msdos_longify (file, stat_info):
+    if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+        dir = '<DIR>'
+    else:
+        dir = '     '
+    date = msdos_date (stat_info[stat.ST_MTIME])
+    return '%s       %s %8d %s' % (
+            date,
+            dir,
+            stat_info[stat.ST_SIZE],
+            file
+            )
+
+def msdos_date (t):
+    try:
+        info = time.gmtime (t)
+    except:
+        info = time.gmtime (0)
+    # year, month, day, hour, minute, second, ...
+    if info[3] > 11:
+        merid = 'PM'
+        info[3] = info[3] - 12
+    else:
+        merid = 'AM'
+    return '%02d-%02d-%02d  %02d:%02d%s' % (
+            info[1],
+            info[2],
+            info[0]%100,
+            info[3],
+            info[4],
+            merid
+            )
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+                  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+mode_table = {
+        '0':'---',
+        '1':'--x',
+        '2':'-w-',
+        '3':'-wx',
+        '4':'r--',
+        '5':'r-x',
+        '6':'rw-',
+        '7':'rwx'
+        }
+
+import time
+
+def unix_longify (file, stat_info):
+    # for now, only pay attention to the lower bits
+    mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
+    mode = string.join (map (lambda x: mode_table[x], mode), '')
+    if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+        dirchar = 'd'
+    else:
+        dirchar = '-'
+    date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
+    return '%s%s %3d %-8d %-8d %8d %s %s' % (
+            dirchar,
+            mode,
+            stat_info[stat.ST_NLINK],
+            stat_info[stat.ST_UID],
+            stat_info[stat.ST_GID],
+            stat_info[stat.ST_SIZE],
+            date,
+            file
+            )
+
+# Emulate the unix 'ls' command's date field.
+# it has two formats - if the date is more than 180
+# days in the past, then it's like this:
+# Oct 19  1995
+# otherwise, it looks like this:
+# Oct 19 17:33
+
+def ls_date (now, t):
+    try:
+        info = time.gmtime (t)
+    except:
+        info = time.gmtime (0)
+    # 15,600,000 == 86,400 * 180
+    if (now - t) > 15600000:
+        return '%s %2d  %d' % (
+                months[info[1]-1],
+                info[2],
+                info[0]
+                )
+    else:
+        return '%s %2d %02d:%02d' % (
+                months[info[1]-1],
+                info[2],
+                info[3],
+                info[4]
+                )
+
+# ===========================================================================
+# Producers
+# ===========================================================================
+
+class list_producer:
+    def __init__ (self, list, func=None):
+        self.list = list
+        self.func = func
+
+    # this should do a pushd/popd
+    def more (self):
+        if not self.list:
+            return ''
+        else:
+            # do a few at a time
+            bunch = self.list[:50]
+            if self.func is not None:
+                bunch = map (self.func, bunch)
+            self.list = self.list[50:]
+            return string.joinfields (bunch, '\r\n') + '\r\n'
+
diff --git a/demo/medusa054/ftp_server.py b/demo/medusa054/ftp_server.py
new file mode 100644 (file)
index 0000000..859cd65
--- /dev/null
@@ -0,0 +1,1111 @@
+# -*- Mode: Python -*-
+
+#       Author: Sam Rushing <rushing@nightmare.com>
+#       Copyright 1996-2000 by Sam Rushing
+#                                                All Rights Reserved.
+#
+
+# An extensible, configurable, asynchronous FTP server.
+#
+# All socket I/O is non-blocking, however file I/O is currently
+# blocking.  Eventually file I/O may be made non-blocking, too, if it
+# seems necessary.  Currently the only CPU-intensive operation is
+# getting and formatting a directory listing.  [this could be moved
+# into another process/directory server, or another thread?]
+#
+# Only a subset of RFC 959 is implemented, but much of that RFC is
+# vestigial anyway.  I've attempted to include the most commonly-used
+# commands, using the feature set of wu-ftpd as a guide.
+
+import asyncore
+import asynchat
+
+import os
+import socket
+import stat
+import string
+import sys
+import time
+
+#from medusa.producers import file_producer
+from producers import file_producer
+
+# TODO: implement a directory listing cache.  On very-high-load
+# servers this could save a lot of disk abuse, and possibly the
+# work of computing emulated unix ls output.
+
+# Potential security problem with the FTP protocol?  I don't think
+# there's any verification of the origin of a data connection.  Not
+# really a problem for the server (since it doesn't send the port
+# command, except when in PASV mode) But I think a data connection
+# could be spoofed by a program with access to a sniffer - it could
+# watch for a PORT command to go over a command channel, and then
+# connect to that port before the server does.
+
+# Unix user id's:
+# In order to support assuming the id of a particular user,
+# it seems there are two options:
+# 1) fork, and seteuid in the child
+# 2) carefully control the effective uid around filesystem accessing
+#    methods, using try/finally. [this seems to work]
+
+VERSION = '1.1'
+
+from counter import counter
+import producers
+import status_handler
+import logger
+
+class ftp_channel (asynchat.async_chat):
+
+    # defaults for a reliable __repr__
+    addr = ('unknown','0')
+
+    # unset this in a derived class in order
+    # to enable the commands in 'self.write_commands'
+    read_only = 1
+    write_commands = ['appe','dele','mkd','rmd','rnfr','rnto','stor','stou']
+
+    restart_position = 0
+
+    # comply with (possibly troublesome) RFC959 requirements
+    # This is necessary to correctly run an active data connection
+    # through a firewall that triggers on the source port (expected
+    # to be 'L-1', or 20 in the normal case).
+    bind_local_minus_one = 0
+
+    def __init__ (self, server, conn, addr):
+        self.server = server
+        self.current_mode = 'a'
+        self.addr = addr
+        asynchat.async_chat.__init__ (self, conn)
+        self.set_terminator ('\r\n')
+
+        # client data port.  Defaults to 'the same as the control connection'.
+        self.client_addr = (addr[0], 21)
+
+        self.client_dc = None
+        self.in_buffer = ''
+        self.closing = 0
+        self.passive_acceptor = None
+        self.passive_connection = None
+        self.filesystem = None
+        self.authorized = 0
+        # send the greeting
+        self.respond (
+                '220 %s FTP server (Medusa Async V%s [experimental]) ready.' % (
+                        self.server.hostname,
+                        VERSION
+                        )
+                )
+
+#       def __del__ (self):
+#               print 'ftp_channel.__del__()'
+
+    # --------------------------------------------------
+    # async-library methods
+    # --------------------------------------------------
+
+    def handle_expt (self):
+        # this is handled below.  not sure what I could
+        # do here to make that code less kludgish.
+        pass
+
+    def collect_incoming_data (self, data):
+        self.in_buffer = self.in_buffer + data
+        if len(self.in_buffer) > 4096:
+            # silently truncate really long lines
+            # (possible denial-of-service attack)
+            self.in_buffer = ''
+
+    def found_terminator (self):
+
+        line = self.in_buffer
+
+        if not len(line):
+            return
+
+        sp = string.find (line, ' ')
+        if sp != -1:
+            line = [line[:sp], line[sp+1:]]
+        else:
+            line = [line]
+
+        command = string.lower (line[0])
+        # watch especially for 'urgent' abort commands.
+        if string.find (command, 'abor') != -1:
+            # strip off telnet sync chars and the like...
+            while command and command[0] not in string.letters:
+                command = command[1:]
+        fun_name = 'cmd_%s' % command
+        if command != 'pass':
+            self.log ('<== %s' % repr(self.in_buffer)[1:-1])
+        else:
+            self.log ('<== %s' % line[0]+' <password>')
+        self.in_buffer = ''
+        if not hasattr (self, fun_name):
+            self.command_not_understood (line[0])
+            return
+        fun = getattr (self, fun_name)
+        if (not self.authorized) and (command not in ('user', 'pass', 'help', 'quit')):
+            self.respond ('530 Please log in with USER and PASS')
+        elif (not self.check_command_authorization (command)):
+            self.command_not_authorized (command)
+        else:
+            try:
+                result = apply (fun, (line,))
+            except:
+                self.server.total_exceptions.increment()
+                (file, fun, line), t,v, tbinfo = asyncore.compact_traceback()
+                if self.client_dc:
+                    try:
+                        self.client_dc.close()
+                    except:
+                        pass
+                self.respond (
+                        '451 Server Error: %s, %s: file: %s line: %s' % (
+                                t,v,file,line,
+                                )
+                        )
+
+    closed = 0
+    def close (self):
+        if not self.closed:
+            self.closed = 1
+            if self.passive_acceptor:
+                self.passive_acceptor.close()
+            if self.client_dc:
+                self.client_dc.close()
+            self.server.closed_sessions.increment()
+            asynchat.async_chat.close (self)
+
+    # --------------------------------------------------
+    # filesystem interface functions.
+    # override these to provide access control or perform
+    # other functions.
+    # --------------------------------------------------
+
+    def cwd (self, line):
+        return self.filesystem.cwd (line[1])
+
+    def cdup (self, line):
+        return self.filesystem.cdup()
+
+    def open (self, path, mode):
+        return self.filesystem.open (path, mode)
+
+    # returns a producer
+    def listdir (self, path, long=0):
+        return self.filesystem.listdir (path, long)
+
+    def get_dir_list (self, line, long=0):
+        # we need to scan the command line for arguments to '/bin/ls'...
+        args = line[1:]
+        path_args = []
+        for arg in args:
+            if arg[0] != '-':
+                path_args.append (arg)
+            else:
+                # ignore arguments
+                pass
+        if len(path_args) < 1:
+            dir = '.'
+        else:
+            dir = path_args[0]
+        return self.listdir (dir, long)
+
+    # --------------------------------------------------
+    # authorization methods
+    # --------------------------------------------------
+
+    def check_command_authorization (self, command):
+        if command in self.write_commands and self.read_only:
+            return 0
+        else:
+            return 1
+
+    # --------------------------------------------------
+    # utility methods
+    # --------------------------------------------------
+
+    def log (self, message):
+        self.server.logger.log (
+                self.addr[0],
+                '%d %s' % (
+                        self.addr[1], message
+                        )
+                )
+
+    def respond (self, resp):
+        self.log ('==> %s' % resp)
+        self.push (resp + '\r\n')
+
+    def command_not_understood (self, command):
+        self.respond ("500 '%s': command not understood." % command)
+
+    def command_not_authorized (self, command):
+        self.respond (
+                "530 You are not authorized to perform the '%s' command" % (
+                        command
+                        )
+                )
+
+    def make_xmit_channel (self):
+        # In PASV mode, the connection may or may _not_ have been made
+        # yet.  [although in most cases it is... FTP Explorer being
+        # the only exception I've yet seen].  This gets somewhat confusing
+        # because things may happen in any order...
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                # a connection has already been made.
+                conn, addr = self.passive_acceptor.ready
+                cdc = xmit_channel (self, addr)
+                cdc.set_socket (conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                # we're still waiting for a connect to the PASV port.
+                cdc = xmit_channel (self)
+        else:
+            # not in PASV mode.
+            ip, port = self.client_addr
+            cdc = xmit_channel (self, self.client_addr)
+            cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+            if self.bind_local_minus_one:
+                cdc.bind (('', self.server.port - 1))
+            try:
+                cdc.connect ((ip, port))
+            except socket.error, why:
+                self.respond ("425 Can't build data connection")
+        self.client_dc = cdc
+
+    # pretty much the same as xmit, but only right on the verge of
+    # being worth a merge.
+    def make_recv_channel (self, fd):
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                # a connection has already been made.
+                conn, addr = pa.ready
+                cdc = recv_channel (self, addr, fd)
+                cdc.set_socket (conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                # we're still waiting for a connect to the PASV port.
+                cdc = recv_channel (self, None, fd)
+        else:
+            # not in PASV mode.
+            ip, port = self.client_addr
+            cdc = recv_channel (self, self.client_addr, fd)
+            cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                cdc.connect ((ip, port))
+            except socket.error, why:
+                self.respond ("425 Can't build data connection")
+        self.client_dc = cdc
+
+    type_map = {
+            'a':'ASCII',
+            'i':'Binary',
+            'e':'EBCDIC',
+            'l':'Binary'
+            }
+
+    type_mode_map = {
+            'a':'t',
+            'i':'b',
+            'e':'b',
+            'l':'b'
+            }
+
+    # --------------------------------------------------
+    # command methods
+    # --------------------------------------------------
+
+    def cmd_type (self, line):
+        'specify data transfer type'
+        # ascii, ebcdic, image, local <byte size>
+        t = string.lower (line[1])
+        # no support for EBCDIC
+        # if t not in ['a','e','i','l']:
+        if t not in ['a','i','l']:
+            self.command_not_understood (string.join (line))
+        elif t == 'l' and (len(line) > 2 and line[2] != '8'):
+            self.respond ('504 Byte size must be 8')
+        else:
+            self.current_mode = t
+            self.respond ('200 Type set to %s.' % self.type_map[t])
+
+
+    def cmd_quit (self, line):
+        'terminate session'
+        self.respond ('221 Goodbye.')
+        self.close_when_done()
+
+    def cmd_port (self, line):
+        'specify data connection port'
+        info = string.split (line[1], ',')
+        ip = string.join (info[:4], '.')
+        port = string.atoi(info[4])*256 + string.atoi(info[5])
+        # how many data connections at a time?
+        # I'm assuming one for now...
+        # TODO: we should (optionally) verify that the
+        # ip number belongs to the client.  [wu-ftpd does this?]
+        self.client_addr = (ip, port)
+        self.respond ('200 PORT command successful.')
+
+    def new_passive_acceptor (self):
+        # ensure that only one of these exists at a time.
+        if self.passive_acceptor is not None:
+            self.passive_acceptor.close()
+            self.passive_acceptor = None
+        self.passive_acceptor = passive_acceptor (self)
+        return self.passive_acceptor
+
+    def cmd_pasv (self, line):
+        'prepare for server-to-server transfer'
+        pc = self.new_passive_acceptor()
+        port = pc.addr[1]
+        ip_addr = pc.control_channel.getsockname()[0]
+        self.respond (
+                '227 Entering Passive Mode (%s,%d,%d)' % (
+                        string.replace(ip_addr, '.', ','),
+                        port/256,
+                        port%256
+                        )
+                )
+        self.client_dc = None
+
+    def cmd_nlst (self, line):
+        'give name list of files in directory'
+        # ncftp adds the -FC argument for the user-visible 'nlist'
+        # command.  We could try to emulate ls flags, but not just yet.
+        if '-FC' in line:
+            line.remove ('-FC')
+        try:
+            dir_list_producer = self.get_dir_list (line, 0)
+        except os.error, why:
+            self.respond ('550 Could not list directory: %s' % why)
+            return
+        self.respond (
+                '150 Opening %s mode data connection for file list' % (
+                        self.type_map[self.current_mode]
+                        )
+                )
+        self.make_xmit_channel()
+        self.client_dc.push_with_producer (dir_list_producer)
+        self.client_dc.close_when_done()
+
+    def cmd_list (self, line):
+        'give a list of files in a directory'
+        try:
+            dir_list_producer = self.get_dir_list (line, 1)
+        except os.error, why:
+            self.respond ('550 Could not list directory: %s' % why)
+            return
+        self.respond (
+                '150 Opening %s mode data connection for file list' % (
+                        self.type_map[self.current_mode]
+                        )
+                )
+        self.make_xmit_channel()
+        self.client_dc.push_with_producer (dir_list_producer)
+        self.client_dc.close_when_done()
+
+    def cmd_cwd (self, line):
+        'change working directory'
+        if self.cwd (line):
+            self.respond ('250 CWD command successful.')
+        else:
+            self.respond ('550 No such directory.')
+
+    def cmd_cdup (self, line):
+        'change to parent of current working directory'
+        if self.cdup(line):
+            self.respond ('250 CDUP command successful.')
+        else:
+            self.respond ('550 No such directory.')
+
+    def cmd_pwd (self, line):
+        'print the current working directory'
+        self.respond (
+                '257 "%s" is the current directory.' % (
+                        self.filesystem.current_directory()
+                        )
+                )
+
+    # modification time
+    # example output:
+    # 213 19960301204320
+    def cmd_mdtm (self, line):
+        'show last modification time of file'
+        filename = line[1]
+        if not self.filesystem.isfile (filename):
+            self.respond ('550 "%s" is not a file' % filename)
+        else:
+            mtime = time.gmtime(self.filesystem.stat(filename)[stat.ST_MTIME])
+            self.respond (
+                    '213 %4d%02d%02d%02d%02d%02d' % (
+                            mtime[0],
+                            mtime[1],
+                            mtime[2],
+                            mtime[3],
+                            mtime[4],
+                            mtime[5]
+                            )
+                    )
+
+    def cmd_noop (self, line):
+        'do nothing'
+        self.respond ('200 NOOP command successful.')
+
+    def cmd_size (self, line):
+        'return size of file'
+        filename = line[1]
+        if not self.filesystem.isfile (filename):
+            self.respond ('550 "%s" is not a file' % filename)
+        else:
+            self.respond (
+                    '213 %d' % (self.filesystem.stat(filename)[stat.ST_SIZE])
+                    )
+
+    def cmd_retr (self, line):
+        'retrieve a file'
+        if len(line) < 2:
+            self.command_not_understood (string.join (line))
+        else:
+            file = line[1]
+            if not self.filesystem.isfile (file):
+                self.log_info ('checking %s' % file)
+                self.respond ('550 No such file')
+            else:
+                try:
+                    # FIXME: for some reason, 'rt' isn't working on win95
+                    mode = 'r'+self.type_mode_map[self.current_mode]
+                    fd = self.open (file, mode)
+                except IOError, why:
+                    self.respond ('553 could not open file for reading: %s' % (repr(why)))
+                    return
+                self.respond (
+                        "150 Opening %s mode data connection for file '%s'" % (
+                                self.type_map[self.current_mode],
+                                file
+                                )
+                        )
+                self.make_xmit_channel()
+
+                if self.restart_position:
+                    # try to position the file as requested, but
+                    # give up silently on failure (the 'file object'
+                    # may not support seek())
+                    try:
+                        fd.seek (self.restart_position)
+                    except:
+                        pass
+                    self.restart_position = 0
+
+                self.client_dc.push_with_producer (
+                        file_producer (fd)
+                        )
+                self.client_dc.close_when_done()
+
+    def cmd_stor (self, line, mode='wb'):
+        'store a file'
+        if len (line) < 2:
+            self.command_not_understood (string.join (line))
+        else:
+            if self.restart_position:
+                restart_position = 0
+                self.respond ('553 restart on STOR not yet supported')
+                return
+            file = line[1]
+            # todo: handle that type flag
+            try:
+                fd = self.open (file, mode)
+            except IOError, why:
+                self.respond ('553 could not open file for writing: %s' % (repr(why)))
+                return
+            self.respond (
+                    '150 Opening %s connection for %s' % (
+                            self.type_map[self.current_mode],
+                            file
+                            )
+                    )
+            self.make_recv_channel (fd)
+
+    def cmd_abor (self, line):
+        'abort operation'
+        if self.client_dc:
+            self.client_dc.close()
+        self.respond ('226 ABOR command successful.')
+
+    def cmd_appe (self, line):
+        'append to a file'
+        return self.cmd_stor (line, 'ab')
+
+    def cmd_dele (self, line):
+        if len (line) != 2:
+            self.command_not_understood (string.join (line))
+        else:
+            file = line[1]
+            if self.filesystem.isfile (file):
+                try:
+                    self.filesystem.unlink (file)
+                    self.respond ('250 DELE command successful.')
+                except:
+                    self.respond ('550 error deleting file.')
+            else:
+                self.respond ('550 %s: No such file.' % file)
+
+    def cmd_mkd (self, line):
+        if len (line) != 2:
+            self.command_not_understood (string.join (line))
+        else:
+            path = line[1]
+            try:
+                self.filesystem.mkdir (path)
+                self.respond ('257 MKD command successful.')
+            except:
+                self.respond ('550 error creating directory.')
+
+    def cmd_rmd (self, line):
+        if len (line) != 2:
+            self.command_not_understood (string.join (line))
+        else:
+            path = line[1]
+            try:
+                self.filesystem.rmdir (path)
+                self.respond ('250 RMD command successful.')
+            except:
+                self.respond ('550 error removing directory.')
+
+    def cmd_user (self, line):
+        'specify user name'
+        if len(line) > 1:
+            self.user = line[1]
+            self.respond ('331 Password required.')
+        else:
+            self.command_not_understood (string.join (line))
+
+    def cmd_pass (self, line):
+        'specify password'
+        if len(line) < 2:
+            pw = ''
+        else:
+            pw = line[1]
+        result, message, fs = self.server.authorizer.authorize (self, self.user, pw)
+        if result:
+            self.respond ('230 %s' % message)
+            self.filesystem = fs
+            self.authorized = 1
+            self.log_info('Successful login: Filesystem=%s' % repr(fs))
+        else:
+            self.respond ('530 %s' % message)
+
+    def cmd_rest (self, line):
+        'restart incomplete transfer'
+        try:
+            pos = string.atoi (line[1])
+        except ValueError:
+            self.command_not_understood (string.join (line))
+        self.restart_position = pos
+        self.respond (
+                '350 Restarting at %d. Send STORE or RETRIEVE to initiate transfer.' % pos
+                )
+
+    def cmd_stru (self, line):
+        'obsolete - set file transfer structure'
+        if line[1] in 'fF':
+            # f == 'file'
+            self.respond ('200 STRU F Ok')
+        else:
+            self.respond ('504 Unimplemented STRU type')
+
+    def cmd_mode (self, line):
+        'obsolete - set file transfer mode'
+        if line[1] in 'sS':
+            # f == 'file'
+            self.respond ('200 MODE S Ok')
+        else:
+            self.respond ('502 Unimplemented MODE type')
+
+# The stat command has two personalities.  Normally it returns status
+# information about the current connection.  But if given an argument,
+# it is equivalent to the LIST command, with the data sent over the
+# control connection.  Strange.  But wuftpd, ftpd, and nt's ftp server
+# all support it.
+#
+##      def cmd_stat (self, line):
+##              'return status of server'
+##              pass
+
+    def cmd_syst (self, line):
+        'show operating system type of server system'
+        # Replying to this command is of questionable utility, because
+        # this server does not behave in a predictable way w.r.t. the
+        # output of the LIST command.  We emulate Unix ls output, but
+        # on win32 the pathname can contain drive information at the front
+        # Currently, the combination of ensuring that os.sep == '/'
+        # and removing the leading slash when necessary seems to work.
+        # [cd'ing to another drive also works]
+        #
+        # This is how wuftpd responds, and is probably
+        # the most expected.  The main purpose of this reply is so that
+        # the client knows to expect Unix ls-style LIST output.
+        self.respond ('215 UNIX Type: L8')
+        # one disadvantage to this is that some client programs
+        # assume they can pass args to /bin/ls.
+        # a few typical responses:
+        # 215 UNIX Type: L8 (wuftpd)
+        # 215 Windows_NT version 3.51
+        # 215 VMS MultiNet V3.3
+        # 500 'SYST': command not understood. (SVR4)
+
+    def cmd_help (self, line):
+        'give help information'
+        # find all the methods that match 'cmd_xxxx',
+        # use their docstrings for the help response.
+        attrs = dir(self.__class__)
+        help_lines = []
+        for attr in attrs:
+            if attr[:4] == 'cmd_':
+                x = getattr (self, attr)
+                if type(x) == type(self.cmd_help):
+                    if x.__doc__:
+                        help_lines.append ('\t%s\t%s' % (attr[4:], x.__doc__))
+        if help_lines:
+            self.push ('214-The following commands are recognized\r\n')
+            self.push_with_producer (producers.lines_producer (help_lines))
+            self.push ('214\r\n')
+        else:
+            self.push ('214-\r\n\tHelp Unavailable\r\n214\r\n')
+
+class ftp_server (asyncore.dispatcher):
+    # override this to spawn a different FTP channel class.
+    ftp_channel_class = ftp_channel
+
+    SERVER_IDENT = 'FTP Server (V%s)' % VERSION
+
+    def __init__ (
+            self,
+            authorizer,
+            hostname        =None,
+            ip              ='',
+            port            =21,
+            resolver        =None,
+            logger_object=logger.file_logger (sys.stdout)
+            ):
+        self.ip = ip
+        self.port = port
+        self.authorizer = authorizer
+
+        if hostname is None:
+            self.hostname = socket.gethostname()
+        else:
+            self.hostname = hostname
+
+        # statistics
+        self.total_sessions = counter()
+        self.closed_sessions = counter()
+        self.total_files_out = counter()
+        self.total_files_in = counter()
+        self.total_bytes_out = counter()
+        self.total_bytes_in = counter()
+        self.total_exceptions = counter()
+        #
+        asyncore.dispatcher.__init__ (self)
+        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+        self.set_reuse_addr()
+        self.bind ((self.ip, self.port))
+        self.listen (5)
+
+        if not logger_object:
+            logger_object = sys.stdout
+
+        if resolver:
+            self.logger = logger.resolving_logger (resolver, logger_object)
+        else:
+            self.logger = logger.unresolving_logger (logger_object)
+
+        self.log_info('FTP server started at %s\n\tAuthorizer:%s\n\tHostname: %s\n\tPort: %d' % (
+                time.ctime(time.time()),
+                repr (self.authorizer),
+                self.hostname,
+                self.port)
+                )
+
+    def writable (self):
+        return 0
+
+    def handle_read (self):
+        pass
+
+    def handle_connect (self):
+        pass
+
+    def handle_accept (self):
+        conn, addr = self.accept()
+        self.total_sessions.increment()
+        self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+        self.ftp_channel_class (self, conn, addr)
+
+    # return a producer describing the state of the server
+    def status (self):
+
+        def nice_bytes (n):
+            return string.join (status_handler.english_bytes (n))
+
+        return producers.lines_producer (
+                ['<h2>%s</h2>'                          % self.SERVER_IDENT,
+                 '<br>Listening on <b>Host:</b> %s' % self.hostname,
+                 '<b>Port:</b> %d'                      % self.port,
+                 '<br>Sessions',
+                 '<b>Total:</b> %s'                     % self.total_sessions,
+                 '<b>Current:</b> %d'           % (self.total_sessions.as_long() - self.closed_sessions.as_long()),
+                 '<br>Files',
+                 '<b>Sent:</b> %s'                      % self.total_files_out,
+                 '<b>Received:</b> %s'          % self.total_files_in,
+                 '<br>Bytes',
+                 '<b>Sent:</b> %s'                      % nice_bytes (self.total_bytes_out.as_long()),
+                 '<b>Received:</b> %s'          % nice_bytes (self.total_bytes_in.as_long()),
+                 '<br>Exceptions: %s'           % self.total_exceptions,
+                 ]
+                )
+
+# ======================================================================
+#                                                Data Channel Classes
+# ======================================================================
+
+# This socket accepts a data connection, used when the server has been
+# placed in passive mode.  Although the RFC implies that we ought to
+# be able to use the same acceptor over and over again, this presents
+# a problem: how do we shut it off, so that we are accepting
+# connections only when we expect them?  [we can't]
+#
+# wuftpd, and probably all the other servers, solve this by allowing
+# only one connection to hit this acceptor.  They then close it.  Any
+# subsequent data-connection command will then try for the default
+# port on the client side [which is of course never there].  So the
+# 'always-send-PORT/PASV' behavior seems required.
+#
+# Another note: wuftpd will also be listening on the channel as soon
+# as the PASV command is sent.  It does not wait for a data command
+# first.
+
+# --- we need to queue up a particular behavior:
+#  1) xmit : queue up producer[s]
+#  2) recv : the file object
+#
+# It would be nice if we could make both channels the same.  Hmmm..
+#
+
+class passive_acceptor (asyncore.dispatcher):
+    ready = None
+
+    def __init__ (self, control_channel):
+        # connect_fun (conn, addr)
+        asyncore.dispatcher.__init__ (self)
+        self.control_channel = control_channel
+        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+        # bind to an address on the interface that the
+        # control connection is coming from.
+        self.bind ((
+                self.control_channel.getsockname()[0],
+                0
+                ))
+        self.addr = self.getsockname()
+        self.listen (1)
+
+#       def __del__ (self):
+#               print 'passive_acceptor.__del__()'
+
+    def log (self, *ignore):
+        pass
+
+    def handle_accept (self):
+        conn, addr = self.accept()
+        dc = self.control_channel.client_dc
+        if dc is not None:
+            dc.set_socket (conn)
+            dc.addr = addr
+            dc.connected = 1
+            self.control_channel.passive_acceptor = None
+        else:
+            self.ready = conn, addr
+        self.close()
+
+
+class xmit_channel (asynchat.async_chat):
+
+    # for an ethernet, you want this to be fairly large, in fact, it
+    # _must_ be large for performance comparable to an ftpd.  [64k] we
+    # ought to investigate automatically-sized buffers...
+
+    ac_out_buffer_size = 16384
+    bytes_out = 0
+
+    def __init__ (self, channel, client_addr=None):
+        self.channel = channel
+        self.client_addr = client_addr
+        asynchat.async_chat.__init__ (self)
+
+#       def __del__ (self):
+#               print 'xmit_channel.__del__()'
+
+    def log (self, *args):
+        pass
+
+    def readable (self):
+        return not self.connected
+
+    def writable (self):
+        return 1
+
+    def send (self, data):
+        result = asynchat.async_chat.send (self, data)
+        self.bytes_out = self.bytes_out + result
+        return result
+
+    def handle_error (self):
+        # usually this is to catch an unexpected disconnect.
+        self.log_info ('unexpected disconnect on data xmit channel', 'error')
+        try:
+            self.close()
+        except:
+            pass
+
+    # TODO: there's a better way to do this.  we need to be able to
+    # put 'events' in the producer fifo.  to do this cleanly we need
+    # to reposition the 'producer' fifo as an 'event' fifo.
+
+    def close (self):
+        c = self.channel
+        s = c.server
+        c.client_dc = None
+        s.total_files_out.increment()
+        s.total_bytes_out.increment (self.bytes_out)
+        if not len(self.producer_fifo):
+            c.respond ('226 Transfer complete')
+        elif not c.closed:
+            c.respond ('426 Connection closed; transfer aborted')
+        del c
+        del s
+        del self.channel
+        asynchat.async_chat.close (self)
+
+class recv_channel (asyncore.dispatcher):
+    def __init__ (self, channel, client_addr, fd):
+        self.channel = channel
+        self.client_addr = client_addr
+        self.fd = fd
+        asyncore.dispatcher.__init__ (self)
+        self.bytes_in = counter()
+
+    def log (self, *ignore):
+        pass
+
+    def handle_connect (self):
+        pass
+
+    def writable (self):
+        return 0
+
+    def recv (*args):
+        result = apply (asyncore.dispatcher.recv, args)
+        self = args[0]
+        self.bytes_in.increment(len(result))
+        return result
+
+    buffer_size = 8192
+
+    def handle_read (self):
+        block = self.recv (self.buffer_size)
+        if block:
+            try:
+                self.fd.write (block)
+            except IOError:
+                self.log_info ('got exception writing block...', 'error')
+
+    def handle_close (self):
+        s = self.channel.server
+        s.total_files_in.increment()
+        s.total_bytes_in.increment(self.bytes_in.as_long())
+        self.fd.close()
+        self.channel.respond ('226 Transfer complete.')
+        self.close()
+
+import filesys
+
+# not much of a doorman! 8^)
+class dummy_authorizer:
+    def __init__ (self, root='/'):
+        self.root = root
+    def authorize (self, channel, username, password):
+        channel.persona = -1, -1
+        channel.read_only = 1
+        return 1, 'Ok.', filesys.os_filesystem (self.root)
+
+class anon_authorizer:
+    def __init__ (self, root='/'):
+        self.root = root
+
+    def authorize (self, channel, username, password):
+        if username in ('ftp', 'anonymous'):
+            channel.persona = -1, -1
+            channel.read_only = 1
+            return 1, 'Ok.', filesys.os_filesystem (self.root)
+        else:
+            return 0, 'Password invalid.', None
+
+# ===========================================================================
+# Unix-specific improvements
+# ===========================================================================
+
+if os.name == 'posix':
+
+    class unix_authorizer:
+        # return a trio of (success, reply_string, filesystem)
+        def authorize (self, channel, username, password):
+            import crypt
+            import pwd
+            try:
+                info = pwd.getpwnam (username)
+            except KeyError:
+                return 0, 'No such user.', None
+            mangled = info[1]
+            if crypt.crypt (password, mangled[:2]) == mangled:
+                channel.read_only = 0
+                fs = filesys.schizophrenic_unix_filesystem (
+                        '/',
+                        info[5],
+                        persona = (info[2], info[3])
+                        )
+                return 1, 'Login successful.', fs
+            else:
+                return 0, 'Password invalid.', None
+
+        def __repr__ (self):
+            return '<standard unix authorizer>'
+
+    # simple anonymous ftp support
+    class unix_authorizer_with_anonymous (unix_authorizer):
+        def __init__ (self, root=None, real_users=0):
+            self.root = root
+            self.real_users = real_users
+
+        def authorize (self, channel, username, password):
+            if string.lower(username) in ['anonymous', 'ftp']:
+                import pwd
+                try:
+                    # ok, here we run into lots of confusion.
+                    # on some os', anon runs under user 'nobody',
+                    # on others as 'ftp'.  ownership is also critical.
+                    # need to investigate.
+                    # linux: new linuxen seem to have nobody's UID=-1,
+                    #    which is an illegal value.  Use ftp.
+                    ftp_user_info = pwd.getpwnam ('ftp')
+                    if string.lower(os.uname()[0]) == 'linux':
+                        nobody_user_info = pwd.getpwnam ('ftp')
+                    else:
+                        nobody_user_info = pwd.getpwnam ('nobody')
+                    channel.read_only = 1
+                    if self.root is None:
+                        self.root = ftp_user_info[5]
+                    fs = filesys.unix_filesystem (self.root, '/')
+                    return 1, 'Anonymous Login Successful', fs
+                except KeyError:
+                    return 0, 'Anonymous account not set up', None
+            elif self.real_users:
+                return unix_authorizer.authorize (
+                        self,
+                        channel,
+                        username,
+                        password
+                        )
+            else:
+                return 0, 'User logins not allowed', None
+
+# usage: ftp_server /PATH/TO/FTP/ROOT PORT
+# for example:
+# $ ftp_server /home/users/ftp 8021
+
+if os.name == 'posix':
+    def test (port='8021'):
+        fs = ftp_server (
+                unix_authorizer(),
+                port=string.atoi (port)
+                )
+        try:
+            asyncore.loop()
+        except KeyboardInterrupt:
+            fs.log_info('FTP server shutting down. (received SIGINT)', 'warning')
+            # close everything down on SIGINT.
+            # of course this should be a cleaner shutdown.
+            asyncore.close_all()
+
+    if __name__ == '__main__':
+        test (sys.argv[1])
+# not unix
+else:
+    def test ():
+        fs = ftp_server (dummy_authorizer())
+    if __name__ == '__main__':
+        test ()
+
+# this is the command list from the wuftpd man page
+# '*' means we've implemented it.
+# '!' requires write access
+#
+command_documentation = {
+        'abor': 'abort previous command',                                                       #*
+        'acct': 'specify account (ignored)',
+        'allo': 'allocate storage (vacuously)',
+        'appe': 'append to a file',                                                                     #*!
+        'cdup': 'change to parent of current working directory',        #*
+        'cwd':  'change working directory',                                                     #*
+        'dele': 'delete a file',                                                                        #!
+        'help': 'give help information',                                                        #*
+        'list': 'give list files in a directory',                                       #*
+        'mkd':  'make a directory',                                                                     #!
+        'mdtm': 'show last modification time of file',                          #*
+        'mode': 'specify data transfer mode',
+        'nlst': 'give name list of files in directory',                         #*
+        'noop': 'do nothing',                                                                           #*
+        'pass': 'specify password',                                                                     #*
+        'pasv': 'prepare for server-to-server transfer',                        #*
+        'port': 'specify data connection port',                                         #*
+        'pwd':  'print the current working directory',                          #*
+        'quit': 'terminate session',                                                            #*
+        'rest': 'restart incomplete transfer',                                          #*
+        'retr': 'retrieve a file',                                                                      #*
+        'rmd':  'remove a directory',                                                           #!
+        'rnfr': 'specify rename-from file name',                                        #!
+        'rnto': 'specify rename-to file name',                                          #!
+        'site': 'non-standard commands (see next section)',
+        'size': 'return size of file',                                                          #*
+        'stat': 'return status of server',                                                      #*
+        'stor': 'store a file',                                                                         #*!
+        'stou': 'store a file with a unique name',                                      #!
+        'stru': 'specify data transfer structure',
+        'syst': 'show operating system type of server system',          #*
+        'type': 'specify data transfer type',                                           #*
+        'user': 'specify user name',                                                            #*
+        'xcup': 'change to parent of current working directory (deprecated)',
+        'xcwd': 'change working directory (deprecated)',
+        'xmkd': 'make a directory (deprecated)',                                        #!
+        'xpwd': 'print the current working directory (deprecated)',
+        'xrmd': 'remove a directory (deprecated)',                                      #!
+}
+
+
+# debugging aid (linux)
+def get_vm_size ():
+    return string.atoi (string.split(open ('/proc/self/stat').readline())[22])
+
+def print_vm():
+    print 'vm: %8dk' % (get_vm_size()/1024)
diff --git a/demo/medusa054/ftps_server.py b/demo/medusa054/ftps_server.py
new file mode 100644 (file)
index 0000000..bf2f5a9
--- /dev/null
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server. 
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+    
+    """FTP/TLS server channel for Medusa."""
+
+    def __init__(self, server, ssl_ctx, conn, addr):
+        """Initialise the channel."""
+        self.ssl_ctx = ssl_ctx
+        self.server = server
+        self.current_mode = 'a'
+        self.addr = addr
+        asynchat.async_chat.__init__(self, conn)
+        self.set_terminator('\r\n')
+        self.client_addr = (addr[0], 21)
+        self.client_dc = None
+        self.in_buffer = ''
+        self.closing = 0
+        self.passive_acceptor = None
+        self.passive_connection = None
+        self.filesystem = None
+        self.authorized = 0
+        self._ssl_accepting = 0
+        self._ssl_accepted = 0
+        self._pbsz = None
+        self._prot = None
+        resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+        self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+    def writable(self):
+        return self._ssl_accepting or self._ssl_accepted
+
+    def handle_read(self):
+        """Handle a read event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_read(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def handle_write(self):
+        """Handle a write event."""
+        if self._ssl_accepting:
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+        else:
+            try:
+                ftp_server.ftp_channel.handle_write(self) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.close()
+                else:
+                    raise
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+    def found_terminator(self):
+        """Dispatch the FTP command."""
+        line = self.in_buffer
+        if not len(line):
+            return
+
+        sp = string.find(line, ' ')
+        if sp != -1:
+            line = [line[:sp], line[sp+1:]]
+        else:
+            line = [line]
+
+        command = string.lower(line[0])
+        if string.find(command, 'stor') != -1:
+            while command and command[0] not in string.letters:
+                command = command[1:]
+        
+        func_name = 'cmd_%s' % command
+        if command != 'pass':
+            self.log('<== %s' % repr(self.in_buffer)[1:-1])
+        else:
+            self.log('<== %s' % line[0]+' <password>')
+
+        self.in_buffer = ''
+        if not hasattr(self, func_name):
+            self.command_not_understood(line[0])
+            return 
+    
+        func = getattr(self, func_name)
+        if not self.check_command_authorization(command):
+            self.command_not_authorized(command)
+        else:
+            try:
+                result = apply(func, (line,))
+            except:
+                self.server.total_exceptions.increment()
+                (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+                if self.client_dc:
+                    try:
+                        self.client_dc_close()
+                    except:
+                        pass
+                resp = '451 Server error: %s, %s: file %s line: %s'
+                self.respond(resp % (t, v, file, line))
+
+    def make_xmit_channel(self):
+        """Create a connection for sending data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+                else:
+                    cdc = ftp_server.xmit_channel(self, addr)
+                    cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+                else:
+                    cdc = ftp_server.xmit_channel(self)
+        else:
+            if self._prot:
+                cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+            else:
+                cdc = ftp_server.xmit_channel(self, self.client_addr)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            if self.bind_local_minus_one:
+                cdc.bind(('', self.server.port - 1))
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def make_recv_channel(self, fd):
+        """Create a connection for receiving data."""
+        pa = self.passive_acceptor
+        if pa:
+            if pa.ready:
+                conn, addr = pa.ready
+                if self._prot:
+                    cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, addr, fd)
+                cdc.set_socket(conn)
+                cdc.connected = 1
+                self.passive_acceptor.close()
+                self.passive_acceptor = None
+            else:
+                if self._prot:
+                    cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+                else:
+                    cdc = ftp_server.recv_channel(self, None, fd)
+        else:
+            if self._prot:
+                cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+            else:
+                cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+            cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                cdc.connect(self.client_addr)
+            except socket.error, what:
+                self.respond('425 Cannot build data connection')
+        self.client_dc = cdc
+
+    def cmd_auth(self, line):
+        """Prepare for TLS operation."""
+        # XXX Handle variations.
+        if line[1] != 'TLS':
+            self.command_not_understood (string.join(line))
+        else:
+            self.respond('234 AUTH TLS successful')
+            self._ssl_accepting = 1
+            self.socket = SSL.Connection(self.ssl_ctx, self.socket)    
+            self.socket.setup_addr(self.addr)
+            self.socket.setup_ssl()
+            self.socket.set_accept_state()
+            self._ssl_accepted = self.socket.accept_ssl()
+            if self._ssl_accepted:
+                self._ssl_accepting = 0
+
+    def cmd_pbsz(self, line):
+        """Negotiate size of buffer for secure data transfer. For
+        FTP/TLS the only valid value for the parameter is '0'; any 
+        other value is accepted but ignored."""
+        if not (self._ssl_accepting or self._ssl_accepted):
+            return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+        self._pbsz = 1
+        self.respond('200 PBSZ=0 successful.')
+
+    def cmd_prot(self, line):
+        """Negotiate the security level of the data connection.""" 
+        if self._pbsz is None:
+            return self.respond('503 PBSZ must be issued prior to PROT')
+        if line[1] == 'C':
+            self.respond('200 Protection set to Clear')
+            self._pbsz = None
+            self._prot = None
+        elif line[1] == 'P': 
+            self.respond('200 Protection set to Private')
+            self._prot = 1
+        elif line[1] in ('S', 'E'):
+            self.respond('536 PROT %s unsupported' % line[1])
+        else:
+            self.respond('504 PROT %s unsupported' % line[1])
+            
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+    """FTP/TLS server for Medusa."""
+
+    SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+    ftp_channel_class = ftp_tls_channel
+
+    def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+        """Initialise the server."""
+        self.ssl_ctx = ssl_ctx
+        self.ip = ip
+        self.port = port
+        self.authorizer = authz
+
+        if host is None:
+            self.hostname = socket.gethostname()
+        else:
+            self.hostname = host
+
+        self.total_sessions = counter()
+        self.closed_sessions = counter()
+        self.total_files_out = counter()
+        self.total_files_in = counter()
+        self.total_bytes_out = counter()
+        self.total_bytes_in = counter()
+        self.total_exceptions = counter()
+
+        asyncore.dispatcher.__init__(self)
+        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.set_reuse_addr()
+        self.bind((self.ip, self.port))
+        self.listen(5)
+
+        if log_obj is None:
+            log_obj = sys.stdout
+
+        if resolver:
+            self.logger = logger.resolving_logger(resolver, log_obj)
+        else:
+            self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+        l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+        self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+    def handle_accept(self):
+        """Accept a socket and dispatch a channel to handle it."""
+        conn, addr = self.accept()
+        self.total_sessions.increment()
+        self.log_info('Connection from %s:%d' % addr)
+        self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+    """TLS protocol negotiation mixin for FTP/TLS."""
+
+    def tls_init(self, sock, ssl_ctx, client_addr):
+        """Perform TLS protocol negotiation."""
+        self.ssl_ctx = ssl_ctx
+        self.client_addr = client_addr
+        self._ssl_handshaking = 1
+        self._ssl_handshake_ok = 0
+        if sock:
+            self.socket = SSL.Connection(self.ssl_ctx, sock)
+            self.socket.setup_addr(self.client_addr)
+            self.socket.setup_ssl()
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+            self.add_channel()
+        # else the client hasn't connected yet; when that happens,
+        # handle_connect() will be triggered.
+
+    def tls_neg_ok(self):
+        """Return status of TLS protocol negotiation."""
+        if self._ssl_handshaking:
+            self._ssl_handshake_ok = self.socket.accept_ssl()
+            if self._ssl_handshake_ok:
+                self._ssl_handshaking = 0
+        return self._ssl_handshake_ok
+
+    def handle_connect(self):
+        """Handle a data connection that occurs after this instance came 
+        into being. When this handler is triggered, self.socket has been 
+        created and refers to the underlying connected socket."""
+        self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+        self.socket.setup_addr(self.client_addr)
+        self.socket.setup_ssl()
+        self._ssl_handshake_ok = self.socket.accept_ssl()
+        if self._ssl_handshake_ok:
+            self._ssl_handshaking = 0
+        self.add_channel()
+
+    def send(self, data):
+        """Send data over SSL."""
+        try:
+            result = self.socket.send(data)
+            if result <= 0:
+                return 0
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), what))
+            return 0
+
+    def recv(self, buffer_size):
+        """Receive data over SSL."""
+        try:
+            result = self.socket.recv(buffer_size)
+            if not result:
+                return ''
+            else:
+                return result
+        except SSL.SSLError, what:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), what))
+            return ''
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+    """TLS driver for a send-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+        """Initialise the driver."""
+        ftp_server.xmit_channel.__init__(self, channel, client_addr)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def readable(self):
+        """This channel is readable iff TLS negotiation is in progress.
+        (Which implies a connected channel, of course.)"""
+        if not self.connected:
+            return 0
+        else:
+            return self._ssl_handshaking
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress
+        or the application has data to send."""
+        if self._ssl_handshaking:
+            return 1
+        else:
+            return ftp_server.xmit_channel.writable(self)
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.xmit_channel.handle_write(self) 
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+    
+    """TLS driver for a receive-only data connection."""
+
+    def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+        """Initialise the driver."""
+        ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+        self.tls_init(conn, ssl_ctx, client_addr)
+
+    def writable(self):
+        """This channel is writable iff TLS negotiation is in progress."""
+        return self._ssl_handshaking
+
+    def handle_read(self):
+        """Handle a read event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_read(self) 
+
+    def handle_write(self):
+        """Handle a write event: either continue with TLS negotiation
+        or let the application handle this event."""
+        if self.tls_neg_ok():
+            ftp_server.recv_channel.handle_write(self) 
+
+
diff --git a/demo/medusa054/http_date.py b/demo/medusa054/http_date.py
new file mode 100644 (file)
index 0000000..d2f90b8
--- /dev/null
@@ -0,0 +1,126 @@
+# -*- Mode: Python -*-
+
+import re
+import string
+import time
+
+def concat (*args):
+    return ''.join (args)
+
+def join (seq, field=' '):
+    return field.join (seq)
+
+def group (s):
+    return '(' + s + ')'
+
+short_days = ['sun','mon','tue','wed','thu','fri','sat']
+long_days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
+
+short_day_reg = group (join (short_days, '|'))
+long_day_reg = group (join (long_days, '|'))
+
+daymap = {}
+for i in range(7):
+    daymap[short_days[i]] = i
+    daymap[long_days[i]] = i
+
+hms_reg = join (3 * [group('[0-9][0-9]')], ':')
+
+months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
+
+monmap = {}
+for i in range(12):
+    monmap[months[i]] = i+1
+
+months_reg = group (join (months, '|'))
+
+# From draft-ietf-http-v11-spec-07.txt/3.3.1
+#       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+#       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+#       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+
+# rfc822 format
+rfc822_date = join (
+        [concat (short_day_reg,','),    # day
+         group('[0-9][0-9]?'),                  # date
+         months_reg,                                    # month
+         group('[0-9]+'),                               # year
+         hms_reg,                                               # hour minute second
+         'gmt'
+         ],
+        ' '
+        )
+
+rfc822_reg = re.compile (rfc822_date)
+
+def unpack_rfc822 (m):
+    g = m.group
+    a = string.atoi
+    return (
+            a(g(4)),                # year
+            monmap[g(3)],   # month
+            a(g(2)),                # day
+            a(g(5)),                # hour
+            a(g(6)),                # minute
+            a(g(7)),                # second
+            0,
+            0,
+            0
+            )
+
+# rfc850 format
+rfc850_date = join (
+        [concat (long_day_reg,','),
+         join (
+                 [group ('[0-9][0-9]?'),
+                  months_reg,
+                  group ('[0-9]+')
+                  ],
+                 '-'
+                 ),
+         hms_reg,
+         'gmt'
+         ],
+        ' '
+        )
+
+rfc850_reg = re.compile (rfc850_date)
+# they actually unpack the same way
+def unpack_rfc850 (m):
+    g = m.group
+    a = string.atoi
+    return (
+            a(g(4)),                # year
+            monmap[g(3)],   # month
+            a(g(2)),                # day
+            a(g(5)),                # hour
+            a(g(6)),                # minute
+            a(g(7)),                # second
+            0,
+            0,
+            0
+            )
+
+# parsdate.parsedate    - ~700/sec.
+# parse_http_date       - ~1333/sec.
+
+def build_http_date (when):
+    return time.strftime ('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(when))
+
+def parse_http_date (d):
+    d = string.lower (d)
+    tz = time.timezone
+    m = rfc850_reg.match (d)
+    if m and m.end() == len(d):
+        retval = int (time.mktime (unpack_rfc850(m)) - tz)
+    else:
+        m = rfc822_reg.match (d)
+        if m and m.end() == len(d):
+            retval = int (time.mktime (unpack_rfc822(m)) - tz)
+        else:
+            return 0
+    # Thanks to Craig Silverstein <csilvers@google.com> for pointing
+    # out the DST discrepancy
+    if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
+        retval = retval + (tz - time.altzone)
+    return retval
diff --git a/demo/medusa054/http_server.py b/demo/medusa054/http_server.py
new file mode 100644 (file)
index 0000000..8502add
--- /dev/null
@@ -0,0 +1,749 @@
+#! /usr/local/bin/python
+# -*- Mode: Python -*-
+#
+#       Author: Sam Rushing <rushing@nightmare.com>
+#       Copyright 1996-2000 by Sam Rushing
+#                                                All Rights Reserved.
+#
+
+# python modules
+import os
+import re
+import socket
+import string
+import sys
+import time
+
+# async modules
+import asyncore
+import asynchat
+
+# medusa modules
+import http_date
+import producers
+import status_handler
+import logger
+
+VERSION_STRING = '1.1'
+
+from counter import counter
+from urllib import unquote, splitquery
+
+# ===========================================================================
+#                                                       Request Object
+# ===========================================================================
+
+class http_request:
+
+    # default reply code
+    reply_code = 200
+
+    request_counter = counter()
+
+    # Whether to automatically use chunked encoding when
+    #
+    #   HTTP version is 1.1
+    #   Content-Length is not set
+    #   Chunked encoding is not already in effect
+    #
+    # If your clients are having trouble, you might want to disable this.
+    use_chunked = 1
+
+    # by default, this request object ignores user data.
+    collector = None
+
+    def __init__ (self, *args):
+        # unpack information about the request
+        (self.channel, self.request,
+         self.command, self.uri, self.version,
+         self.header) = args
+
+        self.outgoing = []
+        self.reply_headers = {
+                'Server'        : 'Medusa/%s' % VERSION_STRING,
+                'Date'          : http_date.build_http_date (time.time())
+                }
+        self.request_number = http_request.request_counter.increment()
+        self._split_uri = None
+        self._header_cache = {}
+
+    # --------------------------------------------------
+    # reply header management
+    # --------------------------------------------------
+    def __setitem__ (self, key, value):
+        self.reply_headers[key] = value
+
+    def __getitem__ (self, key):
+        return self.reply_headers[key]
+
+    def has_key (self, key):
+        return self.reply_headers.has_key (key)
+
+    def build_reply_header (self):
+        return string.join (
+                [self.response(self.reply_code)] + map (
+                        lambda x: '%s: %s' % x,
+                        self.reply_headers.items()
+                        ),
+                '\r\n'
+                ) + '\r\n\r\n'
+
+    # --------------------------------------------------
+    # split a uri
+    # --------------------------------------------------
+
+    # <path>;<params>?<query>#<fragment>
+    path_regex = re.compile (
+    #      path      params    query   fragment
+            r'([^;?#]*)(;[^?#]*)?(\?[^#]*)?(#.*)?'
+            )
+
+    def split_uri (self):
+        if self._split_uri is None:
+            m = self.path_regex.match (self.uri)
+            if m.end() != len(self.uri):
+                raise ValueError, "Broken URI"
+            else:
+                self._split_uri = m.groups()
+        return self._split_uri
+
+    def get_header_with_regex (self, head_reg, group):
+        for line in self.header:
+            m = head_reg.match (line)
+            if m.end() == len(line):
+                return m.group (group)
+        return ''
+
+    def get_header (self, header):
+        header = string.lower (header)
+        hc = self._header_cache
+        if not hc.has_key (header):
+            h = header + ': '
+            hl = len(h)
+            for line in self.header:
+                if string.lower (line[:hl]) == h:
+                    r = line[hl:]
+                    hc[header] = r
+                    return r
+            hc[header] = None
+            return None
+        else:
+            return hc[header]
+
+    # --------------------------------------------------
+    # user data
+    # --------------------------------------------------
+
+    def collect_incoming_data (self, data):
+        if self.collector:
+            self.collector.collect_incoming_data (data)
+        else:
+            self.log_info(
+                    'Dropping %d bytes of incoming request data' % len(data),
+                    'warning'
+                    )
+
+    def found_terminator (self):
+        if self.collector:
+            self.collector.found_terminator()
+        else:
+            self.log_info (
+                    'Unexpected end-of-record for incoming request',
+                    'warning'
+                    )
+
+    def push (self, thing):
+        if type(thing) == type(''):
+            self.outgoing.append(producers.simple_producer (thing))
+        else:
+            self.outgoing.append(thing)
+
+    def response (self, code=200):
+        message = self.responses[code]
+        self.reply_code = code
+        return 'HTTP/%s %d %s' % (self.version, code, message)
+
+    def error (self, code):
+        self.reply_code = code
+        message = self.responses[code]
+        s = self.DEFAULT_ERROR_MESSAGE % {
+                'code': code,
+                'message': message,
+                }
+        self['Content-Length'] = len(s)
+        self['Content-Type'] = 'text/html'
+        # make an error reply
+        self.push (s)
+        self.done()
+
+    # can also be used for empty replies
+    reply_now = error
+
+    def done (self):
+        "finalize this transaction - send output to the http channel"
+
+        # ----------------------------------------
+        # persistent connection management
+        # ----------------------------------------
+
+        #  --- BUCKLE UP! ----
+
+        connection = string.lower (get_header (CONNECTION, self.header))
+
+        close_it = 0
+        wrap_in_chunking = 0
+
+        if self.version == '1.0':
+            if connection == 'keep-alive':
+                if not self.has_key ('Content-Length'):
+                    close_it = 1
+                else:
+                    self['Connection'] = 'Keep-Alive'
+            else:
+                close_it = 1
+        elif self.version == '1.1':
+            if connection == 'close':
+                close_it = 1
+            elif not self.has_key ('Content-Length'):
+                if self.has_key ('Transfer-Encoding'):
+                    if not self['Transfer-Encoding'] == 'chunked':
+                        close_it = 1
+                elif self.use_chunked:
+                    self['Transfer-Encoding'] = 'chunked'
+                    wrap_in_chunking = 1
+                else:
+                    close_it = 1
+        elif self.version is None:
+            # Although we don't *really* support http/0.9 (because we'd have to
+            # use \r\n as a terminator, and it would just yuck up a lot of stuff)
+            # it's very common for developers to not want to type a version number
+            # when using telnet to debug a server.
+            close_it = 1
+
+        outgoing_header = producers.simple_producer (self.build_reply_header())
+
+        if close_it:
+            self['Connection'] = 'close'
+
+        if wrap_in_chunking:
+            outgoing_producer = producers.chunked_producer (
+                    producers.composite_producer (self.outgoing)
+                    )
+            # prepend the header
+            outgoing_producer = producers.composite_producer(
+                [outgoing_header, outgoing_producer]
+                )
+        else:
+            # prepend the header
+            self.outgoing.insert(0, outgoing_header)
+            outgoing_producer = producers.composite_producer (self.outgoing)
+
+        # apply a few final transformations to the output
+        self.channel.push_with_producer (
+                # globbing gives us large packets
+                producers.globbing_producer (
+                        # hooking lets us log the number of bytes sent
+                        producers.hooked_producer (
+                                outgoing_producer,
+                                self.log
+                                )
+                        )
+                )
+
+        self.channel.current_request = None
+
+        if close_it:
+            self.channel.close_when_done()
+
+    def log_date_string (self, when):
+        gmt = time.gmtime(when)
+        if time.daylight and gmt[8]:
+            tz = time.altzone
+        else:
+            tz = time.timezone
+        if tz > 0:
+            neg = 1
+        else:
+            neg = 0
+            tz = -tz
+        h, rem = divmod (tz, 3600)
+        m, rem = divmod (rem, 60)
+        if neg:
+            offset = '-%02d%02d' % (h, m)
+        else:
+            offset = '+%02d%02d' % (h, m)
+
+        return time.strftime ( '%d/%b/%Y:%H:%M:%S ', gmt) + offset
+
+    def log (self, bytes):
+        self.channel.server.logger.log (
+                self.channel.addr[0],
+                '%d - - [%s] "%s" %d %d\n' % (
+                        self.channel.addr[1],
+                        self.log_date_string (time.time()),
+                        self.request,
+                        self.reply_code,
+                        bytes
+                        )
+                )
+
+    responses = {
+            100: "Continue",
+            101: "Switching Protocols",
+            200: "OK",
+            201: "Created",
+            202: "Accepted",
+            203: "Non-Authoritative Information",
+            204: "No Content",
+            205: "Reset Content",
+            206: "Partial Content",
+            300: "Multiple Choices",
+            301: "Moved Permanently",
+            302: "Moved Temporarily",
+            303: "See Other",
+            304: "Not Modified",
+            305: "Use Proxy",
+            400: "Bad Request",
+            401: "Unauthorized",
+            402: "Payment Required",
+            403: "Forbidden",
+            404: "Not Found",
+            405: "Method Not Allowed",
+            406: "Not Acceptable",
+            407: "Proxy Authentication Required",
+            408: "Request Time-out",
+            409: "Conflict",
+            410: "Gone",
+            411: "Length Required",
+            412: "Precondition Failed",
+            413: "Request Entity Too Large",
+            414: "Request-URI Too Large",
+            415: "Unsupported Media Type",
+            500: "Internal Server Error",
+            501: "Not Implemented",
+            502: "Bad Gateway",
+            503: "Service Unavailable",
+            504: "Gateway Time-out",
+            505: "HTTP Version not supported"
+            }
+
+    # Default error message
+    DEFAULT_ERROR_MESSAGE = string.join (
+            ['<head>',
+             '<title>Error response</title>',
+             '</head>',
+             '<body>',
+             '<h1>Error response</h1>',
+             '<p>Error code %(code)d.',
+             '<p>Message: %(message)s.',
+             '</body>',
+             ''
+             ],
+            '\r\n'
+            )
+
+
+# ===========================================================================
+#                                                HTTP Channel Object
+# ===========================================================================
+
+class http_channel (asynchat.async_chat):
+
+    # use a larger default output buffer
+    ac_out_buffer_size = 1<<16
+
+    current_request = None
+    channel_counter = counter()
+
+    def __init__ (self, server, conn, addr):
+        self.channel_number = http_channel.channel_counter.increment()
+        self.request_counter = counter()
+        asynchat.async_chat.__init__ (self, conn)
+        self.server = server
+        self.addr = addr
+        self.set_terminator ('\r\n\r\n')
+        self.in_buffer = ''
+        self.creation_time = int (time.time())
+        self.check_maintenance()
+
+    def __repr__ (self):
+        ar = asynchat.async_chat.__repr__(self)[1:-1]
+        return '<%s channel#: %s requests:%s>' % (
+                ar,
+                self.channel_number,
+                self.request_counter
+                )
+
+    # Channel Counter, Maintenance Interval...
+    maintenance_interval = 500
+
+    def check_maintenance (self):
+        if not self.channel_number % self.maintenance_interval:
+            self.maintenance()
+
+    def maintenance (self):
+        self.kill_zombies()
+
+    # 30-minute zombie timeout.  status_handler also knows how to kill zombies.
+    zombie_timeout = 30 * 60
+
+    def kill_zombies (self):
+        now = int (time.time())
+        for channel in asyncore.socket_map.values():
+            if channel.__class__ == self.__class__:
+                if (now - channel.creation_time) > channel.zombie_timeout:
+                    channel.close()
+
+    # --------------------------------------------------
+    # send/recv overrides, good place for instrumentation.
+    # --------------------------------------------------
+
+    # this information needs to get into the request object,
+    # so that it may log correctly.
+    def send (self, data):
+        result = asynchat.async_chat.send (self, data)
+        self.server.bytes_out.increment (len(data))
+        return result
+
+    def recv (self, buffer_size):
+        try:
+            result = asynchat.async_chat.recv (self, buffer_size)
+            self.server.bytes_in.increment (len(result))
+            return result
+        except MemoryError:
+            # --- Save a Trip to Your Service Provider ---
+            # It's possible for a process to eat up all the memory of
+            # the machine, and put it in an extremely wedged state,
+            # where medusa keeps running and can't be shut down.  This
+            # is where MemoryError tends to get thrown, though of
+            # course it could get thrown elsewhere.
+            sys.exit ("Out of Memory!")
+
+    def handle_error (self):
+        t, v = sys.exc_info()[:2]
+        if t is SystemExit:
+            raise t, v
+        else:
+            asynchat.async_chat.handle_error (self)
+
+    def log (self, *args):
+        pass
+
+    # --------------------------------------------------
+    # async_chat methods
+    # --------------------------------------------------
+
+    def collect_incoming_data (self, data):
+        if self.current_request:
+            # we are receiving data (probably POST data) for a request
+            self.current_request.collect_incoming_data (data)
+        else:
+            # we are receiving header (request) data
+            self.in_buffer = self.in_buffer + data
+
+    def found_terminator (self):
+        if self.current_request:
+            self.current_request.found_terminator()
+        else:
+            header = self.in_buffer
+            self.in_buffer = ''
+            lines = string.split (header, '\r\n')
+
+            # --------------------------------------------------
+            # crack the request header
+            # --------------------------------------------------
+
+            while lines and not lines[0]:
+                # as per the suggestion of http-1.1 section 4.1, (and
+                # Eric Parker <eparker@zyvex.com>), ignore a leading
+                # blank lines (buggy browsers tack it onto the end of
+                # POST requests)
+                lines = lines[1:]
+
+            if not lines:
+                self.close_when_done()
+                return
+
+            request = lines[0]
+
+            command, uri, version = crack_request (request)
+            header = join_headers (lines[1:])
+
+            # unquote path if necessary (thanks to Skip Montanaro for pointing
+            # out that we must unquote in piecemeal fashion).
+            rpath, rquery = splitquery(uri)
+            if '%' in rpath:
+                if rquery:
+                    uri = unquote (rpath) + '?' + rquery
+                else:
+                    uri = unquote (rpath)
+
+            r = http_request (self, request, command, uri, version, header)
+            self.request_counter.increment()
+            self.server.total_requests.increment()
+
+            if command is None:
+                self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
+                r.error (400)
+                return
+
+            # --------------------------------------------------
+            # handler selection and dispatch
+            # --------------------------------------------------
+            for h in self.server.handlers:
+                if h.match (r):
+                    try:
+                        self.current_request = r
+                        # This isn't used anywhere.
+                        # r.handler = h # CYCLE
+                        h.handle_request (r)
+                    except:
+                        self.server.exceptions.increment()
+                        (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
+                        self.log_info(
+                                        'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
+                                        'error')
+                        try:
+                            r.error (500)
+                        except:
+                            pass
+                    return
+
+            # no handlers, so complain
+            r.error (404)
+
+    def writable_for_proxy (self):
+        # this version of writable supports the idea of a 'stalled' producer
+        # [i.e., it's not ready to produce any output yet] This is needed by
+        # the proxy, which will be waiting for the magic combination of
+        # 1) hostname resolved
+        # 2) connection made
+        # 3) data available.
+        if self.ac_out_buffer:
+            return 1
+        elif len(self.producer_fifo):
+            p = self.producer_fifo.first()
+            if hasattr (p, 'stalled'):
+                return not p.stalled()
+            else:
+                return 1
+
+# ===========================================================================
+#                                                HTTP Server Object
+# ===========================================================================
+
+class http_server (asyncore.dispatcher):
+
+    SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING
+
+    channel_class = http_channel
+
+    def __init__ (self, ip, port, resolver=None, logger_object=None):
+        self.ip = ip
+        self.port = port
+        asyncore.dispatcher.__init__ (self)
+        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+        self.handlers = []
+
+        if not logger_object:
+            logger_object = logger.file_logger (sys.stdout)
+
+        self.set_reuse_addr()
+        self.bind ((ip, port))
+
+        # lower this to 5 if your OS complains
+        self.listen (1024)
+
+        host, port = self.socket.getsockname()
+        if not ip:
+            self.log_info('Computing default hostname', 'warning')
+            ip = socket.gethostbyname (socket.gethostname())
+        try:
+            self.server_name = socket.gethostbyaddr (ip)[0]
+        except socket.error:
+            self.log_info('Cannot do reverse lookup', 'warning')
+            self.server_name = ip       # use the IP address as the "hostname"
+
+        self.server_port = port
+        self.total_clients = counter()
+        self.total_requests = counter()
+        self.exceptions = counter()
+        self.bytes_out = counter()
+        self.bytes_in  = counter()
+
+        if not logger_object:
+            logger_object = logger.file_logger (sys.stdout)
+
+        if resolver:
+            self.logger = logger.resolving_logger (resolver, logger_object)
+        else:
+            self.logger = logger.unresolving_logger (logger_object)
+
+        self.log_info (
+                'Medusa (V%s) started at %s'
+                '\n\tHostname: %s'
+                '\n\tPort:%d'
+                '\n' % (
+                        VERSION_STRING,
+                        time.ctime(time.time()),
+                        self.server_name,
+                        port,
+                        )
+                )
+
+    def writable (self):
+        return 0
+
+    def handle_read (self):
+        pass
+
+    def readable (self):
+        return self.accepting
+
+    def handle_connect (self):
+        pass
+
+    def handle_accept (self):
+        self.total_clients.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            # linux: on rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            self.log_info ('warning: server accept() threw an exception', 'warning')
+            return
+        except TypeError:
+            # unpack non-sequence.  this can happen when a read event
+            # fires on a listening socket, but when we call accept()
+            # we get EWOULDBLOCK, so dispatcher.accept() returns None.
+            # Seen on FreeBSD3.
+            self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
+            return
+
+        self.channel_class (self, conn, addr)
+
+    def install_handler (self, handler, back=0):
+        if back:
+            self.handlers.append (handler)
+        else:
+            self.handlers.insert (0, handler)
+
+    def remove_handler (self, handler):
+        self.handlers.remove (handler)
+
+    def status (self):
+        def nice_bytes (n):
+            return string.join (status_handler.english_bytes (n))
+
+        handler_stats = filter (None, map (maybe_status, self.handlers))
+
+        if self.total_clients:
+            ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
+        else:
+            ratio = 0.0
+
+        return producers.composite_producer (
+                [producers.lines_producer (
+                        ['<h2>%s</h2>'                                                  % self.SERVER_IDENT,
+                        '<br>Listening on: <b>Host:</b> %s'             % self.server_name,
+                        '<b>Port:</b> %d'                                               % self.port,
+                         '<p><ul>'
+                         '<li>Total <b>Clients:</b> %s'                 % self.total_clients,
+                         '<b>Requests:</b> %s'                                  % self.total_requests,
+                         '<b>Requests/Client:</b> %.1f'                 % (ratio),
+                         '<li>Total <b>Bytes In:</b> %s'        % (nice_bytes (self.bytes_in.as_long())),
+                         '<b>Bytes Out:</b> %s'                         % (nice_bytes (self.bytes_out.as_long())),
+                         '<li>Total <b>Exceptions:</b> %s'              % self.exceptions,
+                         '</ul><p>'
+                         '<b>Extension List</b><ul>',
+                         ])] + handler_stats + [producers.simple_producer('</ul>')]
+                )
+
+def maybe_status (thing):
+    if hasattr (thing, 'status'):
+        return thing.status()
+    else:
+        return None
+
+CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
+
+# merge multi-line headers
+# [486dx2: ~500/sec]
+def join_headers (headers):
+    r = []
+    for i in range(len(headers)):
+        if headers[i][0] in ' \t':
+            r[-1] = r[-1] + headers[i][1:]
+        else:
+            r.append (headers[i])
+    return r
+
+def get_header (head_reg, lines, group=1):
+    for line in lines:
+        m = head_reg.match (line)
+        if m and m.end() == len(line):
+            return m.group (group)
+    return ''
+
+def get_header_match (head_reg, lines):
+    for line in lines:
+        m = head_reg.match (line)
+        if m and m.end() == len(line):
+            return m
+    return ''
+
+REQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')
+
+def crack_request (r):
+    m = REQUEST.match (r)
+    if m and m.end() == len(r):
+        if m.group(3):
+            version = m.group(5)
+        else:
+            version = None
+        return m.group(1), m.group(2), version
+    else:
+        return None, None, None
+
+if __name__ == '__main__':
+    import sys
+    if len(sys.argv) < 2:
+        print 'usage: %s <root> <port>' % (sys.argv[0])
+    else:
+        import monitor
+        import filesys
+        import default_handler
+        import status_handler
+        import ftp_server
+        import chat_server
+        import resolver
+        import logger
+        rs = resolver.caching_resolver ('127.0.0.1')
+        lg = logger.file_logger (sys.stdout)
+        ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
+        fs = filesys.os_filesystem (sys.argv[1])
+        dh = default_handler.default_handler (fs)
+        hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
+        hs.install_handler (dh)
+        ftp = ftp_server.ftp_server (
+                ftp_server.dummy_authorizer(sys.argv[1]),
+                port=8021,
+                resolver=rs,
+                logger_object=lg
+                )
+        cs = chat_server.chat_server ('', 7777)
+        sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
+        hs.install_handler (sh)
+        if ('-p' in sys.argv):
+            def profile_loop ():
+                try:
+                    asyncore.loop()
+                except KeyboardInterrupt:
+                    pass
+            import profile
+            profile.run ('profile_loop()', 'profile.out')
+        else:
+            asyncore.loop()
diff --git a/demo/medusa054/https_server.py b/demo/medusa054/https_server.py
new file mode 100644 (file)
index 0000000..f30bc86
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server. 
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+    ac_in_buffer_size = 1 << 16
+
+    def __init__(self, server, conn, addr):
+        http_server.http_channel.__init__(self, server, conn, addr)
+
+    def send(self, data):
+        try:
+            result = self.socket._write_nbio(data)
+            if result <= 0:
+                return 0
+            else:
+                self.server.bytes_out.increment(result)
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('send: closing channel %s %s' % (repr(self), why))
+            return 0
+
+    def recv(self, buffer_size):
+        try:
+            result = self.socket._read_nbio(buffer_size)
+            if result is None:
+                return ''
+            elif result == '':
+                self.close()
+                return ''
+            else:
+                self.server.bytes_in.increment(len(result))
+                return result
+        except SSL.SSLError, why:
+            self.close()
+            self.log_info('recv: closing channel %s %s' % (repr(self), why))
+            return ''
+
+
+class https_server(http_server.http_server):
+
+    SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+    channel_class=https_channel
+
+    def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+        http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+        sys.stdout.write(self.SERVER_IDENT + '\n\n')
+        sys.stdout.flush()
+        self.ssl_ctx=ssl_ctx
+        
+    def handle_accept(self):
+        # Cribbed from http_server.
+        self.total_clients.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            # linux: on rare occasions we get a bogus socket back from
+            # accept.  socketmodule.c:makesockaddr complains that the
+            # address family is unknown.  We don't want the whole server
+            # to shut down because of this.
+            sys.stderr.write ('warning: server accept() threw an exception\n')
+            return
+
+        # Turn the vanilla socket into an SSL connection.
+        try:
+            ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+            ssl_conn._setup_ssl(addr)
+            ssl_conn.accept_ssl()
+            self.channel_class(self, ssl_conn, addr)
+        except SSL.SSLError:
+            pass
+
+    def writeable(self):
+        return 0
+
diff --git a/demo/medusa054/index.html b/demo/medusa054/index.html
new file mode 100644 (file)
index 0000000..0f7de19
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+<title>M2Crypto HTTPS Server</title>
+<body>
+<h1>M2Crypto HTTPS Server - It works!</h1>
+</body>
+</html>
diff --git a/demo/medusa054/logger.py b/demo/medusa054/logger.py
new file mode 100644 (file)
index 0000000..d131ab5
--- /dev/null
@@ -0,0 +1,261 @@
+# -*- Mode: Python -*-
+
+import asynchat
+import socket
+import time         # these three are for the rotating logger
+import os           # |
+import stat         # v
+
+#
+# three types of log:
+# 1) file
+#    with optional flushing.  Also, one that rotates the log.
+# 2) socket
+#    dump output directly to a socket connection. [how do we
+#    keep it open?]
+# 3) syslog
+#    log to syslog via tcp.  this is a per-line protocol.
+#
+
+#
+# The 'standard' interface to a logging object is simply
+# log_object.log (message)
+#
+
+# a file-like object that captures output, and
+# makes sure to flush it always...  this could
+# be connected to:
+#  o    stdio file
+#  o    low-level file
+#  o    socket channel
+#  o    syslog output...
+
+class file_logger:
+
+    # pass this either a path or a file object.
+    def __init__ (self, file, flush=1, mode='a'):
+        if type(file) == type(''):
+            if (file == '-'):
+                import sys
+                self.file = sys.stdout
+            else:
+                self.file = open (file, mode)
+        else:
+            self.file = file
+        self.do_flush = flush
+
+    def __repr__ (self):
+        return '<file logger: %s>' % self.file
+
+    def write (self, data):
+        self.file.write (data)
+        self.maybe_flush()
+
+    def writeline (self, line):
+        self.file.writeline (line)
+        self.maybe_flush()
+
+    def writelines (self, lines):
+        self.file.writelines (lines)
+        self.maybe_flush()
+
+    def maybe_flush (self):
+        if self.do_flush:
+            self.file.flush()
+
+    def flush (self):
+        self.file.flush()
+
+    def softspace (self, *args):
+        pass
+
+    def log (self, message):
+        if message[-1] not in ('\r', '\n'):
+            self.write (message + '\n')
+        else:
+            self.write (message)
+
+# like a file_logger, but it must be attached to a filename.
+# When the log gets too full, or a certain time has passed,
+# it backs up the log and starts a new one.  Note that backing
+# up the log is done via "mv" because anything else (cp, gzip)
+# would take time, during which medusa would do nothing else.
+
+class rotating_file_logger (file_logger):
+
+    # If freq is non-None we back up "daily", "weekly", or "monthly".
+    # Else if maxsize is non-None we back up whenever the log gets
+    # to big.  If both are None we never back up.
+    def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
+        self.filename = file
+        self.mode = mode
+        self.file = open (file, mode)
+        self.freq = freq
+        self.maxsize = maxsize
+        self.rotate_when = self.next_backup(self.freq)
+        self.do_flush = flush
+
+    def __repr__ (self):
+        return '<rotating-file logger: %s>' % self.file
+
+    # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
+    def next_backup (self, freq):
+        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+        if freq == 'daily':
+            return time.mktime((yr,mo,day+1, 0,0,0, 0,0,-1))
+        elif freq == 'weekly':
+            return time.mktime((yr,mo,day-wd+7, 0,0,0, 0,0,-1)) # wd(monday)==0
+        elif freq == 'monthly':
+            return time.mktime((yr,mo+1,1, 0,0,0, 0,0,-1))
+        else:
+            return None                  # not a date-based backup
+
+    def maybe_flush (self):              # rotate first if necessary
+        self.maybe_rotate()
+        if self.do_flush:                # from file_logger()
+            self.file.flush()
+
+    def maybe_rotate (self):
+        if self.freq and time.time() > self.rotate_when:
+            self.rotate()
+            self.rotate_when = self.next_backup(self.freq)
+        elif self.maxsize:               # rotate when we get too big
+            try:
+                if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
+                    self.rotate()
+            except os.error:             # file not found, probably
+                self.rotate()            # will create a new file
+
+    def rotate (self):
+        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+        try:
+            self.file.close()
+            newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
+            try:
+                open(newname, "r").close()      # check if file exists
+                newname = newname + "-%02d%02d%02d" % (hr, min, sec)
+            except:                             # YEARMODY is unique
+                pass
+            os.rename(self.filename, newname)
+            self.file = open(self.filename, self.mode)
+        except:
+            pass
+
+# syslog is a line-oriented log protocol - this class would be
+# appropriate for FTP or HTTP logs, but not for dumping stderr to.
+
+# TODO: a simple safety wrapper that will ensure that the line sent
+# to syslog is reasonable.
+
+# TODO: async version of syslog_client: now, log entries use blocking
+# send()
+
+import m_syslog
+syslog_logger = m_syslog.syslog_client
+
+class syslog_logger (m_syslog.syslog_client):
+    def __init__ (self, address, facility='user'):
+        m_syslog.syslog_client.__init__ (self, address)
+        self.facility = m_syslog.facility_names[facility]
+        self.address=address
+
+    def __repr__ (self):
+        return '<syslog logger address=%s>' % (repr(self.address))
+
+    def log (self, message):
+        m_syslog.syslog_client.log (
+                self,
+                message,
+                facility=self.facility,
+                priority=m_syslog.LOG_INFO
+                )
+
+# log to a stream socket, asynchronously
+
+class socket_logger (asynchat.async_chat):
+
+    def __init__ (self, address):
+
+        if type(address) == type(''):
+            self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
+        else:
+            self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+        self.connect (address)
+        self.address = address
+
+    def __repr__ (self):
+        return '<socket logger: address=%s>' % (self.address)
+
+    def log (self, message):
+        if message[-2:] != '\r\n':
+            self.socket.push (message + '\r\n')
+        else:
+            self.socket.push (message)
+
+# log to multiple places
+class multi_logger:
+    def __init__ (self, loggers):
+        self.loggers = loggers
+
+    def __repr__ (self):
+        return '<multi logger: %s>' % (repr(self.loggers))
+
+    def log (self, message):
+        for logger in self.loggers:
+            logger.log (message)
+
+class resolving_logger:
+    """Feed (ip, message) combinations into this logger to get a
+    resolved hostname in front of the message.  The message will not
+    be logged until the PTR request finishes (or fails)."""
+
+    def __init__ (self, resolver, logger):
+        self.resolver = resolver
+        self.logger = logger
+
+    class logger_thunk:
+        def __init__ (self, message, logger):
+            self.message = message
+            self.logger = logger
+
+        def __call__ (self, host, ttl, answer):
+            if not answer:
+                answer = host
+            self.logger.log ('%s:%s' % (answer, self.message))
+
+    def log (self, ip, message):
+        self.resolver.resolve_ptr (
+                ip,
+                self.logger_thunk (
+                        message,
+                        self.logger
+                        )
+                )
+
+class unresolving_logger:
+    "Just in case you don't want to resolve"
+    def __init__ (self, logger):
+        self.logger = logger
+
+    def log (self, ip, message):
+        self.logger.log ('%s:%s' % (ip, message))
+
+
+def strip_eol (line):
+    while line and line[-1] in '\r\n':
+        line = line[:-1]
+    return line
+
+class tail_logger:
+    "Keep track of the last <size> log messages"
+    def __init__ (self, logger, size=500):
+        self.size = size
+        self.logger = logger
+        self.messages = []
+
+    def log (self, message):
+        self.messages.append (strip_eol (message))
+        if len (self.messages) > self.size:
+            del self.messages[0]
+        self.logger.log (message)
diff --git a/demo/medusa054/m_syslog.py b/demo/medusa054/m_syslog.py
new file mode 100644 (file)
index 0000000..e212766
--- /dev/null
@@ -0,0 +1,182 @@
+# -*- Mode: Python -*-
+
+# ======================================================================
+# Copyright 1997 by Sam Rushing
+#
+#                         All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""socket interface to unix syslog.
+On Unix, there are usually two ways of getting to syslog: via a
+local unix-domain socket, or via the TCP service.
+
+Usually "/dev/log" is the unix domain socket.  This may be different
+for other systems.
+
+>>> my_client = syslog_client ('/dev/log')
+
+Otherwise, just use the UDP version, port 514.
+
+>>> my_client = syslog_client (('my_log_host', 514))
+
+On win32, you will have to use the UDP version.  Note that
+you can use this to log to other hosts (and indeed, multiple
+hosts).
+
+This module is not a drop-in replacement for the python
+<syslog> extension module - the interface is different.
+
+Usage:
+
+>>> c = syslog_client()
+>>> c = syslog_client ('/strange/non_standard_log_location')
+>>> c = syslog_client (('other_host.com', 514))
+>>> c.log ('testing', facility='local0', priority='debug')
+
+"""
+
+# TODO: support named-pipe syslog.
+# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]
+
+# from <linux/sys/syslog.h>:
+# ===========================================================================
+# priorities/facilities are encoded into a single 32-bit quantity, where the
+# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
+# (0-big number).  Both the priorities and the facilities map roughly
+# one-to-one to strings in the syslogd(8) source code.  This mapping is
+# included in this file.
+#
+# priorities (these are ordered)
+
+LOG_EMERG               = 0             #  system is unusable
+LOG_ALERT               = 1             #  action must be taken immediately
+LOG_CRIT                = 2             #  critical conditions
+LOG_ERR                 = 3             #  error conditions
+LOG_WARNING             = 4             #  warning conditions
+LOG_NOTICE              = 5             #  normal but significant condition
+LOG_INFO                = 6             #  informational
+LOG_DEBUG               = 7             #  debug-level messages
+
+#  facility codes
+LOG_KERN                = 0             #  kernel messages
+LOG_USER                = 1             #  random user-level messages
+LOG_MAIL                = 2             #  mail system
+LOG_DAEMON              = 3             #  system daemons
+LOG_AUTH                = 4             #  security/authorization messages
+LOG_SYSLOG              = 5             #  messages generated internally by syslogd
+LOG_LPR                 = 6             #  line printer subsystem
+LOG_NEWS                = 7             #  network news subsystem
+LOG_UUCP                = 8             #  UUCP subsystem
+LOG_CRON                = 9             #  clock daemon
+LOG_AUTHPRIV    = 10    #  security/authorization messages (private)
+
+#  other codes through 15 reserved for system use
+LOG_LOCAL0              = 16            #  reserved for local use
+LOG_LOCAL1              = 17            #  reserved for local use
+LOG_LOCAL2              = 18            #  reserved for local use
+LOG_LOCAL3              = 19            #  reserved for local use
+LOG_LOCAL4              = 20            #  reserved for local use
+LOG_LOCAL5              = 21            #  reserved for local use
+LOG_LOCAL6              = 22            #  reserved for local use
+LOG_LOCAL7              = 23            #  reserved for local use
+
+priority_names = {
+        "alert":        LOG_ALERT,
+        "crit":         LOG_CRIT,
+        "debug":        LOG_DEBUG,
+        "emerg":        LOG_EMERG,
+        "err":          LOG_ERR,
+        "error":        LOG_ERR,                #  DEPRECATED
+        "info":         LOG_INFO,
+        "notice":       LOG_NOTICE,
+        "panic":        LOG_EMERG,              #  DEPRECATED
+        "warn":         LOG_WARNING,            #  DEPRECATED
+        "warning":      LOG_WARNING,
+        }
+
+facility_names = {
+        "auth":         LOG_AUTH,
+        "authpriv":     LOG_AUTHPRIV,
+        "cron":         LOG_CRON,
+        "daemon":       LOG_DAEMON,
+        "kern":         LOG_KERN,
+        "lpr":          LOG_LPR,
+        "mail":         LOG_MAIL,
+        "news":         LOG_NEWS,
+        "security":     LOG_AUTH,               #  DEPRECATED
+        "syslog":       LOG_SYSLOG,
+        "user":         LOG_USER,
+        "uucp":         LOG_UUCP,
+        "local0":       LOG_LOCAL0,
+        "local1":       LOG_LOCAL1,
+        "local2":       LOG_LOCAL2,
+        "local3":       LOG_LOCAL3,
+        "local4":       LOG_LOCAL4,
+        "local5":       LOG_LOCAL5,
+        "local6":       LOG_LOCAL6,
+        "local7":       LOG_LOCAL7,
+        }
+
+import socket
+
+class syslog_client:
+    def __init__ (self, address='/dev/log'):
+        self.address = address
+        self.stream = 0
+        if isinstance(address, type('')):
+            try:
+                self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+                self.socket.connect(address)
+            except socket.error:
+                # Some Linux installations have /dev/log
+                # a stream socket instead of a datagram socket.
+                self.socket = socket.socket (socket.AF_UNIX,
+                                             socket.SOCK_STREAM)
+                self.stream = 1
+        else:
+            self.socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
+
+    # curious: when talking to the unix-domain '/dev/log' socket, a
+    #   zero-terminator seems to be required.  this string is placed
+    #   into a class variable so that it can be overridden if
+    #   necessary.
+
+    log_format_string = '<%d>%s\000'
+
+    def log (self, message, facility=LOG_USER, priority=LOG_INFO):
+        message = self.log_format_string % (
+                self.encode_priority (facility, priority),
+                message
+                )
+        if self.stream:
+            self.socket.send (message)
+        else:
+            self.socket.sendto (message, self.address)
+
+    def encode_priority (self, facility, priority):
+        if type(facility) == type(''):
+            facility = facility_names[facility]
+        if type(priority) == type(''):
+            priority = priority_names[priority]
+        return (facility<<3) | priority
+
+    def close (self):
+        if self.stream:
+            self.socket.close()
diff --git a/demo/medusa054/medusa_gif.py b/demo/medusa054/medusa_gif.py
new file mode 100644 (file)
index 0000000..005644a
--- /dev/null
@@ -0,0 +1,8 @@
+# -*- Mode: Python -*-
+
+# the medusa icon as a python source file.
+
+width = 97
+height = 61
+
+data = 'GIF89aa\000=\000\204\000\000\000\000\000\255\255\255\245\245\245ssskkkccc111)))\326\326\326!!!\316\316\316\300\300\300\204\204\000\224\224\224\214\214\214\200\200\200RRR\377\377\377JJJ\367\367\367BBB\347\347\347\000\204\000\020\020\020\265\265\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004\001\000\000\021\000,\000\000\000\000a\000=\000\000\005\376`$\216di\236h\252\256l\353\276p,\317tm\337x\256\357|m\001@\240E\305\000\364\2164\206R)$\005\201\214\007r\012{X\255\312a\004\260\\>\026\3240\353)\224n\001W+X\334\373\231~\344.\303b\216\024\027x<\273\307\255G,rJiWN\014{S}k"?ti\013EdPQ\207G@_%\000\026yy\\\201\202\227\224<\221Fs$pOjWz\241<r@vO\236\231\233k\247M\2544\203F\177\235\236L#\247\256Z\270,\266BxJ[\276\256A]iE\304\305\262\273E\313\201\275i#\\\303\321\'h\203V\\\177\326\276\216\220P~\335\230_\264\013\342\275\344KF\233\360Q\212\352\246\000\367\274s\361\236\334\347T\341;\341\246\2202\177\3142\211`\242o\325@S\202\264\031\252\207\260\323\256\205\311\036\236\270\002\'\013\302\177\274H\010\324X\002\0176\212\037\376\321\360\032\226\207\244\2674(+^\202\346r\205J\0211\375\241Y#\256f\0127\315>\272\002\325\307g\012(\007\205\312#j\317(\012A\200\224.\241\003\346GS\247\033\245\344\264\366\015L\'PXQl]\266\263\243\232\260?\245\316\371\362\225\035\332\243J\273\332Q\263\357-D\241T\327\270\265\013W&\330\010u\371b\322IW0\214\261]\003\033Va\365Z#\207\213a\030k\2647\262\014p\354\024[n\321N\363\346\317\003\037P\000\235C\302\000\3228(\244\363YaA\005\022\255_\237@\260\000A\212\326\256qbp\321\332\266\011\334=T\023\010"!B\005\003A\010\224\020\220 H\002\337#\020 O\276E\357h\221\327\003\\\000b@v\004\351A.h\365\354\342B\002\011\257\025\\ \220\340\301\353\006\000\024\214\200pA\300\353\012\364\241k/\340\033C\202\003\000\310fZ\011\003V\240R\005\007\354\376\026A\000\000\360\'\202\177\024\004\210\003\000\305\215\360\000\000\015\220\240\332\203\027@\'\202\004\025VpA\000%\210x\321\206\032J\341\316\010\262\211H"l\333\341\200\200>"]P\002\212\011\010`\002\0066FP\200\001\'\024p]\004\027(8B\221\306]\000\201w>\002iB\001\007\340\260"v7J1\343(\257\020\251\243\011\242i\263\017\215\337\035\220\200\221\365m4d\015\016D\251\341iN\354\346Ng\253\200I\240\031\35609\245\2057\311I\302\2007t\231"&`\314\310\244\011e\226(\236\010w\212\300\234\011\012HX(\214\253\311@\001\233^\222pg{% \340\035\224&H\000\246\201\362\215`@\001"L\340\004\030\234\022\250\'\015(V:\302\235\030\240q\337\205\224\212h@\177\006\000\250\210\004\007\310\207\337\005\257-P\346\257\367]p\353\203\271\256:\203\236\211F\340\247\010\3329g\244\010\307*=A\000\203\260y\012\304s#\014\007D\207,N\007\304\265\027\021C\233\207%B\366[m\353\006\006\034j\360\306+\357\274a\204\000\000;'
diff --git a/demo/medusa054/poison_handler.py b/demo/medusa054/poison_handler.py
new file mode 100644 (file)
index 0000000..acc78ab
--- /dev/null
@@ -0,0 +1,69 @@
+
+import string
+import whrandom
+
+RESP_HEAD="""\
+<HTML><BODY BGCOLOR=\"#ffffff\">
+"""
+
+RESP_MIDDLE="""
+<h2>M2Crypto https server demonstration</h2>
+
+This web page is generated by the "poison" http request handler. 
+<br>
+The links just go on and on and on...
+<br><br>
+"""
+
+RESP_TAIL="""
+</BODY></HTML>
+"""
+
+charset='012345678/90ABCDEFGHIJKLM/NOPQRSTUVWXYZabcd/efghijklmnopqrs/tuvwxyz'
+numchar=len(charset)
+
+def makepage(numlinks):
+
+    title='<title>'
+    for u in range(whrandom.randint(3, 15)):
+        pick=whrandom.randint(0, numchar-1)
+        title=title+charset[pick]
+    title=title+'</title>'
+
+    url='\r\n'
+    numlinks=whrandom.randint(2, numlinks)
+    for i in range(numlinks): 
+        url=url+'<a href="/poison/'
+        for u in range(whrandom.randint(3, 15)):
+            pick=whrandom.randint(0, numchar-1)
+            ch=charset[pick]
+            if ch=='/' and url[-1]=='/':
+                ch=charset[pick+1]
+            url=url+ch
+        url=url+'/">'
+        for u in range(whrandom.randint(3, 15)):
+            pick=whrandom.randint(0, numchar-1)
+            url=url+charset[pick]
+        url=url+'</a><br>\r\n'
+
+    url=RESP_HEAD+title+RESP_MIDDLE+url+RESP_TAIL
+    return url
+
+
+class poison_handler:
+    """This is a clone of webpoison - every URL returns a page of URLs, each of which 
+    returns a page of URLs, each of _which_ returns a page of URLs, ad infinitum.
+    The objective is to sucker address-harvesting bots run by spammers."""
+
+    def __init__(self, numlinks=10):
+        self.numlinks = numlinks
+        self.poison_level = 0
+
+    def match(self, request):
+        return  (request.uri[:7] == '/poison')
+
+    def handle_request(self, request):
+        if request.command == 'get':
+            request.push(makepage(self.numlinks))
+        request.done()
+
diff --git a/demo/medusa054/producers.py b/demo/medusa054/producers.py
new file mode 100644 (file)
index 0000000..a04906e
--- /dev/null
@@ -0,0 +1,323 @@
+# -*- Mode: Python -*-
+
+"""
+A collection of producers.
+Each producer implements a particular feature:  They can be combined
+in various ways to get interesting and useful behaviors.
+
+For example, you can feed dynamically-produced output into the compressing
+producer, then wrap this with the 'chunked' transfer-encoding producer.
+"""
+
+import string
+from asynchat import find_prefix_at_end
+
+class simple_producer:
+    "producer for a string"
+    def __init__ (self, data, buffer_size=1024):
+        self.data = data
+        self.buffer_size = buffer_size
+
+    def more (self):
+        if len (self.data) > self.buffer_size:
+            result = self.data[:self.buffer_size]
+            self.data = self.data[self.buffer_size:]
+            return result
+        else:
+            result = self.data
+            self.data = ''
+            return result
+
+class scanning_producer:
+    "like simple_producer, but more efficient for large strings"
+    def __init__ (self, data, buffer_size=1024):
+        self.data = data
+        self.buffer_size = buffer_size
+        self.pos = 0
+
+    def more (self):
+        if self.pos < len(self.data):
+            lp = self.pos
+            rp = min (
+                    len(self.data),
+                    self.pos + self.buffer_size
+                    )
+            result = self.data[lp:rp]
+            self.pos = self.pos + len(result)
+            return result
+        else:
+            return ''
+
+class lines_producer:
+    "producer for a list of lines"
+
+    def __init__ (self, lines):
+        self.lines = lines
+
+    def more (self):
+        if self.lines:
+            chunk = self.lines[:50]
+            self.lines = self.lines[50:]
+            return string.join (chunk, '\r\n') + '\r\n'
+        else:
+            return ''
+
+class buffer_list_producer:
+    "producer for a list of strings"
+
+    # i.e., data == string.join (buffers, '')
+
+    def __init__ (self, buffers):
+
+        self.index = 0
+        self.buffers = buffers
+
+    def more (self):
+        if self.index >= len(self.buffers):
+            return ''
+        else:
+            data = self.buffers[self.index]
+            self.index = self.index + 1
+            return data
+
+class file_producer:
+    "producer wrapper for file[-like] objects"
+
+    # match http_channel's outgoing buffer size
+    out_buffer_size = 1<<16
+
+    def __init__ (self, file):
+        self.done = 0
+        self.file = file
+
+    def more (self):
+        if self.done:
+            return ''
+        else:
+            data = self.file.read (self.out_buffer_size)
+            if not data:
+                self.file.close()
+                del self.file
+                self.done = 1
+                return ''
+            else:
+                return data
+
+# A simple output producer.  This one does not [yet] have
+# the safety feature builtin to the monitor channel:  runaway
+# output will not be caught.
+
+# don't try to print from within any of the methods
+# of this object.
+
+class output_producer:
+    "Acts like an output file; suitable for capturing sys.stdout"
+    def __init__ (self):
+        self.data = ''
+
+    def write (self, data):
+        lines = string.splitfields (data, '\n')
+        data = string.join (lines, '\r\n')
+        self.data = self.data + data
+
+    def writeline (self, line):
+        self.data = self.data + line + '\r\n'
+
+    def writelines (self, lines):
+        self.data = self.data + string.joinfields (
+                lines,
+                '\r\n'
+                ) + '\r\n'
+
+    def flush (self):
+        pass
+
+    def softspace (self, *args):
+        pass
+
+    def more (self):
+        if self.data:
+            result = self.data[:512]
+            self.data = self.data[512:]
+            return result
+        else:
+            return ''
+
+class composite_producer:
+    "combine a fifo of producers into one"
+    def __init__ (self, producers):
+        self.producers = producers
+
+    def more (self):
+        while len(self.producers):
+            p = self.producers[0]
+            d = p.more()
+            if d:
+                return d
+            else:
+                self.producers.pop(0)
+        else:
+            return ''
+
+
+class globbing_producer:
+    """
+    'glob' the output from a producer into a particular buffer size.
+    helps reduce the number of calls to send().  [this appears to
+    gain about 30% performance on requests to a single channel]
+    """
+
+    def __init__ (self, producer, buffer_size=1<<16):
+        self.producer = producer
+        self.buffer = ''
+        self.buffer_size = buffer_size
+
+    def more (self):
+        while len(self.buffer) < self.buffer_size:
+            data = self.producer.more()
+            if data:
+                self.buffer = self.buffer + data
+            else:
+                break
+        r = self.buffer
+        self.buffer = ''
+        return r
+
+
+class hooked_producer:
+    """
+    A producer that will call <function> when it empties,.
+    with an argument of the number of bytes produced.  Useful
+    for logging/instrumentation purposes.
+    """
+
+    def __init__ (self, producer, function):
+        self.producer = producer
+        self.function = function
+        self.bytes = 0
+
+    def more (self):
+        if self.producer:
+            result = self.producer.more()
+            if not result:
+                self.producer = None
+                self.function (self.bytes)
+            else:
+                self.bytes = self.bytes + len(result)
+            return result
+        else:
+            return ''
+
+# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
+# correct.  In the face of Strange Files, it is conceivable that
+# reading a 'file' may produce an amount of data not matching that
+# reported by os.stat() [text/binary mode issues, perhaps the file is
+# being appended to, etc..]  This makes the chunked encoding a True
+# Blessing, and it really ought to be used even with normal files.
+# How beautifully it blends with the concept of the producer.
+
+class chunked_producer:
+    """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+    Here is a sample usage:
+            request['Transfer-Encoding'] = 'chunked'
+            request.push (
+                    producers.chunked_producer (your_producer)
+                    )
+            request.done()
+    """
+
+    def __init__ (self, producer, footers=None):
+        self.producer = producer
+        self.footers = footers
+
+    def more (self):
+        if self.producer:
+            data = self.producer.more()
+            if data:
+                return '%x\r\n%s\r\n' % (len(data), data)
+            else:
+                self.producer = None
+                if self.footers:
+                    return string.join (
+                            ['0'] + self.footers,
+                            '\r\n'
+                            ) + '\r\n\r\n'
+                else:
+                    return '0\r\n\r\n'
+        else:
+            return ''
+
+# Unfortunately this isn't very useful right now (Aug 97), because
+# apparently the browsers don't do on-the-fly decompression.  Which
+# is sad, because this could _really_ speed things up, especially for
+# low-bandwidth clients (i.e., most everyone).
+
+try:
+    import zlib
+except ImportError:
+    zlib = None
+
+class compressed_producer:
+    """
+    Compress another producer on-the-fly, using ZLIB
+    [Unfortunately, none of the current browsers seem to support this]
+    """
+
+    # Note: It's not very efficient to have the server repeatedly
+    # compressing your outgoing files: compress them ahead of time, or
+    # use a compress-once-and-store scheme.  However, if you have low
+    # bandwidth and low traffic, this may make more sense than
+    # maintaining your source files compressed.
+    #
+    # Can also be used for compressing dynamically-produced output.
+
+    def __init__ (self, producer, level=5):
+        self.producer = producer
+        self.compressor = zlib.compressobj (level)
+
+    def more (self):
+        if self.producer:
+            cdata = ''
+            # feed until we get some output
+            while not cdata:
+                data = self.producer.more()
+                if not data:
+                    self.producer = None
+                    return self.compressor.flush()
+                else:
+                    cdata = self.compressor.compress (data)
+            return cdata
+        else:
+            return ''
+
+class escaping_producer:
+
+    "A producer that escapes a sequence of characters"
+    " Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc..."
+
+    def __init__ (self, producer, esc_from='\r\n.', esc_to='\r\n..'):
+        self.producer = producer
+        self.esc_from = esc_from
+        self.esc_to = esc_to
+        self.buffer = ''
+        self.find_prefix_at_end = find_prefix_at_end
+
+    def more (self):
+        esc_from = self.esc_from
+        esc_to   = self.esc_to
+
+        buffer = self.buffer + self.producer.more()
+
+        if buffer:
+            buffer = string.replace (buffer, esc_from, esc_to)
+            i = self.find_prefix_at_end (buffer, esc_from)
+            if i:
+                # we found a prefix
+                self.buffer = buffer[-i:]
+                return buffer[:-i]
+            else:
+                # no prefix, return it all
+                self.buffer = ''
+                return buffer
+        else:
+            return buffer
diff --git a/demo/medusa054/server.pem b/demo/medusa054/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa054/status_handler.py b/demo/medusa054/status_handler.py
new file mode 100644 (file)
index 0000000..8d2583e
--- /dev/null
@@ -0,0 +1,272 @@
+# -*- Mode: Python -*-
+
+VERSION_STRING = "$Id: status_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+#
+# medusa status extension
+#
+
+import string
+import time
+import re
+from cgi import escape
+
+import asyncore
+import http_server
+import medusa_gif
+import producers
+from counter import counter
+
+START_TIME = long(time.time())
+
+class status_extension:
+    hit_counter = counter()
+
+    def __init__ (self, objects, statusdir='/status', allow_emergency_debug=0):
+        self.objects = objects
+        self.statusdir = statusdir
+        self.allow_emergency_debug = allow_emergency_debug
+        # We use /status instead of statusdir here because it's too
+        # hard to pass statusdir to the logger, who makes the HREF
+        # to the object dir.  We don't need the security-through-
+        # obscurity here in any case, because the id is obscurity enough
+        self.hyper_regex = re.compile('/status/object/([0-9]+)/.*')
+        self.hyper_objects = []
+        for object in objects:
+            self.register_hyper_object (object)
+
+    def __repr__ (self):
+        return '<Status Extension (%s hits) at %x>' % (
+                self.hit_counter,
+                id(self)
+                )
+
+    def match (self, request):
+        path, params, query, fragment = request.split_uri()
+        # For reasons explained above, we don't use statusdir for /object
+        return (path[:len(self.statusdir)] == self.statusdir or
+                        path[:len("/status/object/")] == '/status/object/')
+
+    # Possible Targets:
+    # /status
+    # /status/channel_list
+    # /status/medusa.gif
+
+    # can we have 'clickable' objects?
+    # [yes, we can use id(x) and do a linear search]
+
+    # Dynamic producers:
+    # HTTP/1.0: we must close the channel, because it's dynamic output
+    # HTTP/1.1: we can use the chunked transfer-encoding, and leave
+    #   it open.
+
+    def handle_request (self, request):
+        [path, params, query, fragment] = request.split_uri()
+        self.hit_counter.increment()
+        if path == self.statusdir:          # and not a subdirectory
+            up_time = string.join (english_time (long(time.time()) - START_TIME))
+            request['Content-Type'] = 'text/html'
+            request.push (
+                    '<html>'
+                    '<title>Medusa Status Reports</title>'
+                    '<body bgcolor="#ffffff">'
+                    '<h1>Medusa Status Reports</h1>'
+                    '<b>Up:</b> %s' % up_time
+                    )
+            for i in range(len(self.objects)):
+                request.push (self.objects[i].status())
+                request.push ('<hr>\r\n')
+            request.push (
+                    '<p><a href="%s/channel_list">Channel List</a>'
+                    '<hr>'
+                    '<img src="%s/medusa.gif" align=right width=%d height=%d>'
+                    '</body></html>' % (
+                            self.statusdir,
+                            self.statusdir,
+                            medusa_gif.width,
+                            medusa_gif.height
+                            )
+                    )
+            request.done()
+        elif path == self.statusdir + '/channel_list':
+            request['Content-Type'] = 'text/html'
+            request.push ('<html><body>')
+            request.push(channel_list_producer(self.statusdir))
+            request.push (
+                    '<hr>'
+                    '<img src="%s/medusa.gif" align=right width=%d height=%d>' % (
+                            self.statusdir,
+                            medusa_gif.width,
+                            medusa_gif.height
+                            ) +
+                    '</body></html>'
+                    )
+            request.done()
+
+        elif path == self.statusdir + '/medusa.gif':
+            request['Content-Type'] = 'image/gif'
+            request['Content-Length'] = len(medusa_gif.data)
+            request.push (medusa_gif.data)
+            request.done()
+
+        elif path == self.statusdir + '/close_zombies':
+            message = (
+                    '<h2>Closing all zombie http client connections...</h2>'
+                    '<p><a href="%s">Back to the status page</a>' % self.statusdir
+                    )
+            request['Content-Type'] = 'text/html'
+            request['Content-Length'] = len (message)
+            request.push (message)
+            now = int (time.time())
+            for channel in asyncore.socket_map.keys():
+                if channel.__class__ == http_server.http_channel:
+                    if channel != request.channel:
+                        if (now - channel.creation_time) > channel.zombie_timeout:
+                            channel.close()
+            request.done()
+
+        # Emergency Debug Mode
+        # If a server is running away from you, don't KILL it!
+        # Move all the AF_INET server ports and perform an autopsy...
+        # [disabled by default to protect the innocent]
+        elif self.allow_emergency_debug and path == self.statusdir + '/emergency_debug':
+            request.push ('<html>Moving All Servers...</html>')
+            request.done()
+            for channel in asyncore.socket_map.keys():
+                if channel.accepting:
+                    if type(channel.addr) is type(()):
+                        ip, port = channel.addr
+                        channel.socket.close()
+                        channel.del_channel()
+                        channel.addr = (ip, port+10000)
+                        fam, typ = channel.family_and_type
+                        channel.create_socket (fam, typ)
+                        channel.set_reuse_addr()
+                        channel.bind (channel.addr)
+                        channel.listen(5)
+
+        else:
+            m = self.hyper_regex.match (path)
+            if m:
+                oid = string.atoi (m.group (1))
+                for object in self.hyper_objects:
+                    if id (object) == oid:
+                        if hasattr (object, 'hyper_respond'):
+                            object.hyper_respond (self, path, request)
+            else:
+                request.error (404)
+                return
+
+    def status (self):
+        return producers.simple_producer (
+                '<li>Status Extension <b>Hits</b> : %s' % self.hit_counter
+                )
+
+    def register_hyper_object (self, object):
+        if not object in self.hyper_objects:
+            self.hyper_objects.append (object)
+
+import logger
+
+class logger_for_status (logger.tail_logger):
+
+    def status (self):
+        return 'Last %d log entries for: %s' % (
+                len (self.messages),
+                html_repr (self)
+                )
+
+    def hyper_respond (self, sh, path, request):
+        request['Content-Type'] = 'text/plain'
+        messages = self.messages[:]
+        messages.reverse()
+        request.push (lines_producer (messages))
+        request.done()
+
+class lines_producer:
+    def __init__ (self, lines):
+        self.lines = lines
+
+    def more (self):
+        if self.lines:
+            chunk = self.lines[:50]
+            self.lines = self.lines[50:]
+            return string.join (chunk, '\r\n') + '\r\n'
+        else:
+            return ''
+
+class channel_list_producer (lines_producer):
+    def __init__ (self, statusdir):
+        channel_reprs = map (
+                lambda x: '&lt;' + repr(x)[1:-1] + '&gt;',
+                asyncore.socket_map.values()
+                )
+        channel_reprs.sort()
+        lines_producer.__init__ (
+                self,
+                ['<h1>Active Channel List</h1>',
+                 '<pre>'
+                 ] + channel_reprs + [
+                         '</pre>',
+                         '<p><a href="%s">Status Report</a>' % statusdir
+                         ]
+                )
+
+
+def html_repr (object):
+    so = escape (repr (object))
+    if hasattr (object, 'hyper_respond'):
+        return '<a href="/status/object/%d/">%s</a>' % (id (object), so)
+    else:
+        return so
+
+def html_reprs (list, front='', back=''):
+    reprs = map (
+            lambda x,f=front,b=back: '%s%s%s' % (f,x,b),
+            map (lambda x: escape (html_repr(x)), list)
+            )
+    reprs.sort()
+    return reprs
+
+# for example, tera, giga, mega, kilo
+# p_d (n, (1024, 1024, 1024, 1024))
+# smallest divider goes first - for example
+# minutes, hours, days
+# p_d (n, (60, 60, 24))
+
+def progressive_divide (n, parts):
+    result = []
+    for part in parts:
+        n, rem = divmod (n, part)
+        result.append (rem)
+    result.append (n)
+    return result
+
+# b,k,m,g,t
+def split_by_units (n, units, dividers, format_string):
+    divs = progressive_divide (n, dividers)
+    result = []
+    for i in range(len(units)):
+        if divs[i]:
+            result.append (format_string % (divs[i], units[i]))
+    result.reverse()
+    if not result:
+        return [format_string % (0, units[0])]
+    else:
+        return result
+
+def english_bytes (n):
+    return split_by_units (
+            n,
+            ('','K','M','G','T'),
+            (1024, 1024, 1024, 1024, 1024),
+            '%d %sB'
+            )
+
+def english_time (n):
+    return split_by_units (
+            n,
+            ('secs', 'mins', 'hours', 'days', 'weeks', 'years'),
+            (         60,     60,      24,     7,       52),
+            '%d %s'
+            )
diff --git a/demo/medusa054/xmlrpc_handler.py b/demo/medusa054/xmlrpc_handler.py
new file mode 100644 (file)
index 0000000..15057cf
--- /dev/null
@@ -0,0 +1,103 @@
+# -*- Mode: Python -*-
+
+# See http://www.xml-rpc.com/
+#     http://www.pythonware.com/products/xmlrpc/
+
+# Based on "xmlrpcserver.py" by Fredrik Lundh (fredrik@pythonware.com)
+
+VERSION = "$Id: xmlrpc_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+import http_server
+import xmlrpclib
+
+import string
+import sys
+
+class xmlrpc_handler:
+
+    def match (self, request):
+        # Note: /RPC2 is not required by the spec, so you may override this method.
+        if request.uri[:5] == '/RPC2':
+            return 1
+        else:
+            return 0
+
+    def handle_request (self, request):
+        [path, params, query, fragment] = request.split_uri()
+
+        if request.command == 'POST':
+            request.collector = collector (self, request)
+        else:
+            request.error (400)
+
+    def continue_request (self, data, request):
+        params, method = xmlrpclib.loads (data)
+        try:
+            # generate response
+            try:
+                response = self.call (method, params)
+                if type(response) != type(()):
+                    response = (response,)
+            except:
+                # report exception back to server
+                response = xmlrpclib.dumps (
+                        xmlrpclib.Fault (1, "%s:%s" % (sys.exc_type, sys.exc_value))
+                        )
+            else:
+                response = xmlrpclib.dumps (response, methodresponse=1)
+        except:
+            # internal error, report as HTTP server error
+            request.error (500)
+        else:
+            # got a valid XML RPC response
+            request['Content-Type'] = 'text/xml'
+            request.push (response)
+            request.done()
+
+    def call (self, method, params):
+        # override this method to implement RPC methods
+        raise "NotYetImplemented"
+
+class collector:
+
+    "gathers input for POST and PUT requests"
+
+    def __init__ (self, handler, request):
+
+        self.handler = handler
+        self.request = request
+        self.data = ''
+
+        # make sure there's a content-length header
+        cl = request.get_header ('content-length')
+
+        if not cl:
+            request.error (411)
+        else:
+            cl = string.atoi (cl)
+            # using a 'numeric' terminator
+            self.request.channel.set_terminator (cl)
+
+    def collect_incoming_data (self, data):
+        self.data = self.data + data
+
+    def found_terminator (self):
+        # set the terminator back to the default
+        self.request.channel.set_terminator ('\r\n\r\n')
+        self.handler.continue_request (self.data, self.request)
+
+if __name__ == '__main__':
+
+    class rpc_demo (xmlrpc_handler):
+
+        def call (self, method, params):
+            print 'method="%s" params=%s' % (method, params)
+            return "Sure, that works"
+
+    import asyncore
+
+    hs = http_server.http_server ('', 8000)
+    rpc = rpc_demo()
+    hs.install_handler (rpc)
+
+    asyncore.loop()
diff --git a/demo/perf/memio.py b/demo/perf/memio.py
new file mode 100644 (file)
index 0000000..238d85c
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python2.0
+
+"""A comparison of Python's cStringIO and M2Crypto's MemoryBuffer,
+the outcome of which is that MemoryBuffer suffers from doing too much
+in Python. 
+
+Two way to optimise MemoryBuffer:
+1. Create MemoryBufferIn and MemoryBufferOut a la StringI and StringO.
+2. Have MemoryBuffer do all internal work with cStringIO. ;-)
+"""
+
+from cStringIO import StringIO
+from M2Crypto.BIO import MemoryBuffer
+from M2Crypto import m2
+import profile
+
+txt = 'Python, Smalltalk, Haskell, Scheme, Lisp, Self, Erlang, ML, ...'
+
+def stringi(iter, txt=txt):
+    buf = StringIO()
+    for i in range(iter):
+        buf.write(txt)
+    out = buf.getvalue()
+
+def membufi(iter, txt=txt):
+    buf = MemoryBuffer()
+    for i in range(iter):
+        buf.write(txt)
+    out = buf.getvalue()
+
+def membuf2i(iter, txt=txt):
+    buf = MemoryBuffer()
+    buf.write(txt * iter)
+    out = buf.getvalue()
+
+def cmembufi(iter, txt=txt):
+    buf = m2.bio_new(m2.bio_s_mem())
+    for i in range(iter):
+        m2.bio_write(buf, txt)
+    m2.bio_set_mem_eof_return(buf, 0)
+    out = m2.bio_read(buf, m2.bio_ctrl_pending(buf))
+
+if __name__ == '__main__':
+    profile.run('stringi(10000)')
+    profile.run('cmembufi(10000)')
+    profile.run('membufi(10000)')
+    profile.run('membuf2i(10000)')
+
+
diff --git a/demo/perf/sha1.py b/demo/perf/sha1.py
new file mode 100644 (file)
index 0000000..6d85289
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env python2.0
+
+"""A comparison of Python's sha and M2Crypto.EVP.MessageDigest, 
+the outcome of which is that EVP.MessageDigest suffers from doing 
+too much in Python."""
+
+import profile
+
+from sha import sha
+import M2Crypto
+from M2Crypto import m2
+from M2Crypto.EVP import MessageDigest
+
+txt = 'Python, Smalltalk, Haskell, Scheme, Lisp, Self, Erlang, ML, ...'
+
+def py_sha(iter, txt=txt):
+    s = sha()
+    for i in range(iter):
+        s.update(txt)
+    out = s.digest()
+
+def m2_sha(iter, txt=txt):
+    s = MessageDigest('sha1')
+    for i in range(iter):
+        s.update(txt)
+    out = s.digest()
+
+def m2_sha_2(iter, txt=txt):
+    s = MessageDigest('sha1')
+    s.update(txt * iter)
+    out = s.digest()
+
+def m2c_sha(iter, txt=txt):
+    ctx = m2.md_ctx_new()
+    m2.digest_init(ctx, m2.sha1())
+    for i in range(iter):
+        m2.digest_update(ctx, txt)
+    out = m2.digest_final(ctx)
+
+if __name__ == '__main__':
+    profile.run('py_sha(10000)')
+    profile.run('m2_sha(10000)')
+    profile.run('m2_sha_2(10000)')
+    profile.run('m2c_sha(10000)')
+
+
diff --git a/demo/pgp/pgpstep.py b/demo/pgp/pgpstep.py
new file mode 100644 (file)
index 0000000..ff5b135
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+
+"""pgpstep - steps through a pgp2 packet stream.
+
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import PGP, util
+import time
+
+def desc_public_key(pkt):
+    print 'packet = public_key'
+    print 'version =', pkt.version()
+    print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+    print 'validity code =', pkt.validity()
+    print 'pkc type =', `pkt.pkc()`
+    #e, n = pkt.pubkey()
+    print 'e =', `pkt._e`
+    print 'n =', `pkt._n`
+    print
+    
+def desc_trust(pkt):
+    print 'packet = trust'
+    print 'trustworthiness = <ignored>'
+    print
+
+def desc_userid(pkt):
+    print 'packet = user_id'
+    print 'user_id =', pkt.userid()
+    print
+
+def desc_signature(pkt):
+    print 'packet = signature'
+    print 'version =', pkt.version()
+    print 'classification =', `pkt._classification`
+    print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+    print 'keyid =', `pkt._keyid`
+    print 'pkc type =', `pkt.pkc()`
+    print 'md_algo =', `pkt._md_algo`
+    print 'md_chksum =', `pkt._md_chksum`
+    print 'sig =', `pkt._sig`
+    print
+
+def desc_private_key(pkt):
+    print 'packet = private key'
+    print 'version =', pkt.version()
+    print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+    print 'validity code =', pkt.validity()
+    print 'pkc type =', `pkt.pkc()`
+    print 'e =', `pkt._e`
+    print 'n =', `pkt._n`
+    print 'cipher =', `pkt._cipher`
+    if pkt._cipher == '\001':
+        print 'following attributes are encrypted'
+        print 'iv =', `pkt._iv`
+    print 'd =', `pkt._d`
+    print 'p =', `pkt._p`
+    print 'q =', `pkt._q`
+    print 'u =', `pkt._u`
+    print 'checksum =', `pkt._cksum`
+    print
+
+def desc_cke(pkt):
+    print 'packet = cke'
+    print 'iv =', `pkt.iv`
+    print 'checksum =', `pkt.cksum`
+    print 'ciphertext =', `pkt.ctxt`
+    print
+
+def desc_pke(pkt):
+    print 'packet = pke'
+    print 'version =', pkt.version
+    print 'keyid =', `pkt.keyid`
+    print 'pkc type =', pkt.pkc_type
+    print 'dek =', hex(pkt.dek)[:-1]
+    print
+
+def desc_literal(pkt):
+    print 'packet = literal data'
+    print 'mode =', `pkt.fmode`
+    print 'filename =', pkt.fname
+    print 'time = ', time.asctime(time.gmtime(pkt.ftime))
+    print 'data = <%d octets of literal data>' % (len(pkt.data),)
+    print
+
+DESC = {
+    PGP.public_key_packet: desc_public_key,
+    PGP.trust_packet: desc_trust,
+    PGP.userid_packet: desc_userid,
+    PGP.signature_packet: desc_signature,
+    PGP.private_key_packet: desc_private_key,
+    PGP.cke_packet: desc_cke,
+    PGP.pke_packet: desc_pke,
+    PGP.literal_packet: desc_literal,
+}
+
+if __name__ == '__main__':
+    import sys
+    count = 0
+    for arg in sys.argv[1:]:
+           f = open(arg, 'rb')
+           ps = PGP.packet_stream(f)
+           while 1:
+               pkt = ps.read()
+               if pkt is None:
+                   break
+               elif pkt:
+                   print '-'*70
+                   DESC[pkt.__class__](pkt)
+           count = count + ps.count()
+           ps.close()
+    print '-'*70
+    print 'Total octets processed =', count
+
diff --git a/demo/pgp/pubring.pgp b/demo/pgp/pubring.pgp
new file mode 100644 (file)
index 0000000..fbe4863
Binary files /dev/null and b/demo/pgp/pubring.pgp differ
diff --git a/demo/pgp/secring.pgp b/demo/pgp/secring.pgp
new file mode 100644 (file)
index 0000000..7eca123
Binary files /dev/null and b/demo/pgp/secring.pgp differ
diff --git a/demo/pkcs7/pkcs7-thawte.pem b/demo/pkcs7/pkcs7-thawte.pem
new file mode 100644 (file)
index 0000000..a444627
--- /dev/null
@@ -0,0 +1,45 @@
+-----BEGIN PKCS7-----
+MIAGCSqGSIb3DQEHAqCAMIIH1wIBATELMAkGBSsOAwIaBQAwgAYJKoZIhvcNAQcB
+AACgggXgMIICxDCCAi2gAwIBAgIDAphPMA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
+VQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52
+aWxsZTEPMA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2
+aWNlczEoMCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNjAe
+Fw0wMDA1MTgxMTA4MTJaFw0wMTA1MTgxMTA4MTJaMGUxETAPBgNVBAQTCFN0cm9l
+ZGVyMRAwDgYDVQQqEwdNaWNoYWVsMRkwFwYDVQQDExBNaWNoYWVsIFN0cm9lZGVy
+MSMwIQYJKoZIhvcNAQkBFhRtaWNoYWVsQHN0cm9lZGVyLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAqzxf+ZpWoELOD7sXes/G9O6dSCkGeNL/G5l0k/b4
+C9QnZyEh2S0vWI+g43G1uGhu6sgI5Y1SfrG08xGIUEcmexxnl6krTR/QlHr+t9If
+UXFTmEPAnuIpfPRUqhOQlNFB1DfwKVLb4J3b2947ulv4vpb4owVMC+WjDM5fo765
+RXMCAwEAAaNSMFAwHwYDVR0RBBgwFoEUbWljaGFlbEBzdHJvZWRlci5jb20wDAYD
+VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSIq/Fgg2ZV9ORYx0YdwGG9I9fDjDANBgkq
+hkiG9w0BAQQFAAOBgQCxx7Uvsnq1F/yOIPiUmlaTXLFAvvTj2trHAF4UFhkQGe6V
+IBHmDk62H1xJhA4AHNVz3Pe250S8Fu75OgOh2pRJ8KfB6cJU0gxz9ahRt48Qy6+m
+XUHk9135ru2u8I505nj4i37T6zK3+O/lZU+P79qdPHYwO6dNsYyaRFYLxGyXTjCC
+AxQwggJ9oAMCAQICAQswDQYJKoZIhvcNAQEEBQAwgdExCzAJBgNVBAYTAlpBMRUw
+EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE
+ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy
+dmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFp
+bCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhhd3RlLmNv
+bTAeFw05OTA5MTYxNDAxNDBaFw0wMTA5MTUxNDAxNDBaMIGUMQswCQYDVQQGEwJa
+QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52aWxsZTEP
+MA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEo
+MCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNjCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEAs2lal9TQFgt6tcVd6SGcI3LNEkxL937Px/vK
+ciT0QlKsV5Xje2F6F4Tn/XI5OJS06u1lp5IGXr3gZfYZu5R5dkw+uWhwdYQc9BF0
+ALwFLE8JAxcxzPRB1HLGpl3iiESwiy7ETfHw1oU+bPOVlHiRfkDpnNGNFVeOwnPl
+MN5G9U8CAwEAAaM3MDUwEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBRy
+ScJzNMZV9At2coF+d/SH58ayDjANBgkqhkiG9w0BAQQFAAOBgQBrxlnpMfrptuyx
+A9jfcnL+kWBI6sZV3XvwZ47GYXDnbcKlN9idtxcoVgWL3Vx1b8aRkMZsZnET0BB8
+a5FvhuAhNi3B1+qyCa3PLW3Gg1Kb+7v+nIed/LfpdJLkXJeu/H6syg1vcnpnLGtz
+9Yb5nfUAbvQdB86dnoJjKe+TCX5V3jGCAdAwggHMAgEBMIGcMIGUMQswCQYDVQQG
+EwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52aWxs
+ZTEPMA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2aWNl
+czEoMCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNgIDAphP
+MAkGBSsOAwIaBQCggYowGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG
+9w0BCQUxDxcNMDAwNzE2MTQ0MzQ0WjAjBgkqhkiG9w0BCQQxFgQU2jmj7l5rSw0y
+Vb/vlWAYkK/YBwkwKwYJKoZIhvcNAQkPMR4wHDAKBggqhkiG9w0DBzAOBggqhkiG
+9w0DAgICAIAwDQYJKoZIhvcNAQEBBQAEgYBagl1y4nRwW+GLhZ5tElkh9Z/yFirz
+ZXI0jMywML/ADxsAf1yV2JIYCCk0V33QdrYy8MVqgErGXxGdA5aKnm5545Wf4Noz
+CLWF4FJc6RDSFp7dv2631TrswdFRRCHNds+dvP5v5EAiADvmEOYaDiWM07jp73jx
+TkkzxW9cevZDaQAAAAA=
+-----END PKCS7-----
diff --git a/demo/pkcs7/test.py b/demo/pkcs7/test.py
new file mode 100644 (file)
index 0000000..afa7477
--- /dev/null
@@ -0,0 +1,6 @@
+from M2Crypto import BIO, SMIME
+
+pf = BIO.openfile('pkcs7-thawte.pem')
+p7 = SMIME.load_pkcs7_bio(pf)
+print p7.type(1)
+
diff --git a/demo/rsa.priv.pem b/demo/rsa.priv.pem
new file mode 100644 (file)
index 0000000..ed34db6
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBANQNY7RD9BarYRsmMazM1hd7a+u3QeMPFZQ7Ic+BmmeWHvvVP4Yj
+yu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQJAIHCz8h37N4ScZHThYJgt
+oIYHKpZsg/oIyRaKw54GKxZq5f7YivcWoZ8j7IQ65lHVH3gmaqKOvqdAVVt5imKZ
+KQIhAPPsr9i3FxU+Mac0pvQKhFVJUzAFfKiG3ulVUdHgAaw/AiEA3ozHKzfZWKxH
+gs8v8ZQ/FnfI7DwYYhJC0YsXb6NSvR8CIHymwLo73mTxsogjBQqDcVrwLL3GoAyz
+V6jf+/8HvXMbAiEAj1b3FVQEboOQD6WoyJ1mQO9n/xf50HjYhqRitOnp6ZsCIQDS
+AvkvYKc6LG8IANmVv93g1dyKZvU/OQkAZepqHZB2MQ==
+-----END RSA PRIVATE KEY-----
diff --git a/demo/rsa.priv0.pem b/demo/rsa.priv0.pem
new file mode 100644 (file)
index 0000000..e2b93a1
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-CBC,244DDAFE6F991A53
+
+h1/NnMxq2ku9UkBG2tfM7hCFWmOvwMbVQBYgS4jLm3ENvWRgkm7pCqjCug99Uo5o
+r+NhzqyvSBH+eX3ojVAfpMxkL0TIjMrogRq2TD75v6hTGu/fd4Yrw7vZaKRbLzoh
+rG6m7zdbiQwNyh0bTbbo3WmQ07FXkXrDqihLaZTJOvHNLS1lKwRjIS0MqtyhOfPj
+NNwuNEs6AFz4k6UxNMRXhyU2SXn5SOgZZB12SCIsYA034rwKFKqNRoneaLSlhtut
+1z/BlJaLiZDHJmtxtgz5h3Ss9fQ9J0pLnybx+EM70TPM3hz47/8cQiEYIVg7+QFW
+jNYaGIA6IOcyB7fwX5e/zFy5yXAsmuIw/iP0sYa29hdUG++T8Mp2knrBBwQO77OS
+WRz219dMQQRyyn2jpN5x23AZyz4gHj4YKMm3KO06Y2k=
+-----END RSA PRIVATE KEY-----
+
+This is the same key as rsa.priv.pem. Passphrase is 'qwerty'.
+
diff --git a/demo/rsa.pub.pem b/demo/rsa.pub.pem
new file mode 100644 (file)
index 0000000..42379aa
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANQNY7RD9BarYRsmMazM1hd7a+u3QeMP
+FZQ7Ic+BmmeWHvvVP4Yjyu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/demo/rsa1024pvtkey.pem b/demo/rsa1024pvtkey.pem
new file mode 100644 (file)
index 0000000..481f191
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDE3rnXDvA4lrIUj7ZXQM2ODLw0NnjKXh7+O3GPttvoFoacK1qk
+BRO+aO4hPhvQumySTz+N2RkIsT9PTx6yfyWWwYC1zbbv2cxlCBisGAvrUSL6IfHX
+ND2/vVN+QmOd/b4hfC/ekRo2ylrnX0pOXv/JxiblV9WumiZsBEuhUvxCnQIDAQAB
+AoGBALdaLEDkM8ywZQiLVCptO0RaDgqe1N68zCbBXCGaD7NXD2VxZ0itRdcnyOiC
+/MroZWfakPleQVd8JNeLe66IhosAJd7Zbjf/npYnD4zD1x54zRrSuBquXhz+ev7U
+tZONPxU5QCf/aow4z1s/M+knLyy+0lAUlWwTgOrd5VCQ8suBAkEA5/v48HZW8pQC
+2zxQUFfsnRu6DxJuQ1UvWp51FNUYZM2LjpwgGyCNkCw+nuxPgM1HnOTAMVXkZdwL
+7XG0Gm5ZoQJBANlAKKUc7ItOfN5TeD154Qz9FLtZUgWnWfawvEqiHJ0cPrzyv9v6
+wu19QxP6ORZBWyr8c20/p2fNrk+7YOZAH30CQQDZ9p1HEWlQMlEcu+aaFoJyewKt
+9pszGG6NriRDlpR84cMmEvr3gfaAZ5HOsCli031dpHAP6qvWKJHsXtDhpJ0BAkBs
+fANP4A+myLzF8Hx8hl4BNGej3kh9FkJwU3TS9/y935rck4OG/8NTAFf8o9jZ6izy
+XDnvdffMeALxQapzj9WpAkBQt2yjU3V3bKkNl0XwTk/VK8WGEWTBF1byZv/6VZOb
+q/vOPSexrQmu+T8+lljraJISfIpMSWZhR4oarmnGVRPX
+-----END RSA PRIVATE KEY-----
diff --git a/demo/rsa_bench.py b/demo/rsa_bench.py
new file mode 100644 (file)
index 0000000..b0adacc
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+
+"""
+    RSA signing demo and benchmark.
+
+      Usage:  python -O rsa_bench.py [option option option ...]
+        where options may include:
+          makenewkey  showdigest  showprofile
+          md5  sha1  sha256  sha512
+          <key length>
+        
+    Larry Bugbee
+    November 2006
+    
+    
+    Some portions are Copyright (c) 1999-2003 Ng Pheng Siong. 
+    All rights reserved.
+
+    Portions created by Open Source Applications Foundation 
+    (OSAF) are Copyright (C) 2004 OSAF. All Rights Reserved.
+
+"""
+
+from M2Crypto import RSA, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey  = 0     # 1 = make/save new key, 0 = use existing
+showpubkey  = 0     # 1 = show the public key value
+showdigest  = 0     # 1 = show the digest value
+showprofile = 0     # 1 = use the python profiler
+
+hashalgs  = ['md5', 'ripemd160', 'sha1', 
+             'sha224', 'sha256', 'sha384', 'sha512']
+
+# default hashing algorithm
+hashalg  = 'sha1'
+
+# default key parameters
+keylen   = 1024
+exponent = 65537
+'''
+  There is some temptation to use an RSA exponent of 3
+  because 1) it is easy to remember and 2) it minimizes the
+  effort of signature verification.  Unfortunately there 
+  a couple of attacks based on the use of 3.  From a draft 
+  RFC (Easklake, Dec 2000):
+    A public exponent of 3 minimizes the effort needed to 
+    verify a signature.  Use of 3 as the public exponent is
+    weak for confidentiality uses since, if the same data 
+    can be collected encrypted under three different keys 
+    with an exponent of 3 then, using the Chinese Remainder 
+    Theorem [NETSEC], the original plain text can be easily
+    recovered.   
+  This applies to confidentiality so it is not of major 
+  concern here.  The second attack is a protocol implementation 
+  weakness and can be patched, but has the patch been applied?  
+  ...correctly?  It is arguably better to get into the habit 
+  of using a stronger exponent and avoiding these and possible 
+  future attacks based on 3.  I suggest getting in the habit 
+  of using something stronger.  Some suggest using 65537.
+'''
+
+# number of speed test loops
+N1 = N2  = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(rsa, dgst):
+    print '  testing signing and verification...',
+    try:
+        sig = rsa.sign(dgst)
+    except Exception, e:
+        print '\n\n    *** %s *** \n' % e
+        sys.exit()
+    if not rsa.verify(dgst, sig):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def test_asn1(rsa, dgst):
+    print '  testing asn1 signing and verification...',
+    blob = rsa.sign_asn1(dgst)
+    if not rsa.verify_asn1(dgst, blob):
+        print 'not ok'
+    else:
+        print 'ok'
+
+def speed():
+    from time import time
+    t1 = time()
+    for i in range(N1):
+        sig = rsa.sign(dgst)
+    print '    %d signings:      %8.2fs' % (N1, (time() - t1))
+    t1 = time()
+    for i in range(N2):
+        rsa.verify(dgst, sig)
+    print '    %d verifications: %8.2fs' % (N2, (time() - t1))
+        
+def test_speed(rsa, dgst):
+    print '  measuring speed...'
+    if showprofile:
+        import profile
+        profile.run('speed()')
+    else:
+        speed()
+        print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(keylen, hashalg):
+    global rsa, dgst     # this exists ONLY for speed testing
+    
+    Rand.load_file('randpool.dat', -1) 
+        
+    pvtkeyfilename = 'rsa%dpvtkey.pem' % (keylen)
+    pubkeyfilename = 'rsa%dpubkey.pem' % (keylen)  
+    
+    if makenewkey:
+        print '  making and saving a new key'
+        rsa = RSA.gen_key(keylen, exponent)
+        rsa.save_key(pvtkeyfilename, None )  # no pswd callback
+        rsa.save_pub_key(pubkeyfilename)
+    else:
+        print '  loading an existing key'
+        rsa = RSA.load_key(pvtkeyfilename)
+    print '  rsa key length:', len(rsa)
+    
+    if not rsa.check_key():
+        raise 'key is not initialised'
+
+    # since we are testing signing and verification, let's not 
+    # be fussy about the digest.  Just make one.
+    md = EVP.MessageDigest(hashalg)
+    md.update('can you spell subliminal channel?')
+    dgst = md.digest()
+    print '  hash algorithm: %s' % hashalg
+    if showdigest:
+        print '  %s digest: \n%s' % (hashalg, base64.encodestring(dgst))
+    
+    test(rsa, dgst)
+#    test_asn1(rsa, dgst)
+    test_speed(rsa, dgst)
+    Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+    print """
+  Usage:  python -O %s [option option option ...]
+    where options may include:
+      makenewkey  showdigest  showprofile
+      md5  sha1  sha256  sha512
+      <key length>
+""" % sys.argv[0]
+    sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+    for arg in sys.argv[1:]:
+        if arg in hashalgs:         hashalg = arg; continue
+        if arg == 'makenewkey':   makenewkey  = 1; continue
+        if arg == 'showpubkey':   showpubkey  = 1; continue
+        if arg == 'showdigest':   showdigest  = 1; continue
+        if arg == 'showprofile':  showprofile = 1; continue
+        try:
+            keylen = int(arg)
+        except:
+            print '\n  *** argument "%s" not understood ***' % arg
+            print_usage()
+        
+    main(keylen, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
diff --git a/demo/rsatest.py b/demo/rsatest.py
new file mode 100644 (file)
index 0000000..4277c4d
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+"""RSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import RSA, EVP, Rand
+
+msg="The magic words are squeamish ossifrage."
+sha1=EVP.MessageDigest('sha1')
+sha1.update(msg)
+dgst=sha1.digest()
+
+priv=RSA.load_key('rsa.priv.pem')
+pub=RSA.load_pub_key('rsa.pub.pem')
+
+def test_encrypt(padding):
+    print 'testing public-key encryption:', padding
+    padding=eval('RSA.'+padding)
+    ctxt=pub.public_encrypt(dgst, padding)
+    ptxt=priv.private_decrypt(ctxt, padding)
+    if ptxt!=dgst:
+        print 'public_encrypt -> private_decrypt: not ok'
+
+def test_sign(padding):
+    print 'testing private-key signing:', padding
+    padding=eval('RSA.'+padding)
+    ctxt=priv.private_encrypt(dgst, padding)    
+    ptxt=pub.public_decrypt(ctxt, padding)
+    if ptxt!=dgst:
+        print 'private_decrypt -> public_encrypt: not ok'
+
+def test0():
+    print 'testing misc.'
+    print `pub.e`, `pub.n`
+    print `priv.e`, `priv.n`
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    test_encrypt('pkcs1_padding')
+    test_encrypt('pkcs1_oaep_padding')
+    #test_encrypt('sslv23_padding')
+    test_sign('pkcs1_padding')
+    Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/README b/demo/smime.howto/README
new file mode 100644 (file)
index 0000000..2b494d7
--- /dev/null
@@ -0,0 +1,5 @@
+ 01 Apr 2001
+-------------
+
+These are the example programs from the HOWTO.
+
diff --git a/demo/smime.howto/decrypt.py b/demo/smime.howto/decrypt.py
new file mode 100644 (file)
index 0000000..617f4ab
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load private key and cert.
+s.load_key('recipient_key.pem', 'recipient.pem')
+
+# Load the encrypted data.
+p7, data = SMIME.smime_load_pkcs7('encrypt.p7')
+
+# Decrypt p7.
+out = s.decrypt(p7)
+    
+print out
+
diff --git a/demo/smime.howto/dv.py b/demo/smime.howto/dv.py
new file mode 100644 (file)
index 0000000..94223a8
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load private key and cert.
+s.load_key('recipient_key.pem', 'recipient.pem')
+
+# Load the signed/encrypted data.
+p7, data = SMIME.smime_load_pkcs7('se.p7')
+
+# After the above step, 'data' == None.  
+# Decrypt p7. 'out' now contains a PKCS #7 signed blob.
+out = s.decrypt(p7)
+
+# Load the signer's cert.
+x509 = X509.load_cert('signer.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Load the signer's CA cert. In this case, because the signer's
+# cert is self-signed, it is the signer's cert itself.
+st = X509.X509_Store()
+st.load_info('signer.pem')
+s.set_x509_store(st)
+
+# Recall 'out' contains a PKCS #7 blob.
+# Transform 'out'; verify the resulting PKCS #7 blob.
+p7_bio = BIO.MemoryBuffer(out)
+p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+v = s.verify(p7)
+
+print v
+
diff --git a/demo/smime.howto/encrypt.p7 b/demo/smime.howto/encrypt.p7
new file mode 100644 (file)
index 0000000..eb8b8a5
--- /dev/null
@@ -0,0 +1,17 @@
+From: sender@example.dom
+To: recipient@example.dom
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIBVwYJKoZIhvcNAQcDoIIBSDCCAUQCAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+KoZIhvcNAQEBBQAEgYCdBULuQ/kfjgk17dMwBHdY+vXfqR/B+vJnF0gXhK6gc5d4
+gCkRefEyVu7lGAD2MOdD0cOLFvHdmGHFJqyJ8T+A1mmqrxMAiJImtqN586YnoTUb
+Cw5FaMEd20d6hpGeEVBFHkam0vQKdR1GLG1VPiFn+ytB06g7NNphsI5/uFGyLDA7
+BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECPfuW3hoPVr0gBiA4CiFG4m74CT4wHPu
+q1fhEkG3zjraxn4=
+
+
diff --git a/demo/smime.howto/encrypt.py b/demo/smime.howto/encrypt.py
new file mode 100644 (file)
index 0000000..ae37526
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+def makebuf(text):
+    return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load target cert to encrypt to.
+x509 = X509.load_cert('recipient.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Set cipher: 3-key triple-DES in CBC mode.
+s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+# Encrypt the buffer.
+p7 = s.encrypt(buf)
+    
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/recipient.pem b/demo/smime.howto/recipient.pem
new file mode 100644 (file)
index 0000000..78b6c14
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9zCCAmCgAwIBAgIBADANBgkqhkiG9w0BAQQFADBhMQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xGTAXBgNVBAMTEFMvTUlNRSBSZWNpcGllbnQxJDAi
+BgkqhkiG9w0BCQEWFXJlY2lwaWVudEBleGFtcGxlLmRvbTAeFw0wMTAzMzExMTQy
+MTVaFw0wMjAzMzExMTQyMTVaMGExCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNy
+eXB0bzEZMBcGA1UEAxMQUy9NSU1FIFJlY2lwaWVudDEkMCIGCSqGSIb3DQEJARYV
+cmVjaXBpZW50QGV4YW1wbGUuZG9tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDQwLwQSshbTn/GUZXnZUQEUDc61OUd+qcPpHfi76Y7ar+2NwsalQ3bu2i7edEK
+IZZWMFRnrOwE9PhmJHJIzfYDswgpHWtRy0P/Oyzt5kLBjvJYuMIqu8gZtWFz0G28
+Q8tGvIuPdWba+9TT3LOv4CXNF1V0k0KgAPd1Uq2FUcBa2QIDAQABo4G+MIG7MB0G
+A1UdDgQWBBQe7b4CDEBuMJyiscil27YBdZBr9zCBiwYDVR0jBIGDMIGAgBQe7b4C
+DEBuMJyiscil27YBdZBr96FlpGMwYTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
+Q3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBpZW50MSQwIgYJKoZIhvcNAQkB
+FhVyZWNpcGllbnRAZXhhbXBsZS5kb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQQFAAOBgQAJbQXzP7AK9u2kyvl8oeZAeJQDRsip4PVMkYd64HW3Hq/9ud3g
+hj/laeUyfcga+c1c1yPUug5Ab+loeHhEEKsL9LqYFXzpFU1lXaID02zcqG7g3PWe
+r9RKsUqrn4ZbRQ+clidnIx4nYLuG6CPQ6ME/uFrYHMsmQEO/+KoJONf/cg==
+-----END CERTIFICATE-----
diff --git a/demo/smime.howto/recipient_key.pem b/demo/smime.howto/recipient_key.pem
new file mode 100644 (file)
index 0000000..0cb61f6
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDQwLwQSshbTn/GUZXnZUQEUDc61OUd+qcPpHfi76Y7ar+2Nwsa
+lQ3bu2i7edEKIZZWMFRnrOwE9PhmJHJIzfYDswgpHWtRy0P/Oyzt5kLBjvJYuMIq
+u8gZtWFz0G28Q8tGvIuPdWba+9TT3LOv4CXNF1V0k0KgAPd1Uq2FUcBa2QIDAQAB
+AoGAVOMrFozycH65YtHmXVRGlmJwMxJDoS8+JBRDVBsTw/Gix9wWPdcC7amF60ac
+BLynv6Cjkg01ZMahBBgqCQUH1rii6Kg20MJJtpqvt1X+CAZkytVsQqwutSQXHj+g
+TzZVDxQiuPKMyVhKTSVqutqs2EyFgSKcYuodfms5xDk2EyECQQD6vnEAl2PHBoia
+5wrauujbWTM6H5oioWvJgLaUNgUhJ86/Y+ewKoGxLdYaxx99KhKxN/04i2chIHk0
+c53THOt9AkEA1SD1Rdm93FUMEor+BYEQgiN/4pWnSIsgUjyqV7lPv9QegdDTbVfm
+WuPNev6Z+qo9mpDWbvhCZhH159q7uGfzjQJAe88dLRWThuqK+TGsAmTYJbbdvI1u
+JjteZZjQjk4+KijlxUsnU60pbLsdRQudWMg1gpwKxKjQu2K1dljATUWyYQJARI83
+l2K1+py5J3XixS6BevukdeUiTOnEWe/98/4+szyvG59rg+8UwQQq43fnXIVLD9+r
+u0LNSTxZ2F26qVV3OQJBAIBG0Gv9C44UlCPiJhmMqcpzexX20erOEGu+UiCUhHAC
+ZdWdFaD2dlmk0O/E82LxPPivkGv5DtkNpzCl+3Vo+kI=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/smime.howto/se.p7 b/demo/smime.howto/se.p7
new file mode 100644 (file)
index 0000000..df4af0f
--- /dev/null
@@ -0,0 +1,56 @@
+From: sender@example.dom
+To: recipient@example.dom
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIIwwYJKoZIhvcNAQcDoIIItDCCCLACAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+KoZIhvcNAQEBBQAEgYCn4qL7Z8QlmVEMbO2zU5RUtBqw1cYPzxd/4ZF9H+djlL7f
+EDIYHidvTBksXwcBXSTxsKsSusGF95NtwaZf4D3eiR1ChdkeXaiWCUPAQr0Mw4+A
+EJa9eGBuLH+LXH0pDN5OhYb8hsL/EcV2OpOEjOe4aM/u3Ot5u1kK1582oikhCDCC
+B6UGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQImrxQEPwEHHeAggeACeVVKqKjySva
+zos9ahumXC7bMIcfesxQF0L2CcR4wQtnwuzESXNAnQabtlN6cXKLNFGWmilztaEZ
+zHFEEDqtqEeP5BmEkjnNFakhr49yrK/SZpVYYScsUHNcMDOwXMPKKxkk3qOKUlqQ
+m+WvvyaWwZ8kbZNkO+YY4BpOVDEQRH3HG7ngtLP3y/LSx7kcpTl0wi8On9pigZ46
+4u9Odac7bNh5FVMOFiXbIXMuFJ1epy9agtX09eOK9JC9ZHnIl9IgGEL833d+l8MQ
+OdeKqvSTnNf0JKZs52uzUqluI4pywNIiJT1OeU3Ch2vdfvcOcTQohGhIEUb7NWEn
+Z02pBiZ0Tzg/T/sr8B60rPcRSSnGYAfQZPS86QgAMIEaqoHQVox+Qgigr2dynvWQ
+KO0ny4iJLhtRoxD7W7LxaZat6fWvbC4WXKwgis2UJnEWwe8DsoZ3XEnYid3ke2/F
+2+ejHtugd6E4o05vRAO/pOVZUbc4mbZm2zoPgzwm0M9GqQcTOVy1tkpEc6mhrH6y
+AjbSeP/1YkZakDB9bl+TCw24lRquIUbGyNI9GM+oFYyciIYu6RfPhfoh9YU9wqS7
+KcnHk3WG5AxJZ6tZAsz/i/DhtD5zxFsldKMy5OaeBjh6txmQ0aHXPYLmIEdxPSa0
+BnrAeTam/1iOboXT1O2GkPiLJAJjWRAvK/QXsxJMGtSYEnwT+kkAZtU75FOyBuxI
+gQOHO98CGGEy5CI/e/Th+1yMqyiimqDmfVktN4KgzZXbjyv4xzpgNE6o85v7fUg1
+1WoTF38GmbcIbVfBHaRwdWmSxeAsFltHb38u7pP8D24+3hC5OKsNLXxGFJhfp3HW
+4A51fNAi3CaWIh9aZb+SYof8MmtZp9mjL0dKB1OZTyTXqkQ/yU3PIHE+ytM6men3
+tzQefsJlnk/t/zBhxdZo+yCna/ZJe3KmrShoD4ob9prWs48ohDhG7cY4vfW0NuxX
+Im30y+JZe3A0Ktamq1VmpWDhW6Emo+Wk04nDTtU2AcClMoKWgcvIUp0HAgWDhrrx
+6Rf1CiRZngJuCEheCMAvKT8IgcTt7vWRUpRoJrhlKR23GKEVA6T6DF9q8L4F82ul
+yzQ2kdec6s8bD1EQT7e0QcIqEn8RNoSa2aLxRG2JBWqv0AZflKyCjwx35mCv7AmN
+17zPENkGdDgE9xcEEdEe4rkigJ8uaYauEofQsDgSHWQ2oAk91g5FLNImsRn3Z7uy
+qtjc9JTXOymowDXbdYebqNZTxJhXDUnrbawWqyCgohtytnVdW61wKsXV3oEp7PZc
+aL0ZZvVkVkUofaf/2gIGvQ9WnTfpUJ/bEAr0hDdtnmg1QPVu/v/l9eXAnbkh71GX
+OdFpIfvf0zVm/tn/7Vi+HfHRjupHSJBO+bIVQBt3uMLlovU48axSRXUnpIXdHYwN
+3b/MabDQnzV1qvhGTRRj+o39RbDrueNSpXIleA7SzFqZO2LnaDZ/FKbca1CAL+Iy
+KsByJUz7cReE4gU8N3SKKv7ivaibmFG63cvwA1GMUsWZgIYD/xy2nCB4jPCodiFB
+wWUgAblxMcaidKox6Z1zK9oZA62QIX4XGl+0mE/xwxmt2fE5lSBhY8q7N7MvS/Nv
+RDLEgULBMRvcjgIwL4MZZYkUl7oAqQ2aHL45j7vp7U9vIK2j8bcp9v5K3jeccENJ
+qYn/35awflQ66lrrSUWH3evs5IO0FhKSn9c1GGTaZDA5TABzJKySmvphyAh1m24W
+ylzgXQkp/Zi2oWajZ50txiIBgB14dWhuCS4vcwkWjyKur/yGpkY5DILSHZyguqDk
+cwOgKRrC5myL7Zr0/5CwN6sBofOGb2Qhcx+4Cg6DnPwyMXKVTKYnXPvcTj2KvVW+
+qzTIhur0rwC13lK1+sMs/+Esel99fohCTz7+tiebqkPaFcnx8hUgMrllYfGCMXQ/
+hiY+1BVEyDWHWOjj5lxPwzNtPqbVz3uA//Rut/BozOpD/hEjCMVRbadSFZTM+x6+
+nr39DHGZsOn3XSEZSU2C93v77Ls/vdJi6IdJTwCD/yoEK1OY60ciBvCpJ/lLVSQl
+PAdozKEAjqP+fX95MeAENtysXTsqbWawWeS7H/zXmwpWn3LnSJ48pymvwV6nJrYL
+YXHaiuMGIZim9jOT04AyK5LPZ2E9omwVFaHgi10iRigc9y5MixHvN9P5WQ9nP+/c
+kflFQXuph3RqBEWfG8vPFKFvqVQTN0lxUXoOXkKhZ7/Doe3orbfRw/xZUyKnfrzs
+O7MU7PVAubVNUXjKHs2UCYtVMUH0tSuDMkuWUd6d6cmkPht86KvopoXha6zRw1pu
+SE/nGq/o6bproWMPRfoCCN+xow9jeipTD7xkYbiw1+g/9hYaqUYm7QUbuYOrBtLe
+RAlm69yornWrh7+n3GXUSCFQz3nj6sE4JjXciQzhUfEFrCn+qDrgaviFKe0yVYbQ
+LRr3qtSM0gTVWn2XnOlnNuLdvx2UWV7KNNPrnRIoK3KvnfOJTl/RzkT1g+HaEvC3
+MFNlSJNXN6Gavdr8pAdtnVGivdnM1iw0yFKuuzoF1F+cuN8rGMmp
+
+
diff --git a/demo/smime.howto/se.py b/demo/smime.howto/se.py
new file mode 100644 (file)
index 0000000..cd4df60
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+def makebuf(text):
+    return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load signer's key and cert. Sign the buffer.
+s.load_key('signer_key.pem', 'signer.pem')
+p7 = s.sign(buf)
+
+# Load target cert to encrypt the signed message to.
+x509 = X509.load_cert('recipient.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Set cipher: 3-key triple-DES in CBC mode.
+s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+# Create a temporary buffer.
+tmp = BIO.MemoryBuffer()
+
+# Write the signed message into the temporary buffer.
+s.write(tmp, p7)
+
+# Encrypt the temporary buffer.
+p7 = s.encrypt(tmp)
+    
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/sendsmime.py b/demo/smime.howto/sendsmime.py
new file mode 100644 (file)
index 0000000..6df9a89
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""S/MIME sender.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import smtplib, string, sys
+
+def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+
+    msg_bio = BIO.MemoryBuffer(msg)
+    sign = from_key
+    encrypt = to_certs
+
+    s = SMIME.SMIME()
+    if sign:
+        s.load_key(from_key, from_cert)
+        p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+        msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+
+    if encrypt:
+        sk = X509.X509_Stack()
+        for x in to_certs:
+            sk.push(X509.load_cert(x))
+        s.set_x509_stack(sk)
+        s.set_cipher(SMIME.Cipher('rc2_40_cbc')) 
+        tmp_bio = BIO.MemoryBuffer()
+        if sign:
+            s.write(tmp_bio, p7)
+        else:
+            tmp_bio.write(msg)
+        p7 = s.encrypt(tmp_bio)
+
+    out = BIO.MemoryBuffer()
+    out.write('From: %s\r\n' % from_addr)
+    out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+    out.write('Subject: %s\r\n' % subject) 
+    if encrypt:
+        s.write(out, p7)
+    else:
+        if sign:
+            s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+        else:
+            out.write('\r\n')
+            out.write(msg)
+    out.close()
+
+    smtp = smtplib.SMTP()
+    smtp.connect(smtpd)
+    smtp.sendmail(from_addr, to_addrs, out.read())
+    smtp.quit()
+
+    # XXX Cleanup the stack and store.
+
+
+msg = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] - 
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    sendsmime(from_addr = 'ngps@post1.com', 
+                to_addrs = ['popuser@nova.dyndns.org'],
+                subject = 'S/MIME testing',
+                msg = msg,
+                #from_key = 'signer.pem',
+                from_key = None,
+                #to_certs = None)
+                to_certs = ['recipient.pem'])
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime.howto/sign.p7 b/demo/smime.howto/sign.p7
new file mode 100644 (file)
index 0000000..7a39eaa
--- /dev/null
@@ -0,0 +1,46 @@
+From: sender@example.dom
+To: recipient@example.dom
+Subject: M2Crypto S/MIME testing
+MIME-Version: 1.0
+Content-Type: multipart/signed ; protocol="application/x-pkcs7-signature" ; micalg=sha1 ; boundary="----2221986D060512556B8AC18AAA106F39"
+
+This is an S/MIME signed message
+
+------2221986D060512556B8AC18AAA106F39
+a sign of our times
+------2221986D060512556B8AC18AAA106F39
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+
+MIIE8AYJKoZIhvcNAQcCoIIE4TCCBN0CAQExCzAJBgUrDgMCGgUAMCIGCSqGSIb3
+DQEHAaAVBBNhIHNpZ24gb2Ygb3VyIHRpbWVzoIIC5zCCAuMwggJMoAMCAQICAQAw
+DQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRYwFAYDVQQDEw1TL01JTUUgU2VuZGVyMSEwHwYJKoZIhvcNAQkBFhJzZW5kZXJA
+ZXhhbXBsZS5kb20wHhcNMDEwMzMxMTE0MDMzWhcNMDIwMzMxMTE0MDMzWjBbMQsw
+CQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBT
+ZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA5c5Tj1CHTSOxa1q2q0FYiwMWYHptJpJcvtZm
+UwrgU5sHrA8OnCM0cDXEj0KPf3cfNjHffB8HWMzI4UEgNmFXQNsxoGZ+iqwxLlNj
+y9Mh7eFW/Bjq5hNXbouSlQ0rWBRkoxV64y+t6lQehb32WfYXQbKFxFJSXzSxOx3R
+8YhSPd0CAwEAAaOBtjCBszAdBgNVHQ4EFgQUXOyolL1t4jaBwZFRM7MS8nBLzUow
+gYMGA1UdIwR8MHqAFFzsqJS9beI2gcGRUTOzEvJwS81KoV+kXTBbMQswCQYDVQQG
+EwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIx
+ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbYIBADAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBBAUAA4GBAHo3DrCHR86fSTVAvfiXdSswWqKtCEhUHRdC
+TLFGl4hDk2GyZxaFuqZwiURz/H7nMicymI2wkz8H/wyHFg8G3BIehURpj2v/ZWXY
+eovbgS7EZALVVkDj4hNl/IIHWd6Gtv1UODf7URbxtl3hQ9/eTWITrefT1heuPnar
+8czydsOLMYIBujCCAbYCAQEwYDBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJD
+cnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNl
+bmRlckBleGFtcGxlLmRvbQIBADAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzEL
+BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAxMDMzMTE0NTgyMVowIwYJKoZI
+hvcNAQkEMRYEFOoeRUd8ExIYXfQq8BTFuKWrSP3iMFIGCSqGSIb3DQEJDzFFMEMw
+CgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsO
+AwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIGAKMjEdSteeZg9ji1C
+Y2r6zuxDVVftpSLy9ZhYOblGYY2b3PhzI0XytdUoLdb+GlPImqE/F5FJCPvTAovq
+uLElbx/FuijA2ly7PQF2ekyFJ0EhvNWT1gcLhi1m0W+Hk/jgCQx7YRojT53Pa9fj
+zuHk7ZkSKe5rZlOJvPJtYREnvD8=
+
+------2221986D060512556B8AC18AAA106F39--
+
+
diff --git a/demo/smime.howto/sign.py b/demo/smime.howto/sign.py
new file mode 100644 (file)
index 0000000..38c4bab
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME
+
+def makebuf(text):
+    return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object; set it up; sign the buffer.
+s = SMIME.SMIME()
+s.load_key('signer_key.pem', 'signer.pem')
+p7 = s.sign(buf)
+
+# Recreate buf.
+buf = makebuf('a sign of our times')
+
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7, buf)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/signer.pem b/demo/smime.howto/signer.pem
new file mode 100644 (file)
index 0000000..9189aaa
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4zCCAkygAwIBAgIBADANBgkqhkiG9w0BAQQFADBbMQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkq
+hkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTAeFw0wMTAzMzExMTQwMzNaFw0w
+MjAzMzExMTQwMzNaMFsxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEW
+MBQGA1UEAxMNUy9NSU1FIFNlbmRlcjEhMB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4
+YW1wbGUuZG9tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlzlOPUIdNI7Fr
+WrarQViLAxZgem0mkly+1mZTCuBTmwesDw6cIzRwNcSPQo9/dx82Md98HwdYzMjh
+QSA2YVdA2zGgZn6KrDEuU2PL0yHt4Vb8GOrmE1dui5KVDStYFGSjFXrjL63qVB6F
+vfZZ9hdBsoXEUlJfNLE7HdHxiFI93QIDAQABo4G2MIGzMB0GA1UdDgQWBBRc7KiU
+vW3iNoHBkVEzsxLycEvNSjCBgwYDVR0jBHwweoAUXOyolL1t4jaBwZFRM7MS8nBL
+zUqhX6RdMFsxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEWMBQGA1UE
+AxMNUy9NSU1FIFNlbmRlcjEhMB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUu
+ZG9tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAejcOsIdHzp9J
+NUC9+Jd1KzBaoq0ISFQdF0JMsUaXiEOTYbJnFoW6pnCJRHP8fucyJzKYjbCTPwf/
+DIcWDwbcEh6FRGmPa/9lZdh6i9uBLsRkAtVWQOPiE2X8ggdZ3oa2/VQ4N/tRFvG2
+XeFD395NYhOt59PWF64+dqvxzPJ2w4s=
+-----END CERTIFICATE-----
diff --git a/demo/smime.howto/signer_key.pem b/demo/smime.howto/signer_key.pem
new file mode 100644 (file)
index 0000000..ad15a87
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDlzlOPUIdNI7FrWrarQViLAxZgem0mkly+1mZTCuBTmwesDw6c
+IzRwNcSPQo9/dx82Md98HwdYzMjhQSA2YVdA2zGgZn6KrDEuU2PL0yHt4Vb8GOrm
+E1dui5KVDStYFGSjFXrjL63qVB6FvfZZ9hdBsoXEUlJfNLE7HdHxiFI93QIDAQAB
+AoGBALqc4OgZSbYPjQyTfpD1IJTKLgqsgCR5aE0kR7WZuG7MDt/e3ktWn0ebsgFv
+2J12u2bD+yqM++dVbK7WtvTR+QpMhb/0XMhXNsvmn5gOLdKlJjS0RXDDs2DzIS6p
+JNzAmn5zqTVteZAMDLk7ygkO++iGzwRz713ZgxRaKr5YWiLVAkEA+3ev1TTXNEOk
+wQ9fbukMrfUXesqwgrx9VZ1z1X5we42RIIMTYI1edpcujXYvgS3/jdzAWDS1Nqta
+9QB3uy91ywJBAOnysIIQhHn+4zvaOA5vh85WczPhN9C+yRmV70eyL9h+aThUFS4c
+kg2jQOLp8MaxAkmk4xRbZBgehjmDr45b5fcCQQDpIGNlYFBmhpN129+YffugRgjX
+cJNFEKONPKRHd6mmEW9K2dmb+FNr0+p3gOq3csJpbQ7wdyTMov13B1D4ux4TAkAR
+URB1oCleKlrBjGaH0wOXZ1jBp1MNVYHnLez3Pp5CBSFetQKYVi8NaV8dLLnQyztj
+Hhxc3mLrUh8XVMMC45SDAkEAxRCKmkneLceIdwixLIUF0zr0OzJtgyhxMxvHu3ET
+gJcZqNN0y3EgPwcNihpBw7rjpp5e5sjlRNVqLqn8a5/Fog==
+-----END RSA PRIVATE KEY-----
diff --git a/demo/smime.howto/verify.py b/demo/smime.howto/verify.py
new file mode 100644 (file)
index 0000000..9430add
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load the signer's cert.
+x509 = X509.load_cert('signer.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Load the signer's CA cert. In this case, because the signer's
+# cert is self-signed, it is the signer's cert itself.
+st = X509.X509_Store()
+st.load_info('signer.pem')
+s.set_x509_store(st)
+
+# Load the data, verify it.
+p7, data = SMIME.smime_load_pkcs7('sign.p7')
+v = s.verify(p7)
+print v
+print data
+print data.read()
+
diff --git a/demo/smime/README b/demo/smime/README
new file mode 100644 (file)
index 0000000..736e632
--- /dev/null
@@ -0,0 +1,54 @@
+ 29 Nov 2000
+-------------
+- test.py works.
+
+- makesmime.py is sendsmime.py modified to not send
+  SMTP, because I do not now have an SMTP server handy.
+
+- sendsmime.py should still work, because makesmime.py 
+  does.
+
+- unsmime.py doesn't work, possibly because the certificates
+  used to generate the tested PKCS7 objects are no longer
+  available. 
+
+
+
+ 20 Nov 2000
+-------------
+
+This directory contains various programs and supporting files 
+demonstrating M2Crypto's S/MIME functionality.
+
+- test.py exercises the various S/MIME functionality. 
+- sendsmime.py (optionally) signs and/or encrypts a message, 
+    then sends the output via SMTP.
+- makesmime.py is exactly like sendsmime.py, except it writes
+    its output to sys.stdout.
+- unsmime.py decrypts and verifies an S/MIME SignedAndEnveloped 
+    message. It handles the S/MIME output of Netscape Messenger 
+    successfully.
+
+- ca.pem is M2Crypto's test CA certificate.
+- client.pem and client2.pem contain user certificates and their 
+    corresponding private keys.
+
+- clear.p7 is a clear-signed S/MIME message.
+- opaque.p7 is a signed S/MIME message.
+- ns.p7 is a clear-signed S/MIME message produced by Messenger.
+- ns.se.p7 is a signed-then-encrypted S/MIME message produced 
+    by Messenger.
+- m2.se.p7 is a signed-then-encrypted S/MIME message produced 
+    by sendsmime.py.
+
+
+I tested with export and strong versions of Netscape Communicator 
+4.7x.
+
+I have also done some interoperability testing with Sampo Kellomaki's 
+smime tool.
+
+I am interested in interoperability testing with Outlook and other 
+S/MIME tools. Write me <ngps@post1.com> if you want to collaborate.
+
+
diff --git a/demo/smime/ca.pem b/demo/smime/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/smime/clear.p7 b/demo/smime/clear.p7
new file mode 100644 (file)
index 0000000..c878407
--- /dev/null
@@ -0,0 +1,67 @@
+To: ngps@post1.com
+From: ngps@post1.com
+Subject: testing
+MIME-Version: 1.0
+Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha1; boundary="----F0DD27AD8CDFF9AB55B87EE3A626E6D7"
+
+This is an S/MIME signed message
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7
+
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] - 
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+
+MIIHHAYJKoZIhvcNAQcCoIIHDTCCBwkCAQExCzAJBgUrDgMCGgUAMIICQwYJKoZI
+hvcNAQcBoIICNASCAjANClMvTUlNRSAtIFNlY3VyZSBNdWx0aXB1cnBvc2UgSW50
+ZXJuZXQgTWFpbCBFeHRlbnNpb25zIFtSRkMgMjMxMSwgUkZDIDIzMTJdIC0gDQpw
+cm92aWRlcyBhIGNvbnNpc3RlbnQgd2F5IHRvIHNlbmQgYW5kIHJlY2VpdmUgc2Vj
+dXJlIE1JTUUgZGF0YS4gQmFzZWQgb24gdGhlDQpwb3B1bGFyIEludGVybmV0IE1J
+TUUgc3RhbmRhcmQsIFMvTUlNRSBwcm92aWRlcyB0aGUgZm9sbG93aW5nIGNyeXB0
+b2dyYXBoaWMNCnNlY3VyaXR5IHNlcnZpY2VzIGZvciBlbGVjdHJvbmljIG1lc3Nh
+Z2luZyBhcHBsaWNhdGlvbnMgLSBhdXRoZW50aWNhdGlvbiwNCm1lc3NhZ2UgaW50
+ZWdyaXR5IGFuZCBub24tcmVwdWRpYXRpb24gb2Ygb3JpZ2luICh1c2luZyBkaWdp
+dGFsIHNpZ25hdHVyZXMpDQphbmQgcHJpdmFjeSBhbmQgZGF0YSBzZWN1cml0eSAo
+dXNpbmcgZW5jcnlwdGlvbikuDQoNClMvTUlNRSBpcyBidWlsdCBvbiB0aGUgUEtD
+UyAjNyBzdGFuZGFyZC4gW1BLQ1M3XQ0KDQpTL01JTUUgaXMgaW1wbGVtZW50ZWQg
+aW4gTmV0c2NhcGUgTWVzc2VuZ2VyIGFuZCBNaWNyb3NvZnQgT3V0bG9vay4NCqCC
+AxAwggMMMIICdaADAgECAgECMA0GCSqGSIb3DQEBBAUAMHsxCzAJBgNVBAYTAlNH
+MREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNV
+BAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYO
+bmdwc0Bwb3N0MS5jb20wHhcNMDAwOTEwMDk1ODIwWhcNMDIwOTEwMDk1ODIwWjBZ
+MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAWBgNVBAMTD00yQ3J5
+cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjDY71gWNeXWX28qsBJ
+0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFMcQhEeJ1x9d+8Rzag9yjCiutYKOMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAKy2cWa2BF6cbBPE4ici
+//wOqkLDbsI3YZyUSj7ZPnefghx9EwRJfLB3/sXEf78OHL7yV6IMrvEVEAJCYs+3
+w/lspCMJC0hOomxnt0vjyCCd0JeaEwihQGbOo9V0reXzrUy8yNkwo1w8mMSbIvqh
++D5uTB0jKL/ml1EVLw3NJf68MYIBmjCCAZYCAQEwgYAwezELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5u
+Z3BzQHBvc3QxLmNvbQIBAjAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzELBgkq
+hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAyMTIwODE3NTIyMVowIwYJKoZIhvcN
+AQkEMRYEFI/KcwJXhIg0bRzYLfAtDhxRMzghMFIGCSqGSIb3DQEJDzFFMEMwCgYI
+KoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIH
+MA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABEBrhZ8JVqGZ+S7dh+xKDwbF
+yRXiOQWEa2uxD1O5fD02VfGEzDSrV1sPdQ8AcM3o+ny5AyC11E4Fns2cIkXwZEwz
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7--
+
diff --git a/demo/smime/client.p12 b/demo/smime/client.p12
new file mode 100644 (file)
index 0000000..b7b985a
Binary files /dev/null and b/demo/smime/client.p12 differ
diff --git a/demo/smime/client.pem b/demo/smime/client.pem
new file mode 100644 (file)
index 0000000..0eeeefb
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAnWgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTgyMFoXDTAyMDkxMDA5NTgyMFowWTEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9NMkNyeXB0
+byBDbGllbnQxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZI
+hvcNAQEBBQADSwAwSAJBAKM981BdHZsUlPtX7KPnk3Zow2O9YFjXl1l9vKrASdFB
+ZquCQrcOxr+HydE2YEB1SK14Cq1L87KrrvHolY7j/zcCAwEAAaOCAQQwggEAMAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBTHEIRHidcfXfvEc2oPcoworrWCjjCBpQYDVR0jBIGd
+MIGagBT7hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAP
+BgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMb
+TTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3Bz
+QHBvc3QxLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQCstnFmtgRenGwTxOInIv/8
+DqpCw27CN2GclEo+2T53n4IcfRMESXywd/7FxH+/Dhy+8leiDK7xFRACQmLPt8P5
+bKQjCQtITqJsZ7dL48ggndCXmhMIoUBmzqPVdK3l861MvMjZMKNcPJjEmyL6ofg+
+bkwdIyi/5pdRFS8NzSX+vA==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAKM981BdHZsUlPtX7KPnk3Zow2O9YFjXl1l9vKrASdFBZquCQrcO
+xr+HydE2YEB1SK14Cq1L87KrrvHolY7j/zcCAwEAAQJAUkHlWZmSUZMNf5nOpMkM
+hZ5E1v2Wjy4UFgRGDcTXbZm401Avwrc8006qQxQjoH94pVkwPYjEyAMubhusMFt4
+AQIhANeB4qzW/YsABYpt66x1ByiuUJE1p+QMLngeESJbc989AiEAweoMtXsNRJi4
+1xBzlKB9zljQfESYIt59SctURPfX/4MCICS9kwyOdplM/qTUCprTNM49saSf9iiN
+3xpBXgBygPWtAiEAmoIeDEB26vBxX1OJdKSIeXFE9a9GNYpn8/OiOq3smncCIGIg
+Hj9MtdZ/1uPk7jx5oZVfzN1DM7GYZHAeYeYhVR13
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBEzCBvgIBADBZMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAW
+BgNVBAMTD00yQ3J5cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0
+MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjD
+Y71gWNeXWX28qsBJ0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwID
+AQABoAAwDQYJKoZIhvcNAQEEBQADQQAQho0UsOhmuUUhJrx7uXIEbo984OrOVpa6
+giAU7socmyjCzJvihmr/Nnqub+Md7rkSfDytGDN6CianGL5MROjr
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/smime/client2.pem b/demo/smime/client2.pem
new file mode 100644 (file)
index 0000000..166919a
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAnegAwIBAgIBAzANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMTEyMDEzMDMwNVoXDTAyMTEyMDEzMDMwNVowWzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRowGAYDVQQDExFNMkNyeXB0
+byBDbGllbnQgMjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAn9qneRYTKPokme3obiJa2NTz1Z2kcF3NHVh60Qod
+/TV/q4olPrZdFR2TDWt63Lgnygcsgf3u9pnhcEGk6IvntwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFDKWFe6VWMhtRTE3/78+hAnSGxmvMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBABpE9xt1Hlq2dQZUXHuX
+HI57vc2mlWnhhM0wnNhsNZFwfXRHCZOo/JJBhEIT3Rgyz0ErrbOr1SN96HNDKXOD
+z6bh4NxB5DZ9sRPKEBj66zDsWJVMlom+Lkeal+GkVy36vpAyP1r+cTXyc9M2Gw/o
+FBMinMHH/BXvF5GJ+UleheZe
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAJ/ap3kWEyj6JJnt6G4iWtjU89WdpHBdzR1YetEKHf01f6uKJT62
+XRUdkw1rety4J8oHLIH97vaZ4XBBpOiL57cCAwEAAQJANXfspprUo9MvpPEn2pbR
+Lk/kk2IcW510e0laI0uwBj50djfHqvsU5ccuVLrxowngLGrFmM3G4lnMknR2NvH8
+0QIhAMsK0AwStUNM/KyvIMikHHBOE9PrK7ARgKvlKl+0ieWPAiEAyYwonIVAtr1f
+M8vmrc6TM2YxzSq4+jyYktaaNhYw11kCIA5pmhMBUPSSBm2LkNwtKgeewzGLw/If
+i+6nubZJbnBpAiEAvJQvy4PCsTkvQr+d7zJB+O2920IGId1gxMOXNtQ8jsECIGvn
+Uz54oonshmTg+Kj2DxnUKQEzFAmQLbtFslp1m47v
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBFTCBwAIBADBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGjAY
+BgNVBAMTEU0yQ3J5cHRvIENsaWVudCAyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBv
+c3QxLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCf2qd5FhMo+iSZ7ehuIlrY
+1PPVnaRwXc0dWHrRCh39NX+riiU+tl0VHZMNa3rcuCfKByyB/e72meFwQaToi+e3
+AgMBAAGgADANBgkqhkiG9w0BAQQFAANBAHI5KXfL6kIRoNjR8G9/uKiPUt4uVBKF
+ecGp87M5t2a92Z0KpWOMXSHZ0LLQKqwWzALvWcPPIj6S8F6ENdwpfMk=
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/smime/m2.se.p7 b/demo/smime/m2.se.p7
new file mode 100644 (file)
index 0000000..3a5bb1b
--- /dev/null
@@ -0,0 +1,84 @@
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIAGCSqGSIb3DQEHA6CAMIIOBwIBADGCAVkwggFVAgEAMIG9MIG3MQswCQYDVQQG
+EwJTRzEvMC0GA1UEChMmRmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0
+ZSBMdGQxMjAwBgNVBAsTKUZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5MSQwIgYDVQQDExtGYXN0IFNlY3VyZSBDaGVhcCBLZXlNYXN0ZXIx
+HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tAgEDMA0GCSqGSIb3DQEBAQUA
+BIGAaH1UOF0vZjCfEDbzWAy+kni/T0AVVbTdWeCZAaADIgNrUqVJKkV8BIrAFRUO
+jNdz23gra1TRgoe+sq4qn9WBi61+f+8JUV4UHZYl4DdcIkcGvdEI7utPLFNFif2m
+oqd+kr39wuGWMonoBpzFdycSN/FRpFTrwoyXEIHj9VPCwvUwggyjBgkqhkiG9w0B
+BwEwGgYIKoZIhvcNAwIwDgICAKAECCZMYhSx21tlgIIMeGyY4Sgm4uv0a0Zu5gnx
+x1bA6wPY2W8oOqFC3jEAc6j0aP9TzxFW33W5fT/wCWCuIaS8Bm2nCozt7yhBofV2
+9mEtlahbEV1KeqwgI/RteTHD2WUvz6277O9lsB/xsreKM63WzxkpTWi0BUQBDjC1
+ncwy/74ujmZhdAD28HJie5cqhxKQd8if5/Ild3B786sxpGaSBKbJAtJYG30imbHL
+B7JTqDciCKQZzFeQBc1TOcld9VqFP2dRexmcPMxcyJN1iJe8R0g6PYpfAmLfmN6B
+fAImcgJYBpq8PYzmTi7cpJCMmzlPn7urm8Nx4DUQtzzMArmSuMfWGhu2wWLM+2Mn
+TB1cAVuO0bGgsHnr0BKW5gfQEJEIZY41Kq179RKAzI6OdJYvnKXoadEtzYI07Tup
+D+HJXLdAuU/B6E+09IS8mg2RydNyRP+lp+sOVve6L4LAH8h5pt7hoES5/qgiew09
+FPpx5xmhcGCTHJ9Ad2Xec6zAuYkVWAUfLpiy2A2NDTZnQ6EWaeWHtoAizMjHlqQY
+ZJYeXINzE4/u9ph5DTvXV2Sd57eOoaaaHmWwoV/887cdW33cQQ8LXciuzduI/b4V
+LWFm3X9eVdGEeQFoNDP5D7xwEK67flhufYClB99lt3tCfRLvvcclsd39heOex5/M
+6XeA8TeEbuukMhEe6bW2o+E736XNpziFm7hL1UGBXqbv26sN3A30H9LRMXGlZZxj
+raGBl8uxzUPi/SxERMbRrYC72x3Dev5m5I458wXEyc94GIqDqcHL0Q+za+3iyC2g
+496OsVeqtob9oshXr3rI0FjV+ejNMBcnhWeaPKLl6MOnqI76kczARZR8WrO+Dyy9
+FpgwX/DNKstuw0rLzzN7Zb+J4NPIhlHDt2Hh3KLjoFAiXPSDugKGQiceV6mg0IuS
+8HPq8rTiKFEnFtPTWxvAKIOa3dKaOiBoCue1Qm+ffV/2AB4Q4Xf0SpzgDBa6ruRF
+MImgcdeNjzu5MggGGHQtYU1uMMqhX4o2GIafVmAO5zUVYHsbQ2Lht+/x+m8nBamV
+H8AUzJQBDL10bbmZW8fh4JqwxXsdmoTKSSSKEk1+mP55DeBnP+GPA1EGbZWoI+mO
+FoHd1QKDedI/r8o9y1SkfhPxrKl0XKG+VLIUmmP+qev/7s3d5PEfg+ShkAAf2ccP
+2o+poSQKHYeOVM/RxLjS7AR96MtMl+46Cyinbl1quRjYLh84iQSP9RNVhVM06fi5
+63RHv8BAlJRnEcyiFNV/uDBYTFQ/2Gf8V9gBjYWyajnISsTsD2ZQ93jT/sjL6KpH
+Ouv7LQplVrxkZHXQgOoqd9fI1QR8qyBjoMUivv0DSKPKZo0MT/KfqUxG+DsFgOi/
+a7WW9J+XA7N2hGiP3CEqmm1pPi6IUtQj5kGmjlcEtco9syTwPSh5w+tcmVtQeu1+
+wwg53wSLHUdRUmVGqyCVV55ItkirSQt37JCDkXoivCfbdi5BI7cQ6IBhRvpBw2uV
+IA0O9/AfhXhUred974WInrjjlo0RQ4rGZutwDYWrPzWjzuPPvomih6rKQ0HH1bgb
+vdAZ+UjXtfSdory36LMg9Yb/oFejm/77NRXAo9AAf2MQvHVN00zKzDoMm4CRiJUc
+ZCC2+3ocFEs9l6jOO2tPrQpH+E69XXyqQcer18FyM3rTv81O2SbOz0IfL9vBFEsL
+ZZVifPKLt9LfRRjoqehXTxkmfQ2NmHUyksHO4aE2/OrtMVXBv35WFzLPIsX9C+yX
+r6+QPQrkG2Uad2e0YM0ipNPk4OIy5Bb+Ab6fIbHvLgLBRv0yBZ+e9OoEZ1UFC6sR
+EncKSV1fkRlemGgG2cEP6Ceq+x0zbS4A97nqQO5Hks5Hv5PqVuSaWSIjAEFihmpt
+wB41IunnO7b/jzJiNLK28e83uICuYgrPPEeYbbdGrEn65ZzdvhOQduwj0baJRp3V
+0z4boIs7oODYq0IXklvoMzwoaAPbMo2IBamMbZbY7HQbPOeI9QQTkBmKjavFRg/X
+fismhuFuVbyMQPc9uV27xpI2nRSkfN1X2L1lz+e5MMEwHqVRER3tGB1/g+WMrSGV
+ORvXam6fmkIgC4bCSAGBPtqHr2G2rzphsxESw2pbk/7ll8lGuYf3ZxovXJiLb2RB
+9j9m5TFoZ72Asplc04NDKykVOzZGWvNJCCV9zUHTyZ6X4qx+C4P6g58OvyZEf2VB
+arkD40QoEBkhNP6QLfacdFNAegWp6MwJHCDg2YIeLMfgCKkDVP+/SijiT3x7ahHO
+n3ZxKsq1bC6N7McwHB6gzkdpSSqWdtSv+fh5TNlMf7oCH0Laa/2k8S7NDpNbIeqr
+X46ECkmqPTjt5DOuEp3T5q4sOXV2Y3m7FegD2J2WrpXy+/GNgSVB2Kb7xzhR13ve
+Ipu5V+FFa6Q/jbXxp864ZFE6WuZYBYjrkBns2MaF+9RpUSIzyTYxJmB9pY0SwrzX
+2WWnxCCt7cwB1JL0jbrCz0vZvZwDRbtCyepBYel0xqKOhp1dFPCw2p45ZbQJwhKx
+nFMDUzlDZjiAoUdq5oV8TmqcjbGDmLndYawHPqD8m3g+glsCaFI89Dny8NsT0zv2
+vHCt6IbbpVY6j/Pwg9SFzWwnzGr2s5aL5MEbpzEyfiuYAe1hPdue2gWQO1rvdIBB
+zJwdqbrYb3qzJqrqrPQewSpsIzEWJmc5NjATIJOEtgM+Q4+FFTf4f1rZ2HAsK7MG
+aIMZFyjoyvqBHG/IwCh+GBLLIw3IPRJtDcKf/nDZqUCw0wHpTcXu1nHJllNmtNcI
+Xw03k7WctqlxTUBFUjiC6XcU/yAlLZ8UeyLF3+/tSyyV+Ve17cZfAH63WH+oaWL8
+yiC+UH5tpZQWCSBvCz81ezHOBefefOxeIvq5nZyEyP23ltaV/DJqj1a/5pn2VH63
+IrRlQF04ATaUAowxlNIaqD5Sk6tzPKp6nxOO3vP8xRGT1SFKMaNCuJRaZXXQvYcg
+zyiQ0vtli0g8cdbR8dSSTAP7b6CAqb2dYHwtJVUzPzWbWDIpRzrXOdoI/lze+fwA
+xYwAFoTuLLrKgbkOB8HE02ZN4exCp2dcMfwt+GCgYgCtE1/xCHd4RMg3yHxBBDQQ
+uk6dL6dXSD8cw2TqzrJRU8Xk/pLHeQCf694W2flROD/RF3avgcjnJ+0E+CJXmU9c
++uAhFRmdgRzM6G+h2p/MF8HjZ4MZTR6vA4v6mvTLLBbwIUfVNtXIwXpFqQPXVRvA
+4Q8HmLIX2fXGzR2Nw9UU0GBitT4r1isMHCQwVK5kqw3XyH15oIXCCsE1/ftBun8n
+SCQ2OkLHuZ4JWMIYVpTtDn3jd0Gyu6x8hBK+HpX62wnKZZA8mxzMoInAFK08s0Ie
+G3m+bjX7WsdE1eM0TSBvYv2d8CNqaqxJkF/ZlxkAKV7NoP0isuRJSSHH0IcrWNR7
+9mtfxV+ccBUqqO7BmlzLYPjnN+7hOJbYsm0ZJnDZIn6Oc6t8ti+oF4QmUBQ2u7As
+8uBJLbd7TghA7afcrurQAY8gJTq4Q7/lpYZSZK+W0I4fY04YhAfjFuln8r6w9gqr
++Iwg58dbq7MomCsg0F687kApYygyYQeCsio64tb3sXkCmDX2rdRoAELLf3AYccn+
+BW9iru372oWru/kTQTj6Uziv4TiyjUGQEYhtjweTNFTXmYrxYHgb2Hzu6TNIG9Ev
+4ORq9KL1uWSK8zJ6eL9CZ0kSr/4+SBHFbGupfGXz+fZysu3nFJ1oO86vMOFG/UU9
+24+jbRWklu0fyXZfeNzdY6arTLOX5Gpc9PRNZILoFHGyB2M9ZmMYz3hSNzY8acpk
+XDi+2ycHYeWFGxGCLio9Lc2H6IOMqJTTCpUduWvBX4tfysanUNjOuKJ3FWKWLxda
+IX1jJbPjvrQ3bg/NU/MqliLwFiMA1WkJd/paeK//J2tcOIMyhc40sigu4z65VoJ5
+AsMHlvh1dcfjz645NOx6JOpwdd04hoOxlMDtRTE/b8yjwaV2EVqXnI5WkNwCQESU
+ayIJiQqDfn35EFY8Wkmt7f6YGkpqoBDJkyNadsrcd2YVz9paAhgcM3XjC7zPi8AH
+g9UiTQiowRLcvnxkHK0LxelsQTE+ENL0rwYCt+qHBBJlcLVZhDF+qAchv+G5hmxp
+CClPyvbzOsx7ykcvItE2fpBWJu27l48dFxFviOKY7ecvJlpcTAX83N79k0aJpOC+
+TEPMdMZjIeGn004FiVj9Mo1899e2IlOlNdmj8Mv/jW4sFKpJ8m5fHv8i/ax7W33D
+0GNMrEghOH1w+QAAAAA=
+
+
+
diff --git a/demo/smime/ns.p7 b/demo/smime/ns.p7
new file mode 100644 (file)
index 0000000..d2ab1ac
--- /dev/null
@@ -0,0 +1,53 @@
+From: Ng Pheng Siong <ngps@post1.com>
+X-Mailer: Mozilla 4.5 [en] (WinNT; I)
+X-Accept-Language: en
+MIME-Version: 1.0
+To: ngps@post1.com
+Subject: S/MIME signing
+Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha1; boundary="------------msD996346BF03A805B450DC20D"
+
+This is a cryptographically signed message in MIME format.
+
+--------------msD996346BF03A805B450DC20D
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+smime -vs- pgp
+war over?
+--------------msD996346BF03A805B450DC20D
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+MIIF8gYJKoZIhvcNAQcCoIIF4zCCBd8CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCC
+A9cwggPTMIIDPKADAgECAgEDMA0GCSqGSIb3DQEBBAUAMIG3MQswCQYDVQQGEwJTRzEvMC0G
+A1UEChMmRmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0ZSBMdGQxMjAwBgNVBAsT
+KUZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtG
+YXN0IFNlY3VyZSBDaGVhcCBLZXlNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEu
+Y29tMB4XDTk5MDkxNzA1NTAzOFoXDTAwMDkxNjA1NTAzOFowYDELMAkGA1UEBhMCU0cxGDAW
+BgNVBAoTD00yQ3J5cHRvIENsaWVudDEYMBYGA1UEAxMPTTJDcnlwdG8gQ2xpZW50MR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+vp8eCa+KpzNCv0MWyicUImP+WHxlrxm5EummI1Qe77U4w0k8IQuue7QIURWKDjdZI97izzeQ
+SozHvgSsjCvuJlqifMoV0v7U4iUQoZkrXO3hzwM5VNr875M95SYeBjqWDUc0v3R6tka3xhg3
+dMoEL3QR6gsiardPEwygdL7/FN0CAwEAAaOCAUMwggE/MAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTEHU/WosFL
+yRo8NT9ViQ8APbKBXTCB5AYDVR0jBIHcMIHZgBQOTokwY/jsgXmHJoyQ5DmTVN0c8KGBvaSB
+ujCBtzELMAkGA1UEBhMCU0cxLzAtBgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmlj
+YXRlcyBQdGUgTHRkMTIwMAYDVQQLEylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTEkMCIGA1UEAxMbRmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQBN4w1l4IXm
+DJke8XVyo3SFlre1nL/bjDB+hpYCgTQnceHAmSi6L8FljGs2KYGFePWLSCsOxA99q8lVtl7a
+sdoOtYkTluccWGqnWloyusOzlO2xgpoQxZ2iue3PT4Y4u8czMTs2AyanZyd6NdQoaYGfI2g0
+tpgiDhSqcX8r/JYYVzGCAeMwggHfAgEBMIG9MIG3MQswCQYDVQQGEwJTRzEvMC0GA1UEChMm
+RmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0ZSBMdGQxMjAwBgNVBAsTKUZhc3Qg
+U2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtGYXN0IFNl
+Y3VyZSBDaGVhcCBLZXlNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tAgED
+MAkGBSsOAwIaBQCgfTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEP
+Fw0wMDAzMzEwNTExNTJaMB4GCSqGSIb3DQEJDzERMA8wDQYIKoZIhvcNAwICASgwIwYJKoZI
+hvcNAQkEMRYEFDIu42hsp6w5K/x7ejaVgiqgPYk4MA0GCSqGSIb3DQEBAQUABIGAkipjxn6+
+NhIyRPGgrqY9zpDC8AGdt7sGFMKfxhJ+UoSb+lBH0imHSBhA/BnlfysyyDXCpit1Gxy/W/jn
+vHpI0lMLvvcKOtSQp9HUPAtawdeF6Zau8SovSBroUnxxY7DILJoKnaHheHF7G2MbYusyZCmi
+r3xZ4P0Ps3fhNEAmrH0=
+--------------msD996346BF03A805B450DC20D--
+
diff --git a/demo/smime/ns.se.p7 b/demo/smime/ns.se.p7
new file mode 100644 (file)
index 0000000..a7a75f5
--- /dev/null
@@ -0,0 +1,75 @@
+MIME-Version: 1.0
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggKyMIIBVQIBADCBvTCBtzELMAkGA1UEBhMCU0cxLzAt
+BgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRlcyBQdGUgTHRkMTIwMAYDVQQL
+EylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb
+RmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbQIBAzANBgkqhkiG9w0BAQEFAASBgERIsZhX2nmvB4kOtcfqUpuWjK9njs8YixtPSS4w
+ugmAx3LhBMTgY1OlsbKi7etVPwEU2jSqgkwCHPeYeOQt6M9aQ66tcue7/fWjfMBfZ+tetwOb
+kpqlw//GyjhkJx2QBGPuw3Ye84ll1KA7x/CGxmk8g0YBHoLo9Xy1W1XHVUanMIIBVQIBADCB
+vTCBtzELMAkGA1UEBhMCU0cxLzAtBgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmlj
+YXRlcyBQdGUgTHRkMTIwMAYDVQQLEylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTEkMCIGA1UEAxMbRmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbQIBBzANBgkqhkiG9w0BAQEFAASBgKFEdyFcaj2z
+7LBp/rdEiDRNkyvjZtpEc3F1tvml2jmOuRZq/Gd0Ib4ELmAuC2qZ7AG+MNwlMeCQp1PKGjCk
+3Af9hjcOX27c+qF3pSK35WOPrdQTaVLjqdNwejInEoBjGlHa83V1L/MTiD5ABOXGIZKOFS8m
+G0tPxoc3AhlYbHspMIAGCSqGSIb3DQEHATARBgUrDgMCBwQIvFFOv2EOARiggASBsBBa97DM
+xPCX8mZ2K/jjVl2LKtBP0uwG+nd48IiaoBERbJohEdzxTlIU3pj+QZ4ktmSSYo/4Os0eSCfK
+v2mG3d5SPxcE6HztA4FttvQ4IiByTQUU61vJhMoirV5Nvfkxk8i8xPMKMlJSruFBnIOteXQv
+vwVwBCIAkMhxBE9Sj+OfW5966d+LmB3yGEZa9GEW//4VbR6DNs1frEz7riUod0om5CA66bou
+8kXxGTZ5fYydBDDdrsgcU7DBTlw2XayUmYS3Gp0vQvW4l/iiYEeYh+OrKkGYQGdsb5CcZr0F
+FfT2PRAEICA58XUUrt//zi0TzBTwDKAWoJWe/QfOXXEVQsfqCl1iBBhACmC4JLQRSulBCCpA
+F3E3daztLz81ycQEIDyHXxMGZFzZQCvy51fN7eRLl+yACoXKxKtVs6EKpJq9BBDDGI8CWqTZ
+DIMC98l4AV76BIIBAFKj+L6ZG8Sew6IpE5UVq1oS42UrmUDQRrYpZX3OHC8I9pr7mtlU/V0H
+mA55PmixsjcchT6wOF2wVD9G0rBCOlUfuYNTkhR/k51floP9fk9Oy69+jXxUb7r0AF1M2b13
+XQpmnz6YPQPdPxgi8i34iT1oqRY1d4Nntmgl/3xbQ/9MGbgZQSDbduRiy+Zk3JPZzMSVWEVP
+Pt7anOrPmCKDAvlzpX9yKdyE4Ug68s2///S1JjCjGd65007oPXME3S+7WWylQXdjCe8EM/Li
+IE33DcRUFvbuo7q1ftxbwRKn32+Z1gkiG4j+DQ4fTO0RrhnTLUpaaOaX4N3psjSA/3Y+vKYE
+CAkYtbSSwRr5BAhmVVsaf0vm7QQIE77+DXI3Gt0ECIs/MQuPnwd6BAjpq83cshzQ1QQI3Soo
+r1XwQYIECES6t6POe3yxBAgmObWcpGTawQQI+6e28OKubqkESBlD4aGRdfOXkGSw5zN1pVK0
+s37qLWOsO+jClvd4EmfB/JI/ABieNmfBHx2jweIazMA2b9wylRvYp/TMiDFkRl/thDqyj/sC
+7QRIypPg5i7a745PX+HcyHhcy5lUxkpnK5pjAAHSUPTXNc/3H6PpU/8+qiPncfqNXVdoiiSa
+VMSmFMZI+YGDyf4twVuGZbdR5JV2BFCbQNJeSkEhEnZiMKshmNWXbjs4DzYlTO0Pq5D+9RYJ
+1iy/kahcms+2nDNlrVf6jH/lCTpPCM/OJWrjaTdpiFzp/eEY39qbWk5AcIFYQ7xIcARIHVPX
+uhyuO+5BmO0MdMMOL/KlhRtosKHHRG7PH9huyWYFloA4aeoiOxeXkDJk3y0NkG+nJjdM/gWT
+1J2HJi8opt0F0bn8t7MsBEgt2VgTZmkHT/yaJDInQEqrpe9PJ412M+2/kXAwt4VfYrEC3ocQ
+WQuNEpFc1kb6ZGJSYz0TvnW8DbP/GoZqQpF17p1vs1T0QhIESC1qr8SbwXjHjOdAh94Lp5jG
+b8kAl20P0hqqL+5qq661JzAdGotFN7sMOe07Xn3eaYEVe4htVHlIfYMPH2B0CYyHu9LH2ueG
+gwRQ7Dv1hygub3ioK/s9CkgAi83qUENz5RWYMtIiP7tbv34zq6DKwv0ioBY4MEKfR4cCuGzS
+6I67vTvCgXPVxW9qHtoTTDfufUR6yOOa/gvuZt4ESN3fo5Kgpm+74AMeOVR0RZ1lZ+75E9KV
+wmiBGm5ORHGSmyt3MQMW+5UEloz8mxK39O6RtKBL7iKH3d0clJn63KXvUsf2l7/8swRIIVzc
+1jGzpwOsUseKHaeLQuknjTuXAlyCTQYa3C0gEcEBElJY/TMevV+ZrlBB/nCwyaYbmH+WxxfQ
++xF79+eIZ1+L5zGAlSNUBEhO1Gy1T77JUa+s4UUqGP+BNNsGDuR4YXWfCIA2vBfCFZVggkaD
+v1+uX2Rgim39wzMCLkXQ8ZXLWdWo7/XcllOrxJSH84uPat8EULZTtkbY1oW90JzGEJKbOkEA
+YaoQ7I0dKXnkLsY3Y54p1WW7fPQ4JLLhld2LpmZbYOntRJmn8LZWGWv9J3uYlAk9ujAfF10f
+7/qV2zDB//oKBEih8QUPicP+mSdpvRlws5RfiKmcf9W7btoNALqqA2COEOHjjJHdX9b3EVlk
+vftz5n2AbaS7fenhIl1/rbhmBfub3eHwmInRoUEESHcpUKHCEe3dtGe6vB2VXR4U/MvKScZV
+RpHv6KGj0pFZwNasoZ9W5RB0dwAbhuD+xiimlv/m9XwhIQihgTUmarVdEbYcbl1aHgRIFMEp
+bOJ4D49qYR9DXZprlST9nZTgU/R1KTcZe2LsXBNa9xxlO9NYvZCSQSuRndIIwbAXjgezK1d0
+fk+JSIyC8kaQzWBwDGdXBFDA3tUZzKed3JM+R/a8319iAemyyYgvk/izKKXncpOfCdBtVgHc
+cOC7LPs1BGxqGCyOzgD2KMQSclRZJo+siuRvJ2wlKCPjd3DHMk0IJ6EYWgRI3XuDbhKfLzEQ
+h9G5n3wQr8qizabBZ+xZEmJk7AUxA7ykEdSuLlV7qP/ujj9sZ9e/AaHG+mfjGRI01OPquZxW
+Pwt9nRpg1KqCBEjFQqB+hbkfT218E5+mFX/aotUq+tEoZ1Tg3Lq5WENZVwMNR0eFR69vN+gL
+SZU3nnLPEchS3pasz7ZBohL+kEBad4fZfKNWUa4ESO9adCvusxTvjlvf8Gu/lXU17gZuAHsj
+bMwSWyQeZKObCp7kOtK1nQCtLHkWHEpmx16eQWG57+makKYpjD4BSXFXGEwMf3Pw9AQQd6Po
+6JLMr7k5LjkOcyCe/gQIJ2ORlLJDWKMECHz5jFYLkHrLBAhPDugA9fP+MgQIQq/w/QD4YBIE
+CGeYC0G4o7ElBAj3O1FpJvdU+wQIiOZHO6By7rAECN9TZKOJa8YbBDD/7QgWqJSNhck7fNEY
+tHmopW9OWfXfXUInzaQFBtLauNRj0C2OW/zB5dgsC3+9/jYECG3bevzH5HiDBBBHiuTo8yYJ
+1FFJ/y4nIIXaBDC92swMrVUYZUpoNUjf02DsBiVP53Fon+CnKnEQ8gBCncIkb8DBnlZYeZd2
+XISBWmEECNOMqXZKxL2vBBDDg2nHASKLEobcHqmPoIQFBBhcrb6prkWNKColNlf5ss49ocnK
+68UgsNEECNJJEPZOUMW1BBApQ6CzF2t+QiJTdQwoApPzBBg8ZCeSHgypK9CWcWb2iMWiFuKc
+KUmIuskECFr2TQ/U6cWbBAisYHDp2/w4kgQISMieG/GdQlAEEGLN6GdGVz2wf1Hx93PJ4VsE
+EJRdLeaU3FsCwXEICvSKZPMEEIcEdqQkAhmsxfNoKA90QlUECDdUY0tvC48sBBBNOevphvxS
+0dwE2Lzf6gLGBAjoSuhhSYuZBAQIRKWk1TxshoAECFUuzdHGuR3zBBjlvJQjSDrEPqg7unh2
+ZaNy11cUKB07vPMECIbZbuiAcb81BBCyb6N/Aymqo24VwyE9/lTDBCCR0Sn7D8OfmpwUp0MJ
+zEdp+4eTPVu9W6bNj5cTwcG0VQQI9Z1/nYjZ93cECCZ7u+8p0rP8BAg6S5MaaL8mTQQIMt+W
+TF2XgOMEQLOZQ0FOc4oD9mBoXzN/NDmlEz0QWnZPnO/7OhVcuKPi6rtE+5RfdGDL/qxWGU6G
+0qvFT0zTodnwjpzKnYAnoeMEUDPxz4djUsMXhHzpye9vc7N2/B8QUBubRaFPp/FoLv3jjwga
++AawOtkkW1uLku0m6zdN7YYzXTsKlMhwDd3nZS4Vm/v11AnFChiDC2LFmLw0BCC/X36boypP
+pTObDByKlN6IldnnPEi4tl/6Cxr2+28LWgQohCdg9nl3s5COEnwQW+ggcvKodHHK9SdVRscS
+J4ykS0lWZ0ns4/g6/wQIKIXduWxdKJMAAAAAAAAAAAAA
+
diff --git a/demo/smime/opaque.p7 b/demo/smime/opaque.p7
new file mode 100644 (file)
index 0000000..a242446
--- /dev/null
@@ -0,0 +1,47 @@
+To: ngps@post1.com
+From: ngps@mpost1.com
+Subject: testing
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIHHAYJKoZIhvcNAQcCoIIHDTCCBwkCAQExCzAJBgUrDgMCGgUAMIICQwYJKoZI
+hvcNAQcBoIICNASCAjANClMvTUlNRSAtIFNlY3VyZSBNdWx0aXB1cnBvc2UgSW50
+ZXJuZXQgTWFpbCBFeHRlbnNpb25zIFtSRkMgMjMxMSwgUkZDIDIzMTJdIC0gDQpw
+cm92aWRlcyBhIGNvbnNpc3RlbnQgd2F5IHRvIHNlbmQgYW5kIHJlY2VpdmUgc2Vj
+dXJlIE1JTUUgZGF0YS4gQmFzZWQgb24gdGhlDQpwb3B1bGFyIEludGVybmV0IE1J
+TUUgc3RhbmRhcmQsIFMvTUlNRSBwcm92aWRlcyB0aGUgZm9sbG93aW5nIGNyeXB0
+b2dyYXBoaWMNCnNlY3VyaXR5IHNlcnZpY2VzIGZvciBlbGVjdHJvbmljIG1lc3Nh
+Z2luZyBhcHBsaWNhdGlvbnMgLSBhdXRoZW50aWNhdGlvbiwNCm1lc3NhZ2UgaW50
+ZWdyaXR5IGFuZCBub24tcmVwdWRpYXRpb24gb2Ygb3JpZ2luICh1c2luZyBkaWdp
+dGFsIHNpZ25hdHVyZXMpDQphbmQgcHJpdmFjeSBhbmQgZGF0YSBzZWN1cml0eSAo
+dXNpbmcgZW5jcnlwdGlvbikuDQoNClMvTUlNRSBpcyBidWlsdCBvbiB0aGUgUEtD
+UyAjNyBzdGFuZGFyZC4gW1BLQ1M3XQ0KDQpTL01JTUUgaXMgaW1wbGVtZW50ZWQg
+aW4gTmV0c2NhcGUgTWVzc2VuZ2VyIGFuZCBNaWNyb3NvZnQgT3V0bG9vay4NCqCC
+AxAwggMMMIICdaADAgECAgECMA0GCSqGSIb3DQEBBAUAMHsxCzAJBgNVBAYTAlNH
+MREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNV
+BAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYO
+bmdwc0Bwb3N0MS5jb20wHhcNMDAwOTEwMDk1ODIwWhcNMDIwOTEwMDk1ODIwWjBZ
+MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAWBgNVBAMTD00yQ3J5
+cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjDY71gWNeXWX28qsBJ
+0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFMcQhEeJ1x9d+8Rzag9yjCiutYKOMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAKy2cWa2BF6cbBPE4ici
+//wOqkLDbsI3YZyUSj7ZPnefghx9EwRJfLB3/sXEf78OHL7yV6IMrvEVEAJCYs+3
+w/lspCMJC0hOomxnt0vjyCCd0JeaEwihQGbOo9V0reXzrUy8yNkwo1w8mMSbIvqh
++D5uTB0jKL/ml1EVLw3NJf68MYIBmjCCAZYCAQEwgYAwezELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5u
+Z3BzQHBvc3QxLmNvbQIBAjAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzELBgkq
+hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAyMTIwODE3NTIyMVowIwYJKoZIhvcN
+AQkEMRYEFI/KcwJXhIg0bRzYLfAtDhxRMzghMFIGCSqGSIb3DQEJDzFFMEMwCgYI
+KoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIH
+MA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABEBrhZ8JVqGZ+S7dh+xKDwbF
+yRXiOQWEa2uxD1O5fD02VfGEzDSrV1sPdQ8AcM3o+ny5AyC11E4Fns2cIkXwZEwz
+
diff --git a/demo/smime/sendsmime.py b/demo/smime/sendsmime.py
new file mode 100644 (file)
index 0000000..9cc446b
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""S/MIME sender.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import smtplib, string, sys
+
+def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+
+    msg_bio = BIO.MemoryBuffer(msg)
+    sign = from_key
+    encrypt = to_certs
+
+    s = SMIME.SMIME()
+    if sign:
+        s.load_key(from_key, from_cert)
+        p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+        msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+
+    if encrypt:
+        sk = X509.X509_Stack()
+        for x in to_certs:
+            sk.push(X509.load_cert(x))
+        s.set_x509_stack(sk)
+        s.set_cipher(SMIME.Cipher('rc2_40_cbc')) 
+        tmp_bio = BIO.MemoryBuffer()
+        if sign:
+            s.write(tmp_bio, p7)
+        else:
+            tmp_bio.write(msg)
+        p7 = s.encrypt(tmp_bio)
+
+    out = BIO.MemoryBuffer()
+    out.write('From: %s\r\n' % from_addr)
+    out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+    out.write('Subject: %s\r\n' % subject) 
+    if encrypt:
+        s.write(out, p7)
+    else:
+        if sign:
+            s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+        else:
+            out.write('\r\n')
+            out.write(msg)
+    out.close()
+
+    smtp = smtplib.SMTP()
+    smtp.connect(smtpd)
+    smtp.sendmail(from_addr, to_addrs, out.read())
+    smtp.quit()
+
+    # XXX Cleanup the stack and store.
+
+
+msg = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] - 
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    sendsmime(from_addr = 'ngps@post1.com', 
+                to_addrs = ['jerry','ngps@post1.com'],
+                subject = 'S/MIME testing',
+                msg = msg,
+                from_key = 'client2.pem',
+                #from_key = None,
+                #to_certs = None)
+                to_certs = ['client.pem'])
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime/test.py b/demo/smime/test.py
new file mode 100644 (file)
index 0000000..a59cf9f
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+
+"""S/MIME demo.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+ptxt = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] - 
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+def makebuf():
+    buf = BIO.MemoryBuffer(ptxt)
+    return buf
+
+def sign():
+    print 'test sign & save...',
+    buf = makebuf()
+    s = SMIME.SMIME()
+    s.load_key('client.pem')
+    p7 = s.sign(buf)
+    out = BIO.openfile('clear.p7', 'w')
+    out.write('To: ngps@post1.com\n')
+    out.write('From: ngps@post1.com\n')
+    out.write('Subject: testing\n')
+    buf = makebuf() # Recreate buf, because sign() has consumed it.
+    s.write(out, p7, buf)
+    out.close()
+
+    buf = makebuf()
+    p7 = s.sign(buf)
+    out = BIO.openfile('opaque.p7', 'w')
+    out.write('To: ngps@post1.com\n')
+    out.write('From: ngps@mpost1.com\n')
+    out.write('Subject: testing\n')
+    s.write(out, p7)
+    out.close()
+    print 'ok'
+
+def verify_clear():
+    print 'test load & verify clear...',
+    s = SMIME.SMIME()
+    x509 = X509.load_cert('client.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    st = X509.X509_Store()
+    st.load_info('ca.pem')
+    s.set_x509_store(st)
+    p7, data = SMIME.smime_load_pkcs7('clear.p7')
+    v = s.verify(p7)
+    if v:
+        print 'ok'
+    else:
+        print 'not ok'
+    
+def verify_opaque():
+    print 'test load & verify opaque...',
+    s = SMIME.SMIME()
+    x509 = X509.load_cert('client.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    st = X509.X509_Store()
+    st.load_info('ca.pem')
+    s.set_x509_store(st)
+    p7, data = SMIME.smime_load_pkcs7('opaque.p7')
+    v = s.verify(p7, data)
+    if v:
+        print 'ok'
+    else:
+        print 'not ok'
+    
+def verify_netscape():
+    print 'test load & verify netscape messager output...',
+    s = SMIME.SMIME()
+    #x509 = X509.load_cert('client.pem')
+    sk = X509.X509_Stack()
+    #sk.push(x509)
+    s.set_x509_stack(sk)
+    st = X509.X509_Store()
+    st.load_info('ca.pem')
+    s.set_x509_store(st)
+    p7, data = SMIME.smime_load_pkcs7('ns.p7')
+    v = s.verify(p7, data)
+    print '\n', v, '\n...ok'
+
+    
+def sv():
+    print 'test sign/verify...',
+    buf = makebuf()
+    s = SMIME.SMIME()
+
+    # Load a private key.
+    s.load_key('client.pem')
+
+    # Sign.
+    p7 = s.sign(buf)
+
+    # Output the stuff.
+    bio = BIO.MemoryBuffer()
+    s.write(bio, p7, buf)
+    
+    # Plumbing for verification: CA's cert.
+    st = X509.X509_Store()
+    st.load_info('ca.pem')
+    s.set_x509_store(st)
+
+    # Plumbing for verification: Signer's cert.
+    x509 = X509.load_cert('client.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+
+    # Verify.
+    p7, buf = SMIME.smime_load_pkcs7_bio(bio)
+    v = s.verify(p7, flags=SMIME.PKCS7_DETACHED)
+    
+    if v:
+        print 'ok'
+    else:
+        print 'not ok'
+
+def ed():
+    print 'test encrypt/decrypt...',
+    buf = makebuf()
+    s = SMIME.SMIME()
+
+    # Load target cert to encrypt to.
+    x509 = X509.load_cert('client.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+
+    # Add a cipher.
+    s.set_cipher(SMIME.Cipher('bf_cbc')) 
+
+    # Encrypt.
+    p7 = s.encrypt(buf)
+    
+    # Load target's private key.
+    s.load_key('client.pem')
+
+    # Decrypt.
+    data = s.decrypt(p7)
+    
+    if data:
+        print 'ok'
+    else:
+        print 'not ok'
+
+
+def zope_test():
+    print 'test zophistry...'
+    f = open('client.pem')
+    cert_str = f.read()
+    key_bio = BIO.MemoryBuffer(cert_str)    
+    cert_bio = BIO.MemoryBuffer(cert_str)   # XXX Kludge.
+    s = SMIME.SMIME()
+    s.load_key_bio(key_bio, cert_bio)
+    # XXX unfinished...
+
+
+def leak_test():
+    # Seems ok, not leaking.
+    while 1:
+        ed()
+        #sv()
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    ed()
+    sign()
+    verify_opaque()
+    verify_clear()
+    #verify_netscape()
+    sv()
+    #zope_test()
+    #leak_test()
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime/unsmime.py b/demo/smime/unsmime.py
new file mode 100644 (file)
index 0000000..ffc40ad
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+"""S/MIME demo.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import sys
+
+def decrypt_verify(p7file, recip_key, signer_cert, ca_cert):
+    s = SMIME.SMIME()
+
+    # Load decryption private key.
+    s.load_key(recip_key)
+
+    # Extract PKCS#7 blob from input.
+    p7, bio = SMIME.smime_load_pkcs7_bio(p7file)
+
+    # Decrypt.
+    data = s.decrypt(p7)
+
+    # Because we passed in a SignAndEnveloped blob, the output
+    # of our decryption is a Signed blob. We now verify it.
+
+    # Load the signer's cert.
+    sk = X509.X509_Stack()
+    s.set_x509_stack(sk)
+
+    # Load the CA cert.
+    st = X509.X509_Store()
+    st.load_info(ca_cert)
+    s.set_x509_store(st)
+
+    # Verify.
+    p7, bio = SMIME.smime_load_pkcs7_bio(BIO.MemoryBuffer(data))
+    if bio is not None:
+        # Netscape Messenger clear-signs, when also encrypting.
+        data = s.verify(p7, bio)
+    else:
+        # M2Crypto's sendsmime.py opaque-signs, when also encrypting.
+        data = s.verify(p7)
+
+    print data
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    decrypt_verify(BIO.File(sys.stdin), 'client.pem', 'client2.pem','ca.pem')
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/README b/demo/ssl/README
new file mode 100644 (file)
index 0000000..07e43b8
--- /dev/null
@@ -0,0 +1,25 @@
+ 29 Nov 00
+-----------
+
+This directory contains SSL clients and servers written as demos 
+and testbeds.
+
+The oldest ones are s_server.py and s_client.py: these are modeled 
+after the eponymous demo programs in OpenSSL. Once I got them going,
+I moved on to Medusa and other "real-world" servers. The pair has been
+in a state of neglect and quite likely no longer work with the current 
+M2Crypto.
+
+My current testbeds are the echod-* servers and echo.py. These should
+always work. Note that Python on the three platforms that I test on
+are built --with-threads.
+
+
+Python 2.0's httplib introduces HTTP/1.1 functionality and an interface
+substantially different from Python 1.5.2's HTTP/1.0 interface. 
+
+M2Crypto.httpslib provides both interfaces. The demo programs are
+https_cli.py and urllib_cli.py; these have been tested with 
+Apache/1.3.14 (Unix) mod_ssl/2.7.1 OpenSSL/0.9.6.
+
+
diff --git a/demo/ssl/c.py b/demo/ssl/c.py
new file mode 100644 (file)
index 0000000..bd03e6e
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+"""C programming in Python. Have SWIG sweat the pointers. ;-)
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import sys
+
+from M2Crypto import SSL, m2
+
+HOST = '127.0.0.1'
+PORT = 9443
+req_10 = 'GET / HTTP/1.0\r\n\r\n'
+req_11 = 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
+
+
+def c_10():
+    c_style(HOST, PORT, req_10) 
+
+
+def c_11():
+    c_style(HOST, PORT, req_11) 
+
+
+def c_style(HOST, PORT, req):
+
+    # Set up SSL context.
+    ctx = m2.ssl_ctx_new(m2.sslv3_method())
+    m2.ssl_ctx_use_cert(ctx, 'client.pem')
+    m2.ssl_ctx_use_privkey(ctx, 'client.pem')
+
+    # Make the socket connection.
+    s = socket(AF_INET, SOCK_STREAM)
+    s.connect((HOST, PORT))
+
+    # Set up the SSL connection.
+    sbio = m2.bio_new_socket(s.fileno(), 0)
+    ssl = m2.ssl_new(ctx)
+    m2.ssl_set_bio(ssl, sbio, sbio)
+    m2.ssl_connect(ssl)
+    sslbio = m2.bio_new(m2.bio_f_ssl())
+    m2.bio_set_ssl(sslbio, ssl, 0)
+
+    # Push a buffering BIO over the SSL BIO.
+    iobuf = m2.bio_new(m2.bio_f_buffer())
+    topbio = m2.bio_push(iobuf, sslbio)
+
+    # Send the request.
+    m2.bio_write(sslbio, req)
+
+    # Receive the response.
+    while 1:
+        data = m2.bio_gets(topbio, 4096)
+        if not data: break
+        sys.stdout.write(data)
+
+    # Cleanup. May be missing some necessary steps. ;-|
+    m2.bio_pop(topbio)
+    m2.bio_free(iobuf)
+    m2.ssl_shutdown(ssl)
+    m2.ssl_free(ssl)
+    m2.ssl_ctx_free(ctx)
+    s.close()
+
+
+if __name__ == '__main__':
+    c_10()
+    c_11()
+
diff --git a/demo/ssl/c_bio.py b/demo/ssl/c_bio.py
new file mode 100644 (file)
index 0000000..1c20e91
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+"""C programming in Python. Have SWIG sweat the pointers. ;-)
+
+Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import sys
+
+from M2Crypto import SSL, m2
+
+HOST = '127.0.0.1'
+PORT = 9443
+req_10 = 'GET / HTTP/1.0\r\n\r\n'
+req_11 = 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
+
+
+def c_10():
+    c_style(HOST, PORT, req_10) 
+
+
+def c_11():
+    c_style(HOST, PORT, req_11) 
+
+
+def c_style(HOST, PORT, req):
+
+    # Set up SSL context.
+    ctx = m2.ssl_ctx_new(m2.sslv3_method())
+    m2.ssl_ctx_use_cert(ctx, 'client.pem')
+    m2.ssl_ctx_use_privkey(ctx, 'client.pem')
+
+    # Make the socket connection.
+    s = socket(AF_INET, SOCK_STREAM)
+    s.connect((HOST, PORT))
+
+    # Set up the SSL connection.
+    sbio = m2.bio_new_socket(s.fileno(), 0)
+    ssl = m2.ssl_new(ctx)
+    m2.ssl_set_bio(ssl, sbio, sbio)
+    m2.ssl_connect(ssl)
+    sslbio = m2.bio_new(m2.bio_f_ssl())
+    m2.bio_set_ssl(sslbio, ssl, 0)
+
+    # Push a buffering BIO over the SSL BIO.
+    iobuf = m2.bio_new(m2.bio_f_buffer())
+    topbio = m2.bio_push(iobuf, sslbio)
+
+    # Send the request.
+    m2.bio_write(sslbio, req)
+
+    # Receive the response.
+    while 1:
+        data = m2.bio_gets(topbio, 4096)
+        if not data: break
+        sys.stdout.write(data)
+
+    # Cleanup. May be missing some necessary steps. ;-|
+    m2.bio_pop(topbio)
+    m2.bio_free(iobuf)
+    m2.ssl_shutdown(ssl)
+    m2.ssl_free(ssl)
+    m2.ssl_ctx_free(ctx)
+    s.close()
+
+
+if __name__ == '__main__':
+    #c_10()
+    c_11()
+
diff --git a/demo/ssl/ca.der b/demo/ssl/ca.der
new file mode 100644 (file)
index 0000000..8f4ed80
Binary files /dev/null and b/demo/ssl/ca.der differ
diff --git a/demo/ssl/ca.pem b/demo/ssl/ca.pem
new file mode 100644 (file)
index 0000000..b7c84a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/ssl/client.p12 b/demo/ssl/client.p12
new file mode 100644 (file)
index 0000000..9c7a640
Binary files /dev/null and b/demo/ssl/client.p12 differ
diff --git a/demo/ssl/client.pem b/demo/ssl/client.pem
new file mode 100644 (file)
index 0000000..c0274ad
--- /dev/null
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDPzCCAqigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzYyNFoXDTA0MDYyMTEzMzYy
+NFowOjELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9N
+MkNyeXB0byBDbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOrvRK8h
+lTNZ6rSWfIR/DuNpq+gQ42FCem/ai/dLKCxWB2BMHLHVkfCtEsJpeltigIFgZ28E
+1h9Dx8RjP8R3lbTdJhv37j+3iUUq5lNXrlIWoKkyJsI4c1gC5kXke2z4uloj3W1E
+dWwOPjKxCi2OGKqwRxNzLrIlnBJ6WsP53PghAgMBAAGjggEMMIIBCDAJBgNVHRME
+AjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0
+ZTAdBgNVHQ4EFgQUMwLQdUUrkDAlEuZGt9fw/6N3mKswga0GA1UdIwSBpTCBooAU
+6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNVBAYTAlNHMREwDwYD
+VQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00y
+Q3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3DQEJARYTbmdwc0Bu
+ZXRtZW1ldGljLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQBzJ2Q06/QM2f/OHBzo
+T0QpqAdWFmuclFecuKkzXOdh93cuCpJBrxBsMJMhvW10c7M9kEjW8GEt+obv2prt
+39WDZVejg83O2YUexJlUnhmA3Zc5izkOT0+VasIGSKgt/eQm9p6klk/LKcVEZGzh
+mI26dvftjo0HisrW5zfwEjzbdA==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDq70SvIZUzWeq0lnyEfw7jaavoEONhQnpv2ov3SygsVgdgTByx
+1ZHwrRLCaXpbYoCBYGdvBNYfQ8fEYz/Ed5W03SYb9+4/t4lFKuZTV65SFqCpMibC
+OHNYAuZF5Hts+LpaI91tRHVsDj4ysQotjhiqsEcTcy6yJZwSelrD+dz4IQIDAQAB
+AoGBAL8VDRBEiE3T/IoVPAGoNjvRXvjJg6c/osYHQ4BHqM0my6kPPueFhcXzfyaR
+E+vwGgUgnAA4NtAHGRwqfVsWyLNbeogKO6kEfGoLY+6wjxQrrLohSeS9EUyvqFzW
+dOC082piwzo61OK6q3iLplHPuQ6RK10VR1PQVVtpF/OuaxaBAkEA+6HpW7JTZswu
+WCxMkpT0mXBa6rEqVnkv6WiZyQoFNsqgsSotqifpcdH39mixji+sO9gaUI6uKtcU
+6SSlKRCYzQJBAO8DKaKh9Dq8U+nngc+s9NrkvfDDD0zzdoMznPmV8qQj50sPoJZF
+1nK0wMjaIRrKiMKceOHnFuJnpchKiZiEbKUCQQCk0QJ2azExjd91JV7qS+KCdhM2
+0eA3T51QNpE0GvobT1E9ebD7WLURNkRCA4T46sTXVc62oR33NXWe17/OS+6pAkAB
+FjiYPrhHlBellqHmedjbLfMXJyvoo6rESfXKxL3HtUoV80o9pK+m8d92ildgMc+R
+YvjBvjVCbko4sO4TPXbpAkAE53+iOITLFBeF3dTBh77LvDDHy4Z94knsWSzZv/aL
+26Zvjl5iZjHK6ECIe35kXzVYCWOgghI4ixHSMWOV3bt4
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ssl/dh1024.pem b/demo/ssl/dh1024.pem
new file mode 100644 (file)
index 0000000..81d43f6
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/ssl/echo-eg.py b/demo/ssl/echo-eg.py
new file mode 100644 (file)
index 0000000..f7975d4
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+"""Demo SSL client #1 for the HOWTO.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+import getopt, sys
+from socket import gethostname
+from M2Crypto import Err, Rand, SSL, X509, threading
+
+host = '127.0.0.1'
+port = 9999
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:')
+for opt in optlist:
+    if '-h' in opt:
+        host = opt[1]
+    elif '-p' in opt:
+        port = int(opt[1])
+
+Rand.load_file('../randpool.dat', -1) 
+
+ctx = SSL.Context('sslv3')
+ctx.load_cert('client.pem')
+#ctx.load_verify_info('ca.pem')
+ctx.set_verify(SSL.verify_peer, 10)
+ctx.set_info_callback()
+
+s = SSL.Connection(ctx)
+s.connect((host, port))
+print 'Host =', gethostname()
+print 'Cipher =', s.get_cipher().name()
+
+peer = s.get_peer_cert()
+print 'Server =', peer.get_subject().CN
+
+while 1:
+    data = s.recv()
+    if not data:
+        break
+    sys.stdout.write(data)
+    sys.stdout.flush()
+    buf = sys.stdin.readline()
+    if not buf: 
+        break
+    s.send(buf)
+
+s.close()
+
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echo.py b/demo/ssl/echo.py
new file mode 100644 (file)
index 0000000..912e248
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+"""A simple SSL 'echo' client.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import getopt, sys
+from socket import gethostname
+from M2Crypto import Err, Rand, SSL, X509
+
+host = '127.0.0.1'
+port = 9999
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:')
+for opt in optlist:
+    if '-h' in opt:
+        host = opt[1]
+    elif '-p' in opt:
+        port = int(opt[1])
+
+Rand.load_file('../randpool.dat', -1) 
+
+ctx = SSL.Context('sslv3')
+ctx.load_cert_chain('client.pem')
+#ctx.set_verify(SSL.verify_none, 10)
+ctx.set_verify(SSL.verify_peer, 10, SSL.cb.ssl_verify_callback)
+ctx.load_verify_locations('ca.pem')
+#ctx.set_allow_unknown_ca(1)
+ctx.set_info_callback()
+
+s = SSL.Connection(ctx)
+s.connect((host, port))
+print 'Host =', gethostname()
+print 'Cipher =', s.get_cipher().name()
+
+## 2003-06-28, ngps: Depends on ctx.set_verify() above, RTFM for details.
+## v = s.get_verify_result()
+## if v != X509.V_OK:
+##     s.close()
+##     raise SystemExit, 'Server verification failed'
+
+peer = s.get_peer_cert()
+print 'Server =', str(peer.get_subject())
+
+while 1:
+    data = s.recv()
+    if not data:
+        break
+    sys.stdout.write(data)
+    sys.stdout.flush()
+    buf = sys.stdin.readline()
+    if not buf: 
+        break
+    s.send(buf)
+
+s.close()
+
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-async.py b/demo/ssl/echod-async.py
new file mode 100644 (file)
index 0000000..0881c76
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""An asyncore-based SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asyncore, errno, socket, time
+from M2Crypto import Rand, SSL
+import echod_lib
+
+class ssl_echo_channel(asyncore.dispatcher):
+
+    buffer = 'Ye Olde Echo Servre\r\n'
+
+    def __init__(self, conn):
+        asyncore.dispatcher.__init__(self, conn)
+        self._ssl_accepting = 1
+        self.peer = self.get_peer_cert()
+
+    def handle_connect(self):
+        pass
+
+    def handle_close(self):
+        self.close()
+
+    def writable(self):
+        return self._ssl_accepting or (len(self.buffer) > 0)
+    def handle_write(self):
+        if self._ssl_accepting: 
+            s = self.socket.accept_ssl()
+            if s:
+                self._ssl_accepting = 0
+        else:
+            try:
+                n = self.send(self.buffer)
+                if n == -1:
+                    pass
+                elif n == 0:
+                    self.handle_close()
+                else:
+                    self.buffer = self.buffer[n:]
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.handle_close()
+                    return
+                else:
+                    raise
+
+    def readable(self):
+        return 1
+
+    def handle_read(self):
+        if self._ssl_accepting:
+            s = self.socket.accept_ssl()
+            if s:
+                self._ssl_accepting = 0
+        else:
+            try:
+                blob = self.recv(4096)
+                if blob is None:
+                    pass
+                elif blob == '':
+                    self.handle_close()
+                else: 
+                    self.buffer = self.buffer + blob        
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    self.handle_close()
+                    return
+                else:
+                    raise
+
+
+class ssl_echo_server(SSL.ssl_dispatcher):
+
+    channel_class=ssl_echo_channel
+
+    def __init__(self, addr, port, ssl_context):
+        SSL.ssl_dispatcher.__init__(self)
+        self.create_socket(ssl_context)
+        self.set_reuse_addr()
+        self.socket.setblocking(0)
+        self.bind((addr, port))
+        self.listen(5)
+        self.ssl_ctx=ssl_context
+    
+    def handle_accept(self):
+        try:
+            sock, addr = self.socket.accept()
+            self.channel_class(sock)
+        except:
+            print '-'*40
+            import traceback
+            traceback.print_exc()
+            print '-'*40
+            return
+
+    def writable(self):
+        return 0
+
+
+if __name__=='__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    ctx = echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', \
+            #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+            SSL.verify_none)
+    ctx.set_tmp_dh('dh1024.pem')
+    ssl_echo_server('', 9999, ctx)
+    asyncore.loop()
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-eg1.py b/demo/ssl/echod-eg1.py
new file mode 100644 (file)
index 0000000..87358b7
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Demo SSL server #1 for the HOWTO.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+import SocketServer
+from M2Crypto import Err, Rand, SSL, threading
+
+def init_context(protocol, dhpfile, certfile, cafile, verify, verify_depth=10):
+    ctx = SSL.Context(protocol)
+    ctx.set_tmp_dh(dhpfile)
+    ctx.load_cert(certfile)
+    #ctx.load_verify_info(cafile)
+    ctx.set_verify(verify, verify_depth)
+    ctx.set_session_id_ctx('echod')
+    ctx.set_info_callback()
+    return ctx
+
+class ssl_echo_handler(SocketServer.BaseRequestHandler):
+
+    buffer = 'Ye Olde Echo Servre\r\n'
+
+    def handle(self):
+        peer = self.request.get_peer_cert()
+        if peer is not None:
+            print 'Client CA        =', peer.get_issuer().O
+            print 'Client Subject   =', peer.get_subject().CN
+        self.request.write(self.buffer)
+        while 1:
+            buf = self.request.read()
+            if not buf:
+                break
+            self.request.write(buf) 
+
+    def finish(self):
+        self.request.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+        self.request.close()
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    threading.init()
+    ctx = init_context('sslv23', 'dh1024.pem', 'server.pem', 'ca.pem', SSL.verify_peer)
+    s = SSL.SSLServer(('', 9999), ssl_echo_handler, ctx)
+    s.serve_forever()   
+    threading.cleanup()
+    Rand.save_file('randpool.dat')
+
+
diff --git a/demo/ssl/echod-forking.py b/demo/ssl/echod-forking.py
new file mode 100644 (file)
index 0000000..43faab3
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+"""A forking SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand, SSL
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+    buffer = 'Ye Olde Forking Echo Servre\r\n'
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    ctx = echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', 
+        SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+    ctx.set_tmp_dh('dh1024.pem')
+    s = SSL.ForkingSSLServer(('', 9999), ssl_echo_handler, ctx)
+    s.serve_forever()   
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-iterative.py b/demo/ssl/echod-iterative.py
new file mode 100644 (file)
index 0000000..2e68699
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+"""A simple iterative SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand, SSL
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+    buffer='Ye Olde One-At-A-Time Echo Servre\r\n'
+
+
+if __name__=='__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', \
+        SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+        #SSL.verify_none)
+    ctx.set_tmp_dh('dh1024.pem')
+    s=SSL.SSLServer(('', 9999), ssl_echo_handler, ctx)
+    s.serve_forever()   
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-thread.py b/demo/ssl/echod-thread.py
new file mode 100644 (file)
index 0000000..65db811
--- /dev/null
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+"""Another multi-threading SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand, SSL, threading
+import echod_lib
+
+import thread
+from socket import *
+
+buffer='Ye Newe Threading Echo Servre\r\n'
+
+def echo_handler(sslctx, sock, addr):
+    sslconn = SSL.Connection(sslctx, sock)
+    sslconn.setup_addr(addr)
+    sslconn.setup_ssl()
+    sslconn.set_accept_state()
+    sslconn.accept_ssl()
+    sslconn.write(buffer) 
+    while 1:
+        try:
+            buf = sslconn.read()
+            if not buf:
+                break
+            sslconn.write(buf) 
+        except SSL.SSLError, what:
+            if str(what) == 'unexpected eof':
+                break
+            else:
+                raise
+        except:
+            break
+    # Threading servers need this.
+    sslconn.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN|SSL.SSL_SENT_SHUTDOWN)
+    sslconn.close()
+
+
+if __name__=='__main__':
+    threading.init()
+    Rand.load_file('../randpool.dat', -1) 
+    ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', 
+        SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+    ctx.set_tmp_dh('dh1024.pem')
+    sock = socket(AF_INET, SOCK_STREAM)
+    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+    sock.bind(('', 9999))
+    sock.listen(5)
+    while 1:
+        conn, addr = sock.accept()
+        thread.start_new_thread(echo_handler, (ctx, conn, addr))
+    Rand.save_file('../randpool.dat')
+    threading.cleanup()
+
diff --git a/demo/ssl/echod-threading.py b/demo/ssl/echod-threading.py
new file mode 100644 (file)
index 0000000..81eaef3
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+"""A multi-threading SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand, SSL, threading
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+
+    buffer='Ye Olde Threading Echo Servre\r\n'
+
+    def finish(self):
+        # Threading servers need this.
+        self.request.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+        self.request.close()
+
+
+if __name__=='__main__':
+    try:
+        threading.init()
+        Rand.load_file('../randpool.dat', -1) 
+        ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', 
+            SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+        ctx.set_tmp_dh('dh1024.pem')
+        s=SSL.ThreadingSSLServer(('', 9999), ssl_echo_handler, ctx)
+        s.serve_forever()   
+        Rand.save_file('../randpool.dat')
+    except:
+        threading.cleanup()
+
diff --git a/demo/ssl/echod_lib.py b/demo/ssl/echod_lib.py
new file mode 100644 (file)
index 0000000..7bea683
--- /dev/null
@@ -0,0 +1,45 @@
+"""Support routines for the various SSL 'echo' servers.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import SocketServer
+from M2Crypto import SSL
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+    ctx = SSL.Context(protocol)
+    ctx.load_cert_chain(certfile)
+    ctx.load_verify_locations(cafile)
+    ctx.set_client_CA_list_from_file(cafile)    
+    ctx.set_verify(verify, verify_depth)
+    #ctx.set_allow_unknown_ca(1)
+    ctx.set_session_id_ctx('echod')
+    ctx.set_info_callback()
+    return ctx
+
+
+class ssl_echo_handler(SocketServer.BaseRequestHandler):
+
+    buffer = 'Ye Olde Echo Servre\r\n'
+
+    def handle(self):
+        peer = self.request.get_peer_cert()
+        if peer is not None:
+            print 'Client CA        =', peer.get_issuer().O
+            print 'Client Subject   =', peer.get_subject().CN
+        x = self.request.write(self.buffer)
+        while 1:
+            try:
+                buf = self.request.read()
+                if not buf:
+                    break
+                self.request.write(buf) 
+            except SSL.SSLError, what:
+                if str(what) == 'unexpected eof':
+                    break
+                else:
+                    raise
+
+    def finish(self):
+        self.request.close()
+
+
diff --git a/demo/ssl/ftp_tls.py b/demo/ssl/ftp_tls.py
new file mode 100644 (file)
index 0000000..163d87c
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""Demo for M2Crypto.ftpslib's FTP/TLS client.
+
+This client interoperates with M2Crypto's Medusa-based FTP/TLS
+server as well as Peter Runestig's patched-for-TLS OpenBSD FTP 
+server.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import SSL, ftpslib, threading
+
+def passive():
+    ctx = SSL.Context('sslv23')
+    f = ftpslib.FTP_TLS(ssl_ctx=ctx)
+    f.connect('127.0.0.1', 39021)
+    f.auth_tls()
+    f.set_pasv(1)    
+    f.login('ftp', 'ngps@')
+    f.prot_p()
+    f.retrlines('LIST')
+    f.quit()
+
+def active():
+    ctx = SSL.Context('sslv23')
+    f = ftpslib.FTP_TLS(ssl_ctx=ctx)
+    f.connect('127.0.0.1', 39021)
+    f.auth_tls()
+    f.set_pasv(0)
+    f.login('ftp', 'ngps@')
+    f.prot_p()
+    f.retrlines('LIST')
+    f.quit()
+
+
+if __name__ == '__main__':
+    threading.init()
+    active()
+    passive()
+    threading.cleanup()
+
diff --git a/demo/ssl/http_cli_20.py b/demo/ssl/http_cli_20.py
new file mode 100644 (file)
index 0000000..0ce4cef
--- /dev/null
@@ -0,0 +1,22 @@
+import httplib, sys
+
+def test_httplib():
+    h = httplib.HTTPConnection('127.0.0.1', 80)
+    h.set_debuglevel(1)
+    h.putrequest('GET', '/')
+    h.putheader('Accept', 'text/html')
+    h.putheader('Accept', 'text/plain')
+    h.putheader('Connection', 'close')
+    h.endheaders()
+    resp = h.getresponse()
+    f = resp.fp
+    while 1:
+        data = f.readline()   
+        if not data:
+            break
+        sys.stdout.write(data)
+    f.close()
+    h.close()
+
+if __name__=='__main__':
+    test_httplib()
diff --git a/demo/ssl/https_cli.py b/demo/ssl/https_cli.py
new file mode 100644 (file)
index 0000000..543ba8f
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import sys
+from M2Crypto import Rand, SSL, httpslib, threading
+
+
+def test_httpslib():
+    ctx = SSL.Context('sslv23')
+    ctx.load_cert_chain('client.pem')
+    ctx.load_verify_locations('ca.pem', '')
+    ctx.set_verify(SSL.verify_peer, 10)        
+    ctx.set_info_callback()
+    h = httpslib.HTTPSConnection('localhost', 19443, ssl_context=ctx)
+    h.set_debuglevel(1)
+    h.putrequest('GET', '/')
+    h.putheader('Accept', 'text/html')
+    h.putheader('Accept', 'text/plain')
+    h.putheader('Connection', 'close')
+    h.endheaders()
+    resp = h.getresponse()
+    f = resp.fp
+    c = 0
+    while 1:
+        # Either of following two works.
+        #data = f.readline(4096)   
+        data = resp.read(4096)
+        if not data: break
+        c = c + len(data)
+        #print data
+        sys.stdout.write(data)
+        sys.stdout.flush()
+    f.close()
+    h.close()
+
+if __name__=='__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    #threading.init()
+    test_httpslib()
+    #threading.cleanup()
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/https_cli_async.py b/demo/ssl/https_cli_async.py
new file mode 100644 (file)
index 0000000..9e7a5d0
--- /dev/null
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+
+"""Demo for client-side ssl_dispatcher usage. Note that connect() 
+is blocking. (Need fix?) 
+
+This isn't really a HTTPS client; it's just a toy.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asyncore, sys, time
+from M2Crypto import Rand, SSL
+
+class https_client(SSL.ssl_dispatcher):
+
+    def __init__(self, host, path, ssl_ctx):
+        SSL.ssl_dispatcher.__init__(self)
+        self.path = path
+        self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % self.path
+        self.create_socket(ssl_ctx)
+        self.socket.connect((host, 19443))
+        self._can_read = 1
+        self._count = 0
+
+    def handle_connect(self):
+        pass
+
+    def readable(self):
+        return self._can_read
+
+    def handle_read(self):
+        try:
+            result = self.recv()
+            if result is None:
+                return
+            elif result == '':
+                self._can_read = 0
+                sys.stdout.write('%s: total: %5d\n' % (self.path, self._count,))
+                sys.stdout.flush()
+                self.close()
+            else:
+                #print result
+                l = len(result)
+                self._count = self._count + l
+                display = (time.time(), l, self.path)
+                sys.stdout.write('%14.3f: read %5d from %s\n' % display)
+                sys.stdout.flush()
+        except SSL.SSLError, why:
+            print 'handle_read:', why
+            self.close()
+            raise
+
+    def writable(self):
+        return (len(self.buffer) > 0)
+
+    def handle_write(self):
+        try:
+            sent = self.send(self.buffer)
+            self.buffer = self.buffer[sent:]
+        except SSL.SSLError, why:
+            print 'handle_write:', why
+            self.close()
+
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1) 
+    ctx = SSL.Context()
+    url = ('/jdk118/api/u-names.html', 
+        '/postgresql/xfunc-c.html', 
+        '/python2.1/modindex.html')
+    for u in url:
+        https_client('localhost', u, ctx)
+    asyncore.loop()
+    Rand.save_file('../randpool.dat')
+
+
+# Here's a sample output. Server is Apache+mod_ssl on localhost.
+# $ python https_cli_async.py 
+# 991501090.682: read   278 from /python2.1/modindex.html
+# 991501090.684: read   278 from /postgresql/xfunc-c.html
+# 991501090.742: read  4096 from /postgresql/xfunc-c.html
+# 991501090.743: read  4096 from /postgresql/xfunc-c.html
+# 991501090.744: read  4096 from /postgresql/xfunc-c.html
+# 991501090.744: read  4096 from /postgresql/xfunc-c.html
+# 991501090.755: read  4096 from /postgresql/xfunc-c.html
+# 991501090.756: read   278 from /jdk118/api/u-names.html
+# 991501090.777: read  4096 from /postgresql/xfunc-c.html
+# 991501090.778: read  4096 from /postgresql/xfunc-c.html
+# 991501090.778: read  4096 from /postgresql/xfunc-c.html
+# 991501090.782: read  4096 from /postgresql/xfunc-c.html
+# 991501090.813: read  4096 from /python2.1/modindex.html
+# 991501090.839: read  4096 from /jdk118/api/u-names.html
+# 991501090.849: read  4096 from /python2.1/modindex.html
+# 991501090.873: read  3484 from /postgresql/xfunc-c.html
+# 991501090.874: read  4096 from /jdk118/api/u-names.html
+# 991501090.874: read  4096 from /python2.1/modindex.html
+#/postgresql/xfunc-c.html: total: 40626
+# 991501090.886: read  4096 from /jdk118/api/u-names.html
+# 991501090.886: read  2958 from /python2.1/modindex.html
+# 991501090.887: read  4096 from /jdk118/api/u-names.html
+#/python2.1/modindex.html: total: 15524
+# 991501090.893: read  4096 from /jdk118/api/u-names.html
+# 991501090.894: read  2484 from /jdk118/api/u-names.html
+#/jdk118/api/u-names.html: total: 23242
+# $
+
+
diff --git a/demo/ssl/https_srv.py b/demo/ssl/https_srv.py
new file mode 100644 (file)
index 0000000..6d12d80
--- /dev/null
@@ -0,0 +1,150 @@
+"""This server extends BaseHTTPServer and SimpleHTTPServer thusly:
+1. One thread per connection.
+2. Generates directory listings. 
+
+In addition, it has the following properties:
+1. Works over HTTPS only.
+2. Displays SSL handshaking and SSL session info.
+3. Performs SSL renegotiation when a magic url is requested.
+
+TODO:
+1. Cache stat() of directory entries.
+2. Fancy directory indexing.
+3. Interface ZPublisher.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+"""
+
+import os, sys
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+from M2Crypto import Rand, SSL
+from M2Crypto.SSL.SSLServer import ThreadingSSLServer
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+def mkdirlist(path, url):
+    dirlist = os.listdir(path)
+    dirlist.sort()
+    f = StringIO()
+    f.write('<title>Index listing for %s</title>\r\n' % (url,))
+    f.write('<h1>Index listing for %s</h1>\r\n' % (url,))
+    f.write('<pre>\r\n')
+    for d in dirlist:
+        if os.path.isdir(os.path.join(path, d)):
+            d2 = d + '/'
+        else:
+            d2 = d
+        if url == '/':
+            f.write('<a href="/%s">%s</a><br>\r\n' % (d, d2))
+        else:
+            f.write('<a href="%s/%s">%s</a><br>\r\n' % (url, d, d2))
+    f.write('</pre>\r\n\r\n')
+    f.reset()
+    return f
+
+
+class HTTP_Handler(SimpleHTTPRequestHandler):
+
+    server_version = "https_srv/0.1"
+    reneg = 0
+
+    # Cribbed from SimpleHTTPRequestHander to add the ".der" entry,
+    # which facilitates installing your own certificates into browsers.
+    extensions_map = {
+            '': 'text/plain',   # Default, *must* be present
+            '.html': 'text/html',
+            '.htm': 'text/html',
+            '.gif': 'image/gif',
+            '.jpg': 'image/jpeg',
+            '.jpeg': 'image/jpeg',
+            '.der': 'application/x-x509-ca-cert'
+            }
+
+    def send_head(self):
+        if self.path[1:8] == '_reneg_':
+            self.reneg = 1
+            self.path = self.path[8:]
+        path = self.translate_path(self.path)
+        if os.path.isdir(path):
+            f = mkdirlist(path, self.path)
+            filetype = 'text/html'
+        else:
+            try:
+                f = open(path, 'rb')
+                filetype = self.guess_type(path)
+            except IOError:
+                self.send_error(404, "File not found")
+                return None
+        self.send_response(200)
+        self.send_header("Content-type", filetype)
+        self.end_headers()
+        return f
+
+    def do_GET(self):
+        #sess = self.request.get_session()
+        #self.log_message('\n%s', sess.as_text())
+        f = self.send_head()
+        if self.reneg:
+            self.reneg = 0
+            self.request.renegotiate()
+            sess = self.request.get_session()
+            self.log_message('\n%s', sess.as_text())
+        if f:
+            self.copyfile(f, self.wfile)
+            f.close()
+
+    def do_HEAD(self):
+        #sess = self.request.get_session()
+        #self.log_message('\n%s', sess.as_text())
+        f = self.send_head()
+        if f:
+            f.close()
+
+
+class HTTPS_Server(ThreadingSSLServer):
+    def __init__(self, server_addr, handler, ssl_ctx):
+        ThreadingSSLServer.__init__(self, server_addr, handler, ssl_ctx)
+        self.server_name = server_addr[0]
+        self.server_port = server_addr[1]
+
+    def finish(self):
+        self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+        self.request.close()
+
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+    ctx=SSL.Context(protocol)
+    ctx.load_cert(certfile)
+    ctx.load_client_ca(cafile)
+    ctx.load_verify_info(cafile)
+    ctx.set_verify(verify, verify_depth)
+    ctx.set_allow_unknown_ca(1)
+    ctx.set_session_id_ctx('https_srv')
+    ctx.set_info_callback()
+    return ctx
+
+
+if __name__ == '__main__':
+    from M2Crypto import threading as m2threading
+    m2threading.init()
+    if len(sys.argv) < 2:
+        wdir = '.'
+    else:
+        wdir = sys.argv[1]
+    Rand.load_file('../randpool.dat', -1)
+    ctx = init_context('sslv23', 'server.pem', 'ca.pem', \
+        SSL.verify_none)
+        #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+    ctx.set_tmp_dh('dh1024.pem')
+    os.chdir(wdir)
+    httpsd = HTTPS_Server(('', 19443), HTTP_Handler, ctx)
+    httpsd.serve_forever()
+    Rand.save_file('../randpool.dat')
+    m2threading.cleanup()
+
+
diff --git a/demo/ssl/myapp.py b/demo/ssl/myapp.py
new file mode 100644 (file)
index 0000000..bb024d8
--- /dev/null
@@ -0,0 +1,30 @@
+"""
+Sample application for socklib and somelib.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+# Sample application that uses socklib to override socket.ssl in order
+# to make the 3rd party library use M2Crypto for SSL, instead of python
+# stdlib SSL.
+
+import socklib # sets M2Crypto.SSL.Connection as socket.ssl
+
+# Set up the secure context for socklib
+from M2Crypto import SSL
+
+def getContext():
+    ctx = SSL.Context()
+    ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+    ctx.load_verify_locations('ca.pem')
+    return ctx
+
+socklib.setSSLContextFactory(getContext)
+
+# Import and use 3rd party lib
+import somelib
+
+if __name__ == '__main__':
+    c = somelib.HttpsGetSlash()
+    c.get('verisign.com', 443)
diff --git a/demo/ssl/s_client.py b/demo/ssl/s_client.py
new file mode 100644 (file)
index 0000000..021e3db
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+
+"""An M2Crypto implementation of OpenSSL's s_client.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import getopt
+import string
+import sys
+
+from M2Crypto import SSL
+
+# s_server -www
+HOST='127.0.0.1'
+PORT=4433
+REQ='GET / HTTP/1.0\r\n\r\n'
+
+class Config:
+    pass
+
+def config(args):
+    options=['connect=', 'verify=', 'cert=', 'key=', 'CApath=', 'CAfile=', \
+        'reconnect', 'pause', 'showcerts', 'debug', 'nbio_test', 'state', \
+        'nbio', 'crlf', 'sslv2', 'sslv3', 'tlsv1', 'no_sslv2', 'no_sslv3', \
+        'no_tlsv1', 'bugs', 'cipher=', 'Verify=']
+    optlist, optarg=getopt.getopt(args, '', options)
+
+    cfg=Config()
+    for opt in optlist:
+        setattr(cfg, opt[0][2:], opt[1])
+    for x in (('tlsv1','no_tlsv1'),('sslv3','no_sslv3'),('sslv2','no_sslv2')):
+        if hasattr(cfg, x[0]) and hasattr(cfg, x[1]):
+                raise ValueError, 'mutually exclusive: %s and %s' % x
+
+    if hasattr(cfg, 'connect'):
+        (host, port)=string.split(cfg.connect, ':')
+        cfg.connect=(host, int(port))
+    else:
+        cfg.connect=(HOST, PORT)
+
+    cfg.protocol=[]
+    # First protocol found will be used.
+    # Permutate the following tuple for preference. 
+    for p in ('tlsv1', 'sslv3', 'sslv2'):
+        if hasattr(cfg, p):
+            cfg.protocol.append(p)
+    cfg.protocol.append('sslv23')
+
+    return cfg
+
+def make_context(config):
+    ctx=SSL.Context(config.protocol[0])
+    if hasattr(config, 'cert'):
+        cert=config.cert
+    else:
+        cert='client.pem'
+    if hasattr(config, 'key'):
+        key=config.key
+    else:
+        key='client.pem'
+    #ctx.load_cert(cert, key)
+
+    if hasattr(config, 'verify'):
+        verify=SSL.verify_peer
+        depth=int(config.verify)
+    elif hasattr(config, 'Verify'):
+        verify=SSL.verify_peer | SSL.verify_fail_if_no_peer_cert
+        depth=int(config.Verify)
+    else:
+        verify=SSL.verify_none
+        depth=10
+    config.verify=verify
+    config.verify_depth=depth
+    ctx.set_verify(verify, depth)
+
+    if hasattr(config, 'CAfile'):
+        cafile=config.CAfile
+    else:
+        cafile='ca.pem'
+    ctx.load_verify_location(cafile)
+
+    return ctx
+
+def s_client(config):
+    ctx=make_context(config)
+    s=SSL.Connection(ctx)
+    s.connect(config.connect)
+    if config.verify != SSL.verify_none and not s.verify_ok():
+        print 'peer verification failed'
+        peer=s.get_peer_cert()
+        if peer is None:
+            print 'unable to get peer certificate'
+        else:
+            print 'peer.as_text()'
+        raise SystemExit
+    s.send(REQ)
+    while 1:
+        data=s.recv()
+        if not data:
+            break
+        print data
+    s.close()
+
+if __name__=='__main__':
+    cfg=config(sys.argv[1:])
+    s_client(cfg)
+
diff --git a/demo/ssl/s_server.py b/demo/ssl/s_server.py
new file mode 100644 (file)
index 0000000..84789a1
--- /dev/null
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+
+"""An M2Crypto implementation of OpenSSL's s_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import asyncore
+import cStringIO
+import getopt
+import string
+import sys
+
+from M2Crypto import SSL, BIO, DH, Err
+
+# s_server -www
+HOST=''
+PORT=4433
+
+class Config:
+    pass
+
+def config(args):
+    options=['accept=', 'context=', 'verify=', 'Verify=', 'cert=', 'key=', \
+        'dcert=', 'dkey=', 'nocert', 'crlf', 'debug', 'CApath=', 'CAfile=', \
+        'quiet', 'no_tmp_rsa', 'state', 'sslv2', 'sslv3', 'tlsv1', \
+        'no_sslv2', 'no_sslv3', 'no_tlsv1', 'bugs', 'cipher=']
+    optlist, optarg=getopt.getopt(args, '', options)
+
+    cfg=Config()
+    for opt in optlist:
+        setattr(cfg, opt[0][2:], opt[1])
+    for x in (('tlsv1','no_tlsv1'),('sslv3','no_sslv3'),('sslv2','no_sslv2')):
+        if hasattr(cfg, x[0]) and hasattr(cfg, x[1]):
+                raise ValueError, 'mutually exclusive: %s and %s' % x
+
+    if hasattr(cfg, 'accept'):
+        cfg.accept=string.split(cfg.connect, ':')
+    else:
+        cfg.accept=(HOST, PORT)
+
+    cfg.protocol=[]
+    # First protocol found will be used.
+    # Permutate the following tuple for preference. 
+    for p in ('tlsv1', 'sslv3', 'sslv2'):
+        if hasattr(cfg, p):
+            cfg.protocol.append(p)
+    cfg.protocol.append('sslv23')
+
+    return cfg
+
+RESP_HEAD="""\
+HTTP/1.0 200 ok
+Content-type: text/html
+
+<HTML><BODY BGCOLOR=\"#ffffff\">
+<pre>
+
+Emulating s_server -www
+Ciphers supported in s_server.py
+"""
+
+RESP_TAIL="""\
+</pre>
+</BODY></HTML>
+"""
+
+class channel(SSL.ssl_dispatcher):
+
+    def __init__(self, conn, debug):
+        SSL.ssl_dispatcher.__init__(self, conn)
+        self.socket.setblocking(0)
+        self.buffer=self.fixup_buffer()
+        self.debug=debug
+
+    def fixup_buffer(self):
+        even=0
+        buffer=cStringIO.StringIO()
+        buffer.write(RESP_HEAD)
+        for c in self.get_ciphers():
+            # This formatting works for around 80 columns.
+            buffer.write('%-11s:%-28s' % (c.version(), c.name()))
+            if even:
+                buffer.write('\r\n')
+                even=1-even
+        buffer.write('\r\n%s' % RESP_TAIL)
+        return buffer.getvalue()
+
+    def handle_connect(self):
+        pass
+
+    def handle_close(self):
+        self.close()
+
+    def handle_error(self, exc_type, exc_value, exc_traceback):
+        if self.debug:
+            print 'handle_error()'
+        #print exc_type, exc_value, exc_traceback
+        print Err.get_error()
+        self.handle_close()
+
+
+    def writeable(self):
+        return len(self.buffer)
+
+    def handle_write(self):
+        n=self.send(self.buffer)
+        if n==-1:
+            pass
+        elif n==0:
+            self.handle_close()
+        else:
+            self.buffer=self.buffer[n:]
+        if self.debug:
+            print 'handle_write():', n
+
+    def readable(self):
+        return 1
+
+    def handle_read(self):
+        blob=self.recv()
+        if blob is None:
+            pass
+        elif blob=='':
+            self.handle_close() 
+        else: 
+            pass
+        if self.debug:
+            print 'handle_read():', blob
+
+
+class server(SSL.ssl_dispatcher):
+
+    channel_class=channel
+
+    def __init__(self, addr, port, config, ssl_context):
+        asyncore.dispatcher.__init__(self)
+        self.create_socket(ssl_context)
+        self.set_reuse_addr()
+        self.socket.setblocking(0)
+        self.bind((addr, port))
+        self.listen(5)
+        self.config=config
+        self.debug=config.debug
+        self.ssl_ctx=ssl_context
+
+    def handle_accept(self):
+        sock, addr=self.accept()
+        print self.ssl_ctx.get_verify_mode()
+        if (self.ssl_ctx.get_verify_mode() is SSL.verify_none) or sock.verify_ok():
+            self.channel_class(sock, self.debug)
+        else:
+            print 'client verification failed'
+            sock.close()
+
+    def writeable(self):
+        return 0
+
+def s_server(config):
+    ctx=SSL.Context(config.protocol[0])
+
+    if hasattr(config, 'debug'):
+        config.debug=1
+    else:
+        config.debug=0
+
+    if hasattr(config, 'cert'):
+        cert=config.cert
+    else:
+        cert='server.pem'
+    if hasattr(config, 'key'):
+        cert=config.key
+    else:
+        cert='server.pem'
+    ctx.load_cert(cert)
+
+    if hasattr(config, 'CAfile'):
+        cafile=config.CAfile
+    else:
+        cafile='ca.pem'
+    ctx.load_verify_location(cafile)
+
+    if hasattr(config, 'verify'):
+        verify=SSL.verify_peer
+        depth=int(config.verify)
+    elif hasattr(config, 'Verify'):
+        verify=SSL.verify_peer | SSL.verify_fail_if_no_peer_cert
+        depth=int(config.Verify)
+    else:
+        verify=SSL.verify_none
+        depth=0
+    ctx.set_verify(verify, depth)
+
+    ctx.set_tmp_dh('dh1024.pem')
+    #ctx.set_info_callback()
+
+    server(cfg.accept[0], cfg.accept[1], cfg, ctx)
+    asyncore.loop()
+
+if __name__=='__main__':
+    cfg=config(sys.argv[1:])
+    s_server(cfg)
+
diff --git a/demo/ssl/server.pem b/demo/ssl/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ssl/server3.py b/demo/ssl/server3.py
new file mode 100644 (file)
index 0000000..99fbe97
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+"""
+server3 from the book 'Network Security with OpenSSL', but modified to
+Python/M2Crypto from the original C implementation.
+
+Copyright (c) 2004-2005 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+from M2Crypto import SSL, Rand, threading, DH
+import thread
+from socket import *
+
+verbose_debug = 1
+
+def verify_callback(ok, store):
+    if not ok:
+        print "***Verify Not ok"
+    return ok
+
+dh1024 = None
+
+def init_dhparams():
+    global dh1024
+    dh1024 = DH.load_params('dh1024.pem')
+
+def tmp_dh_callback(ssl, is_export, keylength):
+    global dh1024
+    if not dh1024:
+        init_dhparams()
+    return dh1024._ptr()
+
+def setup_server_ctx():
+    ctx = SSL.Context('sslv23')
+    if ctx.load_verify_locations('ca.pem') != 1:
+        print "***No CA file"
+    #if ctx.set_default_verify_paths() != 1:
+    #    print "***No default verify paths"
+    ctx.load_cert_chain('server.pem')
+    ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
+                   10, verify_callback)
+    ctx.set_options(SSL.op_all | SSL.op_no_sslv2)
+    ctx.set_tmp_dh_callback(tmp_dh_callback)
+    #ctx.set_tmp_dh('dh1024.pem')
+    if ctx.set_cipher_list('ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH') != 1:
+        print "***No valid ciphers"
+    if verbose_debug:
+        ctx.set_info_callback()
+    return ctx
+
+def post_connection_check(peerX509, expectedHost):
+    if peerX509 is None:
+        print "***No peer certificate"
+    # Not sure if we can do any other checks
+    return 1
+
+def do_server_loop(conn):
+    while 1:
+        try:
+            buf = conn.read()
+            if not buf:
+                break
+            print buf
+        except SSL.SSLError, what:
+            if str(what) == 'unexpected eof':
+                break
+            else:
+                raise
+        except:
+            break
+            
+    if conn.get_shutdown():
+        return 1
+    return 0
+
+# How about something like:
+#def server_thread(ctx, ssl, addr):
+#    conn = SSL.Connection(ctx, None)
+#    conn.ssl = ssl
+#    conn.setup_addr(addr)
+def server_thread(ctx, sock, addr):
+    conn = SSL.Connection(ctx, sock)
+    conn.set_post_connection_check_callback(post_connection_check)
+    conn.setup_addr(addr)
+    conn.set_accept_state()
+    conn.setup_ssl()
+    conn.accept_ssl()
+    
+    post_connection_check(conn)
+
+    print 'SSL Connection opened'
+    if do_server_loop(conn):
+        conn.close()
+    else:
+        conn.clear()
+    print 'SSL Connection closed'        
+    
+
+if __name__=='__main__':
+    threading.init()
+    Rand.load_file('../randpool.dat', -1) 
+
+    ctx = setup_server_ctx()
+
+    # How about something like this?
+    #conn_root = SSL.Connection(ctx)
+    #conn_root.bind(('127.0.0.1', 9999))
+    #conn_root.listen(5)
+    #while 1:
+    #    ssl, addr = conn_root.accept()
+    #    thread.start_new_thread(server_thread, (ctx, ssl, addr))
+
+    sock = socket(AF_INET, SOCK_STREAM)
+    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+    sock.bind(('', 9999))
+    sock.listen(5)
+    while 1:
+        conn, addr = sock.accept()
+        thread.start_new_thread(server_thread, (ctx, conn, addr))
+    Rand.save_file('../randpool.dat')
+    threading.cleanup()
diff --git a/demo/ssl/sess.py b/demo/ssl/sess.py
new file mode 100644 (file)
index 0000000..23428fc
--- /dev/null
@@ -0,0 +1,89 @@
+"""M2Crypto.SSL.Session client demo: This program requests a URL from 
+a HTTPS server, saves the negotiated SSL session id, parses the HTML 
+returned by the server, then requests each HREF in a separate thread 
+using the saved SSL session id.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Err, Rand, SSL, X509, threading
+m2_threading = threading; del threading
+
+import formatter, getopt, htmllib, sys
+from threading import Thread
+from socket import gethostname
+
+
+def handler(sslctx, host, port, href, recurs=0, sslsess=None):
+
+    s = SSL.Connection(sslctx)
+    if sslsess:
+        s.set_session(sslsess)
+        s.connect((host, port))
+    else:
+        s.connect((host, port))
+        sslsess = s.get_session()
+    #print sslsess.as_text()
+
+    if recurs:
+        p = htmllib.HTMLParser(formatter.NullFormatter())
+
+    f = s.makefile("rw")
+    f.write(href)
+    f.flush()
+
+    while 1:
+        data = f.read()
+        if not data:
+            break
+        if recurs:
+            p.feed(data)
+
+    if recurs:
+        p.close()
+
+    f.close()
+
+    if recurs:
+        for a in p.anchorlist:
+            req = 'GET %s HTTP/1.0\r\n\r\n' % a
+            thr = Thread(target=handler, 
+                        args=(sslctx, host, port, req, recurs-1, sslsess))
+            print "Thread =", thr.getName()
+            thr.start()
+    
+
+if __name__ == '__main__':
+
+    m2_threading.init()
+    Rand.load_file('../randpool.dat', -1) 
+
+    host = '127.0.0.1'
+    port = 9443
+    req = '/'
+
+    optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:r:')
+    for opt in optlist:
+        if '-h' in opt:
+            host = opt[1]
+        elif '-p' in opt:
+            port = int(opt[1])
+        elif '-r' in opt:
+            req = opt[1]
+    
+    ctx = SSL.Context('sslv3')
+    ctx.load_cert('client.pem')
+    ctx.load_verify_info('ca.pem')
+    ctx.load_client_ca('ca.pem')
+    ctx.set_verify(SSL.verify_none, 10)
+    
+    req = 'GET %s HTTP/1.0\r\n\r\n' % req
+
+    start = Thread(target=handler, args=(ctx, host, port, req, 1))
+    print "Thread =", start.getName()
+    start.start()
+    start.join()
+    
+    m2_threading.cleanup()
+    Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/ssl/sess2.py b/demo/ssl/sess2.py
new file mode 100644 (file)
index 0000000..a3c2bf4
--- /dev/null
@@ -0,0 +1,78 @@
+"""M2Crypto.SSL.Session client demo2: This program creates two sockets, each
+bound to a different local address. The first creates an SSL connection, the
+second then creates another SSL connection using the first's SSL session id.
+
+(This program only works if you've ifconfig'ed your interfaces correctly,
+of course.)
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Err, Rand, SSL, X509, threading
+m2_threading = threading; del threading
+
+import formatter, getopt, htmllib, sys
+from threading import Thread
+from socket import gethostname
+
+ADDR1 = '127.0.0.1', 9999
+ADDR2 = '127.0.0.2', 9999
+
+def handler(addr, sslctx, host, port, req, sslsess=None):
+
+    s = SSL.Connection(sslctx)
+    s.bind(addr)
+    if sslsess:
+        s.set_session(sslsess)
+        s.connect((host, port))
+    else:
+        s.connect((host, port))
+        sslsess = s.get_session()
+    s.write(req)
+    while 1:
+        data = s.read(4096)
+        if not data:
+            break
+
+    if addr != ADDR2:
+        thr = Thread(target=handler, 
+                    args=(ADDR2, sslctx, host, port, req, sslsess))
+        print "Thread =", thr.getName()
+        thr.start()
+
+    s.close()
+
+
+if __name__ == '__main__':
+
+    m2_threading.init()
+    Rand.load_file('../randpool.dat', -1) 
+
+    host = '127.0.0.1'
+    port = 443
+    req = '/'
+
+    optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:r:')
+    for opt in optlist:
+        if '-h' in opt:
+            host = opt[1]
+        elif '-p' in opt:
+            port = int(opt[1])
+        elif '-r' in opt:
+            req = opt[1]
+    
+    ctx = SSL.Context('sslv3')
+    ctx.load_cert('client.pem')
+    ctx.load_verify_info('ca.pem')
+    ctx.set_verify(SSL.verify_none, 10)
+    
+    req = 'GET %s HTTP/1.0\r\n\r\n' % req
+
+    start = Thread(target=handler, args=(ADDR1, ctx, host, port, req))
+    print "Thread =", start.getName()
+    start.start()
+    start.join()
+    
+    m2_threading.cleanup()
+    Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/ssl/sess2.ssldump.out b/demo/ssl/sess2.ssldump.out
new file mode 100644 (file)
index 0000000..9b383ee
--- /dev/null
@@ -0,0 +1,112 @@
+New TCP connection #1: localhost(9999) <-> localhost(443)
+1 1  0.0061 (0.0061)  C>S  Handshake
+      ClientHello
+        Version 3.0 
+        cipher suites
+        SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+        SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+        SSL_RSA_WITH_3DES_EDE_CBC_SHA
+        SSL_DHE_DSS_WITH_RC4_128_SHA
+        SSL_RSA_WITH_IDEA_CBC_SHA
+        SSL_RSA_WITH_RC4_128_SHA
+        SSL_RSA_WITH_RC4_128_MD5
+        SSL_DHE_DSS_WITH_RC2_56_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_RC4_56_SHA
+        SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5
+        SSL_RSA_EXPORT1024_WITH_RC4_56_MD5
+        SSL_DHE_RSA_WITH_DES_CBC_SHA
+        SSL_DHE_DSS_WITH_DES_CBC_SHA
+        SSL_RSA_WITH_DES_CBC_SHA
+        SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+        SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+        SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
+        SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+        SSL_RSA_EXPORT_WITH_RC4_40_MD5
+        compression methods
+                  NULL
+1 2  0.0068 (0.0006)  S>C  Handshake
+      ServerHello
+        Version 3.0 
+        session_id[32]=
+          f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe 
+          53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6 
+        cipherSuite         SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+        compressionMethod                   NULL
+1 3  0.0846 (0.0778)  S>C  Handshake
+      Certificate
+1 4  0.0848 (0.0001)  S>C  Handshake
+      ServerKeyExchange
+1 5  0.0848 (0.0000)  S>C  Handshake
+      ServerHelloDone
+1 6  0.2475 (0.1627)  C>S  Handshake
+      ClientKeyExchange
+        DiffieHellmanClientPublicValue[128]=
+          3c 8f 0f 93 8a 3a 1e 5c 07 cf 40 2f 7d bf 25 7a 
+          e8 2b ba 54 46 d5 54 40 22 90 38 f2 88 c3 62 8a 
+          ec b1 b7 f1 06 6d fa ba 46 dd f1 92 5f 44 18 44 
+          8e df 33 30 64 79 e0 77 07 1f bc 10 dc 0d 6e 4b 
+          4d 68 01 af 4a bf 83 62 de 87 d7 98 6c e3 9b af 
+          a2 a6 60 67 18 46 89 29 fa 1a 72 df 92 2d 9e 4f 
+          2c b2 02 b3 ef b7 03 07 49 69 c5 b2 37 ae a1 0e 
+          10 e1 79 25 a4 70 02 15 69 d9 47 2c c8 48 23 67 
+1 7  0.2475 (0.0000)  C>S  ChangeCipherSpec
+1 8  0.2475 (0.0000)  C>S  Handshake
+1 9  0.3239 (0.0763)  S>C  ChangeCipherSpec
+1 10 0.3239 (0.0000)  S>C  Handshake
+1 11 0.3266 (0.0027)  C>S  application_data
+1 12 0.3566 (0.0299)  S>C  application_data
+1 13 0.3571 (0.0005)  S>C  Alert
+1    0.3574 (0.0003)  S>C  TCP FIN
+New TCP connection #2: 127.0.0.2(9999) <-> localhost(443)
+2 1  0.0039 (0.0039)  C>S  Handshake
+      ClientHello
+        Version 3.0 
+        resume [32]=
+          f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe 
+          53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6 
+        cipher suites
+        SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+        SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+        SSL_RSA_WITH_3DES_EDE_CBC_SHA
+        SSL_DHE_DSS_WITH_RC4_128_SHA
+        SSL_RSA_WITH_IDEA_CBC_SHA
+        SSL_RSA_WITH_RC4_128_SHA
+        SSL_RSA_WITH_RC4_128_MD5
+        SSL_DHE_DSS_WITH_RC2_56_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_RC4_56_SHA
+        SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA
+        SSL_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5
+        SSL_RSA_EXPORT1024_WITH_RC4_56_MD5
+        SSL_DHE_RSA_WITH_DES_CBC_SHA
+        SSL_DHE_DSS_WITH_DES_CBC_SHA
+        SSL_RSA_WITH_DES_CBC_SHA
+        SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+        SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+        SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
+        SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+        SSL_RSA_EXPORT_WITH_RC4_40_MD5
+        compression methods
+                  NULL
+2 2  0.0055 (0.0016)  S>C  Handshake
+      ServerHello
+        Version 3.0 
+        session_id[32]=
+          f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe 
+          53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6 
+        cipherSuite         SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+        compressionMethod                   NULL
+2 3  0.0055 (0.0000)  S>C  ChangeCipherSpec
+2 4  0.0055 (0.0000)  S>C  Handshake
+2 5  0.0066 (0.0010)  C>S  ChangeCipherSpec
+2 6  0.0066 (0.0000)  C>S  Handshake
+1 14 0.3698 (0.0124)  C>S  Alert
+1    0.3702 (0.0004)  C>S  TCP FIN
+2 7  0.0996 (0.0930)  C>S  application_data
+2 8  0.1224 (0.0227)  S>C  application_data
+2 9  0.1244 (0.0019)  S>C  Alert
+2 10 0.1248 (0.0004)  C>S  Alert
+2    0.1254 (0.0005)  C>S  TCP FIN
+2    0.1300 (0.0046)  S>C  TCP FIN
diff --git a/demo/ssl/socklib.py b/demo/ssl/socklib.py
new file mode 100644 (file)
index 0000000..ebaf6d3
--- /dev/null
@@ -0,0 +1,46 @@
+"""
+socklib provides a way to transparently replace socket.ssl with
+M2Crypto.SSL.Connection.
+
+Usage: Import socklib before the 3rd party module that uses socket.ssl. Also,
+       call socketlib.setSSLContextFactory() to set it up with a way to get
+       secure SSL contexts.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+sslContextFactory = None
+
+def setSSLContextFactory(factory):
+    global sslContextFactory
+    sslContextFactory = factory
+
+from M2Crypto.SSL import Connection, Checker
+import socket
+
+class ssl_socket(socket.socket):
+    def connect(self, addr, *args):
+        self.addr = addr
+        return super(ssl_socket, self).connect(addr, *args)
+        
+    def close(self):
+        if hasattr(self, 'conn'):
+            self.conn.close()
+        socket.socket.close(self)
+
+def ssl(sock):
+    sock.conn = Connection(ctx=sslContextFactory(), sock=sock)
+    sock.conn.addr = sock.addr
+    sock.conn.setup_ssl()
+    sock.conn.set_connect_state()
+    sock.conn.connect_ssl()
+    check = getattr(sock.conn, 'postConnectionCheck', sock.conn.clientPostConnectionCheck)
+    if check is not None:
+        if not check(sock.conn.get_peer_cert(), sock.conn.addr[0]):
+            raise Checker.SSLVerificationError, 'post connection check failed'
+    return sock.conn
+
+socket.socket = ssl_socket
+socket.ssl = ssl
+
diff --git a/demo/ssl/somelib.py b/demo/ssl/somelib.py
new file mode 100644 (file)
index 0000000..21b3491
--- /dev/null
@@ -0,0 +1,21 @@
+"""
+Sample 3rd party lib to use with socklib and myapp.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+# This represents some 3rd party library we don't want to modify
+
+import socket
+
+class HttpsGetSlash(object):
+    def __init__(self):
+        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+    def get(self, host, port):
+        self.socket.connect((host, port))
+        ssl_sock = socket.ssl(self.socket)
+        ssl_sock.write('GET / HTTP/1.0\n\n')
+        print ssl_sock.read()
+        self.socket.close()
\ No newline at end of file
diff --git a/demo/ssl/ss.py b/demo/ssl/ss.py
new file mode 100644 (file)
index 0000000..3db6752
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import os, popen2, time
+from socket import *
+
+def main0():
+    cin, cout = popen2.popen2('openssl s_server')
+    cout.write('Q\n')
+    cout.flush()
+    s = socket(AF_INET, SOCK_STREAM)
+    s.connect(('', 4433))
+    s.close()
+
+def main():
+    pid = os.fork()
+    if pid:
+        time.sleep(1)
+        os.kill(pid, 1)
+        os.waitpid(pid, 0)
+    else:
+        os.execvp('openssl', ('s_server',))
+
+if __name__ == '__main__':
+    main()
+
diff --git a/demo/ssl/twistedsslclient.py b/demo/ssl/twistedsslclient.py
new file mode 100755 (executable)
index 0000000..e4b79c0
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+"""
+Demonstrates M2Crypto.SSL.TwistedProtocolWrapper
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import twisted.internet.protocol as protocol
+import twisted.protocols.basic as basic
+import twisted.internet.reactor as reactor
+import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+import M2Crypto.SSL as SSL
+        
+class EchoClient(basic.LineReceiver):
+    def connectionMade(self):
+        self.sendLine('Hello World!')
+
+    def lineReceived(self, line):
+        print 'received: "%s"' % line
+        self.transport.loseConnection()
+        
+
+class EchoClientFactory(protocol.ClientFactory):
+    protocol = EchoClient
+
+    def clientConnectionFailed(self, connector, reason):
+        print 'connection failed'
+        reactor.stop()
+
+    def clientConnectionLost(self, connector, reason):
+        print 'connection lost'
+        reactor.stop()
+
+
+class ContextFactory:
+    def getContext(self):
+        return SSL.Context()
+
+
+if __name__ == '__main__':
+    factory = EchoClientFactory()
+    wrapper.connectSSL('localhost', 8000, factory, ContextFactory())
+    reactor.run() # This will block until reactor.stop() is called
diff --git a/demo/ssl/twistedsslserver.py b/demo/ssl/twistedsslserver.py
new file mode 100755 (executable)
index 0000000..04897a0
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+"""
+Demonstrates M2Crypto.SSL.TwistedProtocolWrapper
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import sys
+import M2Crypto.SSL as SSL
+import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+import twisted.internet.protocol as protocol
+import twisted.internet.reactor as reactor
+import twisted.python.log as log
+
+
+class Echo(protocol.Protocol):
+    def dataReceived(self, data):
+        print 'received: "%s"' % data
+        self.transport.write(data)
+
+    def connectionMade(self):
+        print 'connection made'
+
+
+class ContextFactory:
+    def getContext(self):
+        ctx = SSL.Context()
+        ctx.load_cert('server.pem')
+        return ctx
+
+
+if __name__ == '__main__':
+    log.startLogging(sys.stdout)
+    factory = protocol.Factory()
+    factory.protocol = Echo
+    wrapper.listenSSL(8000, factory, ContextFactory())
+    reactor.run()
diff --git a/demo/ssl/xmlrpc_cli.py b/demo/ssl/xmlrpc_cli.py
new file mode 100644 (file)
index 0000000..69647d7
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+"""Demonstration of M2Crypto.xmlrpclib2.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand
+from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+
+def ZServerSSL():
+    # Server is Zope-2.6.4 on ZServerSSL/0.12.
+    zs = Server('https://127.0.0.1:8443/', SSL_Transport())
+    print zs.propertyMap()
+
+def xmlrpc_srv():
+    # Server is ../https/START_xmlrpc.py or ./xmlrpc_srv.py.
+    zs = Server('https://127.0.0.1:39443', SSL_Transport())
+    print zs.Testing(1, 2, 3)
+    print zs.BringOn('SOAP')
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1)
+    #ZServerSSL()
+    xmlrpc_srv()
+    Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/xmlrpc_srv.py b/demo/ssl/xmlrpc_srv.py
new file mode 100644 (file)
index 0000000..3811992
--- /dev/null
@@ -0,0 +1,26 @@
+"""Server demonstration of M2Crypto.xmlrpclib2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+from M2Crypto import DH, SSL
+from echod_lib import init_context
+
+# /F's xmlrpcserver.py.
+from xmlrpcserver import RequestHandler
+
+class xmlrpc_handler(RequestHandler):
+    def call(self, method, params):
+        print "XMLRPC call:", method, params
+        return params
+
+    def finish(self):
+        self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+        self.request.close()
+
+if __name__ == '__main__':
+    ctx = init_context('sslv23', 'server.pem', 'ca.pem', SSL.verify_none)
+    ctx.set_tmp_dh('dh1024.pem')
+    s = SSL.ThreadingSSLServer(('', 9443), xmlrpc_handler, ctx)
+    s.serve_forever()   
+
diff --git a/demo/tinderbox/build_lib.py b/demo/tinderbox/build_lib.py
new file mode 100644 (file)
index 0000000..2babb14
--- /dev/null
@@ -0,0 +1,162 @@
+#   Copyright (c) 2006-2007 Open Source Applications Foundation
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Trimmed down for M2Crypto build purposes
+
+import os, sys
+import glob
+import fnmatch
+import shutil
+import fileinput
+import errno
+import subprocess
+import killableprocess
+import tempfile
+
+
+_logFilename   = 'tbox.log'
+_logPrefix     = ''
+_logFile       = None
+_logEcho       = True
+_logEchoErrors = False
+
+
+def initLog(filename, prefix='', echo=True, echoErrors=False):
+    """
+    Initialize log file and store log parameters
+
+    Note: initLog assumes it is called only once per program
+    """
+    global _logFilename, _logPrefix, _logFile, _logEcho, _logEchoErrors
+
+    _logFilename   = filename or _logFilename
+    _logEcho       = echo
+    _logEchoErrors = echoErrors
+    _logPrefix     = prefix
+
+    try:
+        _logFile = open(_logFilename, 'w+')
+        result = True
+    except:
+        result = False
+
+    return result
+
+
+def closeLog():
+    """Need to close log to flush all data."""
+    _logFile.close()
+
+
+def log(msg, error=False, newline='\n'):
+    """
+    Output log message to an open log file or to StdOut
+    """
+    echo = _logEcho
+
+    if _logFile is None:
+        if error or _logEcho:
+            echo = True
+    else:
+        _logFile.write('%s%s%s' % (_logPrefix, msg, newline))
+
+        if error and _logEchoErrors:
+            sys.stderr.write('%s%s%s' % (_logPrefix, msg, newline))
+
+    if echo:
+        sys.stdout.write('%s%s%s' % (_logPrefix, msg, newline))
+        sys.stdout.flush()
+
+
+def setpgid_preexec_fn():
+    os.setpgid(0, 0)
+
+
+def runCommand(cmd, env=None, timeout=-1, logger=log, ignorepreexec=False):
+    """
+    Execute the given command and log all output
+
+        Success and failure codes:
+
+        >>> runCommand(['true'])
+        0
+        >>> runCommand(['false'])
+        1
+
+        Interleaved stdout and stderr messages:
+
+        >>> runCommand(['python', '-c', r'print 1;import sys;sys.stdout.flush();print >>sys.stderr, 2;print 3'])
+        1
+        2
+        3
+        0
+
+        Now with timeout:
+
+        >>> runCommand(['python', '-c', r'print 1;import sys;sys.stdout.flush();print >>sys.stderr, 2;print 3'], timeout=5)
+        1
+        2
+        3
+        0
+
+        Setting environment variable:
+
+        >>> runCommand(['python', '-c', 'import os;print os.getenv("ENVTEST")'], env={'ENVTEST': '42'})
+        42
+        0
+
+        Timeout:
+        >>> runCommand(['sleep', '60'], timeout=5)
+        -9
+    """
+    redirect = True
+
+    if logger == log and _logFile is None:
+        redirect = False
+    else:
+        if timeout == -1:
+            output = subprocess.PIPE
+        else:
+            output = tempfile.TemporaryFile()
+
+    if ignorepreexec:
+        preexec_fn = None
+    else:
+        preexec_fn = setpgid_preexec_fn
+
+    if redirect:
+        p = killableprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, stdout=output, stderr=subprocess.STDOUT, preexec_fn=preexec_fn)
+    else:
+        p = killableprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, preexec_fn=preexec_fn)
+
+    try:
+        if timeout == -1 and redirect:
+            for line in p.stdout:
+                logger(line[:-1])
+
+        p.wait(timeout=timeout, group=True)
+
+    except KeyboardInterrupt:
+        try:
+            p.kill(group=True)
+
+        except OSError:
+            p.wait(30)
+
+    if timeout != -1 and redirect:
+        output.seek(0)
+        for line in output:
+            logger(line[:-1])
+
+    return p.returncode
diff --git a/demo/tinderbox/killableprocess.py b/demo/tinderbox/killableprocess.py
new file mode 100644 (file)
index 0000000..9c4fdb0
--- /dev/null
@@ -0,0 +1,212 @@
+# killableprocess - subprocesses which can be reliably killed
+#
+# Parts of this module are copied from the subprocess.py file contained
+# in the Python distribution.
+#
+# Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
+#
+# Additions and modifications written by Benjamin Smedberg
+# <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
+# <http://www.mozilla.org/>
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of the
+# author not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+r"""killableprocess - Subprocesses which can be reliably killed
+
+This module is a subclass of the builtin "subprocess" module. It allows
+processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
+
+It also adds a timeout argument to Wait() for a limited period of time before
+forcefully killing the process.
+
+Note: On Windows, this module requires Windows 2000 or higher (no support for
+Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
+Python 2.5+ or available from http://python.net/crew/theller/ctypes/
+"""
+
+import subprocess
+import sys
+import os
+import time
+import types
+
+try:
+    from subprocess import CalledProcessError
+except ImportError:
+    # Python 2.4 doesn't implement CalledProcessError
+    class CalledProcessError(Exception):
+        """This exception is raised when a process run by check_call() returns
+        a non-zero exit status. The exit status will be stored in the
+        returncode attribute."""
+        def __init__(self, returncode, cmd):
+            self.returncode = returncode
+            self.cmd = cmd
+        def __str__(self):
+            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
+mswindows = (sys.platform == "win32")
+
+if mswindows:
+    import winprocess
+else:
+    import signal
+
+def call(*args, **kwargs):
+    waitargs = {}
+    if "timeout" in kwargs:
+        waitargs["timeout"] = kwargs.pop("timeout")
+
+    return Popen(*args, **kwargs).wait(**waitargs)
+
+def check_call(*args, **kwargs):
+    """Call a program with an optional timeout. If the program has a non-zero
+    exit status, raises a CalledProcessError."""
+
+    retcode = call(*args, **kwargs)
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = args[0]
+        raise CalledProcessError(retcode, cmd)
+
+if not mswindows:
+    def DoNothing(*args):
+        pass
+
+class Popen(subprocess.Popen):
+    if mswindows:
+        def _execute_child(self, args, executable, preexec_fn, close_fds,
+                           cwd, env, universal_newlines, startupinfo,
+                           creationflags, shell,
+                           p2cread, p2cwrite,
+                           c2pread, c2pwrite,
+                           errread, errwrite):
+            if not isinstance(args, types.StringTypes):
+                args = subprocess.list2cmdline(args)
+
+            if startupinfo is None:
+                startupinfo = winprocess.STARTUPINFO()
+
+            if None not in (p2cread, c2pwrite, errwrite):
+                startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
+                
+                startupinfo.hStdInput = int(p2cread)
+                startupinfo.hStdOutput = int(c2pwrite)
+                startupinfo.hStdError = int(errwrite)
+            if shell:
+                startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
+                startupinfo.wShowWindow = winprocess.SW_HIDE
+                comspec = os.environ.get("COMSPEC", "cmd.exe")
+                args = comspec + " /c " + args
+
+            # We create a new job for this process, so that we can kill
+            # the process and any sub-processes 
+            self._job = winprocess.CreateJobObject()
+
+            creationflags |= winprocess.CREATE_SUSPENDED
+            creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
+
+            hp, ht, pid, tid = winprocess.CreateProcess(
+                executable, args,
+                None, None, # No special security
+                1, # Must inherit handles!
+                creationflags,
+                winprocess.EnvironmentBlock(env),
+                cwd, startupinfo)
+            
+            self._child_created = True
+            self._handle = hp
+            self._thread = ht
+            self.pid = pid
+
+            winprocess.AssignProcessToJobObject(self._job, hp)
+            winprocess.ResumeThread(ht)
+
+            if p2cread is not None:
+                p2cread.Close()
+            if c2pwrite is not None:
+                c2pwrite.Close()
+            if errwrite is not None:
+                errwrite.Close()
+
+    def kill(self, group=True):
+        """Kill the process. If group=True, all sub-processes will also be killed."""
+        if mswindows:
+            if group:
+                winprocess.TerminateJobObject(self._job, 127)
+            else:
+                winprocess.TerminateProcess(self._handle, 127)
+            self.returncode = 127    
+        else:
+            if sys.platform == 'cygwin':
+                cmd = "taskkill /f /pid " + str(self.pid)
+                if group:
+                    cmd += " /t"
+                os.system(cmd)
+            elif group:
+                os.killpg(self.pid, signal.SIGKILL)
+            else:
+                os.kill(self.pid, signal.SIGKILL)
+            self.returncode = -9
+
+    def wait(self, timeout=-1, group=True):
+        """Wait for the process to terminate. Returns returncode attribute.
+        If timeout seconds are reached and the process has not terminated,
+        it will be forcefully killed. If timeout is -1, wait will not
+        time out."""
+
+        if self.returncode is not None:
+            return self.returncode
+
+        if mswindows:
+            if timeout != -1:
+                timeout = timeout * 1000
+            rc = winprocess.WaitForSingleObject(self._handle, timeout)
+            if rc == winprocess.WAIT_TIMEOUT:
+                self.kill(group)
+            else:
+                self.returncode = winprocess.GetExitCodeProcess(self._handle)
+        else:
+            if timeout == -1:
+                subprocess.Popen.wait(self)
+                return self.returncode
+            
+            starttime = time.time()
+
+            # Make sure there is a signal handler for SIGCHLD installed
+            oldsignal = signal.signal(signal.SIGCHLD, DoNothing)
+
+            while time.time() < starttime + timeout - 0.01:
+                pid, sts = os.waitpid(self.pid, os.WNOHANG)
+                if pid != 0:
+                    self._handle_exitstatus(sts)
+                    signal.signal(signal.SIGCHLD, oldsignal)
+                    return self.returncode
+                
+                # time.sleep is interrupted by signals (good!)
+                newtimeout = timeout - time.time() + starttime
+                time.sleep(newtimeout)
+            self.kill(group)
+            signal.signal(signal.SIGCHLD, oldsignal)
+            subprocess.Popen.wait(self)
+
+        return self.returncode
diff --git a/demo/tinderbox/slave.py b/demo/tinderbox/slave.py
new file mode 100755 (executable)
index 0000000..eaa64c0
--- /dev/null
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+#
+"""
+This is a sample Tinderbox2 buildslave script.
+
+NOTE: WAIT at least 6 minutes after the last build before starting
+      the next build!
+
+Create config.ini file with the following contents:
+
+[build]
+name = identify your build slave, for example Ubuntu 8.04 32-bit
+;;optional fields:
+;;uname = uname -a
+;;swig = swig -version
+;;cc = gcc --version
+;;openssl = openssl version
+;;python = python --version
+;;clean = rm -fr m2crypto
+;;svn = svn co http://svn.osafoundation.org/m2crypto/trunk m2crypto
+;;patch = 
+;;build = python setup.py clean --all build
+;; OR another way to do tests without setuptools:
+;;build = PYTHONPATH=build/lib-something python tests/alltests.py
+;;test = python setup.py test
+;;wait = 3600
+;;timeout = 180
+
+[email]
+from = your email
+to = Email Heikki Toivonen to get the address
+user = smtp username
+password = smtp password
+server = smtp server
+port = smtp port
+"""
+
+import time, smtplib, os, ConfigParser, tempfile
+import build_lib as bl
+
+# Change to True when you are troubleshooting this build script
+debug_script = False
+
+# These commands assume we are running on a unix-like system where default
+# build options work and all prerequisites are installed and in PATH etc.
+DEFAULT_COMMANDS = {
+  'uname': ['uname', '-a'],
+  'swig': ['swig', '-version'],
+  'cc': ['gcc', '--version'],
+  'openssl': ['openssl', 'version'],
+  'python': ['python', '--version'],
+  'clean': ['rm', '-rf', 'm2crypto'],
+  'svn': ['svn', 'co', 'http://svn.osafoundation.org/m2crypto/trunk', 'm2crypto'],
+  'patch': [],
+  'build': ['python', 'setup.py', 'clean', '--all', 'build'],
+  'test': ['python', 'setup.py', 'test']
+}
+
+def load_config(cfg='config.ini'):
+    config = {}
+    cp = ConfigParser.ConfigParser()
+    cp.read(cfg)
+    for section in cp.sections():
+        for option in cp.options(section):
+            config[option] = cp.get(section, option).strip()
+    return config
+
+# XXX copied from test_ssl
+def zap_servers():
+    s = 's_server'
+    fn = tempfile.mktemp() 
+    cmd = 'ps | egrep %s > %s' % (s, fn)
+    os.system(cmd)
+    f = open(fn)
+    while 1:
+        ps = f.readline()
+        if not ps:
+            break
+        chunk = string.split(ps)
+        pid, cmd = chunk[0], chunk[4]
+        if cmd == s:
+            os.kill(int(pid), 1)
+    f.close()
+    os.unlink(fn)
+
+def build(commands, config):
+    status = 'success'
+    
+    cwd = os.getcwd()
+    timeout = int(config.get('timeout') or 180)
+    
+    bl.initLog('tbox.log', echo=debug_script)
+    
+    starttime = int(time.time())
+    
+    for command in commands:
+        cmd = config.get(command) 
+        if not cmd:
+            cmd = DEFAULT_COMMANDS[command]
+            if not cmd:
+                continue
+        else:
+            cmd = cmd.split()
+        
+        bl.log('*** %s, timeout=%ds' % (' '.join(cmd), timeout))
+        
+        exit_code = bl.runCommand(cmd, timeout=timeout) 
+        if exit_code:
+            bl.log('*** error exit code = %d' % exit_code)
+            if command == 'test':
+                status = 'test_failed'
+                if os.name != 'nt':
+                    try:
+                        # If tests were killed due to timeout, we may have left
+                        # openssl processes running, so try killing
+                        zap_servers()
+                    except Exception, e:
+                        bl.log('*** error: tried to zap_servers: ' + str(e))
+            else:
+                status = 'build_failed'
+            break
+        if command == 'svn':
+            os.chdir('m2crypto')
+        
+    timenow = int(time.time())
+    
+    bl.closeLog()
+    
+    os.chdir(cwd)
+
+    return 'tbox.log', starttime, timenow, status
+
+
+def email(logpath, starttime, timenow, status, config):
+    msg = """From: %(from)s
+To: %(to)s
+Subject: tree: M2Crypto
+
+
+tinderbox: tree: M2Crypto
+tinderbox: starttime: %(starttime)d
+tinderbox: timenow: %(timenow)d
+tinderbox: status: %(status)s
+tinderbox: buildname: %(buildname)s
+tinderbox: errorparser: unix
+tinderbox: END
+
+""" % {'from': config['from'], 'to': config['to'], 
+       'starttime': starttime, 'timenow': timenow,
+       'status': status,
+       'buildname': config['name']}
+    
+    msg += open(logpath).read()
+    
+    server = smtplib.SMTP(host=config['server'], port=int(config['port']))
+    if debug_script:
+        server.set_debuglevel(1)
+    server.starttls() # if your server supports STARTTLS
+    if config.get('user'):
+        server.login(config['user'], config['password'])
+    server.sendmail(config['from'], config['to'], msg)
+    server.quit()
+
+
+if __name__ == '__main__':
+    config = load_config()    
+    
+    commands = ['uname', 'swig', 'cc', 'openssl', 'python', 'clean', 'svn',
+                'patch', 'build', 'test']
+
+    logpath, starttime, timenow, status = build(commands, config)
+    email(logpath, starttime, timenow, status, config)
diff --git a/demo/tinderbox/winprocess.py b/demo/tinderbox/winprocess.py
new file mode 100644 (file)
index 0000000..4d867fe
--- /dev/null
@@ -0,0 +1,262 @@
+# A module to expose various thread/process/job related structures and
+# methods from kernel32
+#
+# The MIT License
+#
+# Copyright (c) 2006 the Mozilla Foundation <http://www.mozilla.org>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE
+from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD
+
+LPVOID = c_void_p
+LPBYTE = POINTER(BYTE)
+LPDWORD = POINTER(DWORD)
+
+def ErrCheckBool(result, func, args):
+    """errcheck function for Windows functions that return a BOOL True
+    on success"""
+    if not result:
+        raise WinError()
+    return args
+
+# CloseHandle()
+
+CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE)
+CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32))
+CloseHandle.errcheck = ErrCheckBool
+
+# AutoHANDLE
+
+class AutoHANDLE(HANDLE):
+    """Subclass of HANDLE which will call CloseHandle() on deletion."""
+    def Close(self):
+        if self.value:
+            CloseHandle(self)
+            self.value = 0
+    
+    def __del__(self):
+        self.Close()
+
+    def __int__(self):
+        return self.value
+
+def ErrCheckHandle(result, func, args):
+    """errcheck function for Windows functions that return a HANDLE."""
+    if not result:
+        raise WinError()
+    return AutoHANDLE(result)
+
+# PROCESS_INFORMATION structure
+
+class PROCESS_INFORMATION(Structure):
+    _fields_ = [("hProcess", HANDLE),
+                ("hThread", HANDLE),
+                ("dwProcessID", DWORD),
+                ("dwThreadID", DWORD)]
+
+    def __init__(self):
+        Structure.__init__(self)
+        
+        self.cb = sizeof(self)
+
+LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
+
+# STARTUPINFO structure
+
+class STARTUPINFO(Structure):
+    _fields_ = [("cb", DWORD),
+                ("lpReserved", LPWSTR),
+                ("lpDesktop", LPWSTR),
+                ("lpTitle", LPWSTR),
+                ("dwX", DWORD),
+                ("dwY", DWORD),
+                ("dwXSize", DWORD),
+                ("dwYSize", DWORD),
+                ("dwXCountChars", DWORD),
+                ("dwYCountChars", DWORD),
+                ("dwFillAttribute", DWORD),
+                ("dwFlags", DWORD),
+                ("wShowWindow", WORD),
+                ("cbReserved2", WORD),
+                ("lpReserved2", LPBYTE),
+                ("hStdInput", HANDLE),
+                ("hStdOutput", HANDLE),
+                ("hStdError", HANDLE)
+                ]
+LPSTARTUPINFO = POINTER(STARTUPINFO)
+
+STARTF_USESHOWWINDOW    = 0x01
+STARTF_USESIZE          = 0x02
+STARTF_USEPOSITION      = 0x04
+STARTF_USECOUNTCHARS    = 0x08
+STARTF_USEFILLATTRIBUTE = 0x10
+STARTF_RUNFULLSCREEN    = 0x20
+STARTF_FORCEONFEEDBACK  = 0x40
+STARTF_FORCEOFFFEEDBACK = 0x80
+STARTF_USESTDHANDLES    = 0x100
+
+# EnvironmentBlock
+
+class EnvironmentBlock:
+    """An object which can be passed as the lpEnv parameter of CreateProcess.
+    It is initialized with a dictionary."""
+
+    def __init__(self, dict):
+        if not dict:
+            self._as_parameter_ = None
+        else:
+            values = ["%s=%s" % (key, value)
+                      for (key, value) in dict.iteritems()]
+            values.append("")
+            self._as_parameter_ = LPCWSTR("\0".join(values))
+        
+# CreateProcess()
+
+CreateProcessProto = WINFUNCTYPE(BOOL,                  # Return type
+                                 LPCWSTR,               # lpApplicationName
+                                 LPWSTR,                # lpCommandLine
+                                 LPVOID,                # lpProcessAttributes
+                                 LPVOID,                # lpThreadAttributes
+                                 BOOL,                  # bInheritHandles
+                                 DWORD,                 # dwCreationFlags
+                                 LPVOID,                # lpEnvironment
+                                 LPCWSTR,               # lpCurrentDirectory
+                                 LPSTARTUPINFO,         # lpStartupInfo
+                                 LPPROCESS_INFORMATION  # lpProcessInformation
+                                 )
+
+CreateProcessFlags = ((1, "lpApplicationName", None),
+                      (1, "lpCommandLine"),
+                      (1, "lpProcessAttributes", None),
+                      (1, "lpThreadAttributes", None),
+                      (1, "bInheritHandles", True),
+                      (1, "dwCreationFlags", 0),
+                      (1, "lpEnvironment", None),
+                      (1, "lpCurrentDirectory", None),
+                      (1, "lpStartupInfo"),
+                      (2, "lpProcessInformation"))
+
+def ErrCheckCreateProcess(result, func, args):
+    ErrCheckBool(result, func, args)
+    # return a tuple (hProcess, hThread, dwProcessID, dwThreadID)
+    pi = args[9]
+    return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID
+
+CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32),
+                                   CreateProcessFlags)
+CreateProcess.errcheck = ErrCheckCreateProcess
+
+CREATE_BREAKAWAY_FROM_JOB = 0x01000000
+CREATE_DEFAULT_ERROR_MODE = 0x04000000
+CREATE_NEW_CONSOLE = 0x00000010
+CREATE_NEW_PROCESS_GROUP = 0x00000200
+CREATE_NO_WINDOW = 0x08000000
+CREATE_SUSPENDED = 0x00000004
+CREATE_UNICODE_ENVIRONMENT = 0x00000400
+DEBUG_ONLY_THIS_PROCESS = 0x00000002
+DEBUG_PROCESS = 0x00000001
+DETACHED_PROCESS = 0x00000008
+
+# CreateJobObject()
+
+CreateJobObjectProto = WINFUNCTYPE(HANDLE,             # Return type
+                                   LPVOID,             # lpJobAttributes
+                                   LPCWSTR             # lpName
+                                   )
+
+CreateJobObjectFlags = ((1, "lpJobAttributes", None),
+                        (1, "lpName", None))
+
+CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32),
+                                       CreateJobObjectFlags)
+CreateJobObject.errcheck = ErrCheckHandle
+
+# AssignProcessToJobObject()
+
+AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL,      # Return type
+                                            HANDLE,    # hJob
+                                            HANDLE     # hProcess
+                                            )
+AssignProcessToJobObjectFlags = ((1, "hJob"),
+                                 (1, "hProcess"))
+AssignProcessToJobObject = AssignProcessToJobObjectProto(
+    ("AssignProcessToJobObject", windll.kernel32),
+    AssignProcessToJobObjectFlags)
+AssignProcessToJobObject.errcheck = ErrCheckBool
+
+# ResumeThread()
+
+def ErrCheckResumeThread(result, func, args):
+    if result == -1:
+        raise WinError()
+
+    return args
+
+ResumeThreadProto = WINFUNCTYPE(DWORD,      # Return type
+                                HANDLE      # hThread
+                                )
+ResumeThreadFlags = ((1, "hThread"),)
+ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32),
+                                 ResumeThreadFlags)
+ResumeThread.errcheck = ErrCheckResumeThread
+
+# TerminateJobObject()
+
+TerminateJobObjectProto = WINFUNCTYPE(BOOL,   # Return type
+                                      HANDLE, # hJob
+                                      UINT    # uExitCode
+                                      )
+TerminateJobObjectFlags = ((1, "hJob"),
+                           (1, "uExitCode", 127))
+TerminateJobObject = TerminateJobObjectProto(
+    ("TerminateJobObject", windll.kernel32),
+    TerminateJobObjectFlags)
+TerminateJobObject.errcheck = ErrCheckBool
+
+# WaitForSingleObject()
+
+WaitForSingleObjectProto = WINFUNCTYPE(DWORD,  # Return type
+                                       HANDLE, # hHandle
+                                       DWORD,  # dwMilliseconds
+                                       )
+WaitForSingleObjectFlags = ((1, "hHandle"),
+                            (1, "dwMilliseconds", -1))
+WaitForSingleObject = WaitForSingleObjectProto(
+    ("WaitForSingleObject", windll.kernel32),
+    WaitForSingleObjectFlags)
+
+INFINITE = -1
+WAIT_TIMEOUT = 0x0102
+WAIT_OBJECT_0 = 0x0
+WAIT_ABANDONED = 0x0080
+
+# GetExitCodeProcess()
+
+GetExitCodeProcessProto = WINFUNCTYPE(BOOL,    # Return type
+                                      HANDLE,  # hProcess
+                                      LPDWORD, # lpExitCode
+                                      )
+GetExitCodeProcessFlags = ((1, "hProcess"),
+                           (2, "lpExitCode"))
+GetExitCodeProcess = GetExitCodeProcessProto(
+    ("GetExitCodeProcess", windll.kernel32),
+    GetExitCodeProcessFlags)
+GetExitCodeProcess.errcheck = ErrCheckBool
diff --git a/demo/x509/ca.py b/demo/x509/ca.py
new file mode 100644 (file)
index 0000000..446998b
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+"""
+How to create a CA certificate with Python.
+
+WARNING: This sample only demonstrates how to use the objects and methods,
+         not how to create a safe and correct certificate.
+
+Copyright (c) 2004 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+
+from M2Crypto import RSA, X509, EVP, m2, Rand, Err
+
+# XXX Do I actually need more keys?
+# XXX Check return values from functions
+
+def generateRSAKey():
+    return RSA.gen_key(2048, m2.RSA_F4)
+
+def makePKey(key):
+    pkey = EVP.PKey()
+    pkey.assign_rsa(key)
+    return pkey
+    
+def makeRequest(pkey):
+    req = X509.Request()
+    req.set_version(2)
+    req.set_pubkey(pkey)
+    name = X509.X509_Name()
+    name.CN = 'My CA, Inc.'
+    req.set_subject_name(name)
+    ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+    ext2 = X509.new_extension('nsComment', 'Hello there')
+    extstack = X509.X509_Extension_Stack()
+    extstack.push(ext1)
+    extstack.push(ext2)
+
+    assert(extstack[1].get_name() == 'nsComment')
+    
+    req.add_extensions(extstack)
+    req.sign(pkey, 'sha1')
+    return req
+
+def makeCert(req, caPkey):
+    pkey = req.get_pubkey()
+    #woop = makePKey(generateRSAKey())
+    #if not req.verify(woop.pkey):
+    if not req.verify(pkey):
+        # XXX What error object should I use?
+        raise ValueError, 'Error verifying request'
+    sub = req.get_subject()
+    # If this were a real certificate request, you would display
+    # all the relevant data from the request and ask a human operator
+    # if you were sure. Now we just create the certificate blindly based
+    # on the request.
+    cert = X509.X509()
+    # We know we are making CA cert now...
+    # Serial defaults to 0.
+    cert.set_serial_number(1)
+    cert.set_version(2)
+    cert.set_subject(sub)
+    issuer = X509.X509_Name()
+    issuer.CN = 'The Issuer Monkey'
+    issuer.O = 'The Organization Otherwise Known as My CA, Inc.'
+    cert.set_issuer(issuer)
+    cert.set_pubkey(pkey)
+    notBefore = m2.x509_get_not_before(cert.x509)
+    notAfter  = m2.x509_get_not_after(cert.x509)
+    m2.x509_gmtime_adj(notBefore, 0)
+    days = 30
+    m2.x509_gmtime_adj(notAfter, 60*60*24*days)
+    cert.add_ext(
+        X509.new_extension('subjectAltName', 'DNS:foobar.example.com'))
+    ext = X509.new_extension('nsComment', 'M2Crypto generated certificate')
+    ext.set_critical(0)# Defaults to non-critical, but we can also set it
+    cert.add_ext(ext)
+    cert.sign(caPkey, 'sha1')
+
+    assert(cert.get_ext('subjectAltName').get_name() == 'subjectAltName')
+    assert(cert.get_ext_at(0).get_name() == 'subjectAltName')
+    assert(cert.get_ext_at(0).get_value() == 'DNS:foobar.example.com')
+    
+    return cert
+
+def ca():
+    key = generateRSAKey()
+    pkey = makePKey(key)
+    req = makeRequest(pkey)
+    cert = makeCert(req, pkey)
+    return (cert, pkey)
+
+if __name__ == '__main__':
+    Rand.load_file('../randpool.dat', -1)
+    rsa = generateRSAKey()
+    pkey = makePKey(rsa)
+    req = makeRequest(pkey)
+    print req.as_text()
+    cert = makeCert(req, pkey)
+    print cert.as_text()
+    print cert.as_pem()
+    cert.save_pem('my_ca_cert.pem')
+    rsa.save_key('my_key.pem', 'aes_256_cbc')
+    Rand.save_file('../randpool.dat')
diff --git a/demo/x509/certdata2pem.py b/demo/x509/certdata2pem.py
new file mode 100755 (executable)
index 0000000..9fd6d32
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+"""
+Small utility to convert the Mozilla-format certificates
+(/mozilla/security/nss/lib/ckfw/builtins/certdata.txt in the Mozilla CVS)
+into PEM. Got the idea from http://curl.haxx.se/docs/parse-certs.txt.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+"""
+
+import array
+from M2Crypto import X509
+
+counter = 0
+value = None
+name = None
+
+out = open('cacert.pem', 'wb')
+
+for line in open('certdata.txt'):
+    line = line.strip()
+    if line.startswith('CKA_LABEL'):
+        assert value is None
+
+        label_encoding, name, dummy = line.split('"')
+        label, encoding = label_encoding.split()
+
+        assert encoding == 'UTF8'
+
+    elif line == 'CKA_VALUE MULTILINE_OCTAL':
+        assert name is not None
+
+        value = array.array('c')
+
+    elif value is not None and line == 'END':
+        assert name is not None
+
+        print 'Writing ' + name
+        x509 = X509.load_cert_string(value.tostring(), X509.FORMAT_DER)
+        if not x509.verify():
+            print '  Skipping ' + name + ' since it does not verify'
+            name = None
+            value = None
+            continue
+        counter += 1
+
+        out.write(name + '\n' + '=' * len(name) + '\n\n')
+        out.write('SHA1 Fingerprint=' + x509.get_fingerprint('sha1') + '\n')
+        out.write(x509.as_text())
+        out.write(x509.as_pem())
+        out.write('\n')
+
+        name = None
+        value = None
+
+    elif value is not None:
+        assert name is not None
+
+        for number in line.split('\\'):
+            if not number:
+                continue
+
+            value.append(chr(int(number, 8)))
+
+print 'Wrote %d certificates' % counter
diff --git a/demo/x509/client2.pem b/demo/x509/client2.pem
new file mode 100644 (file)
index 0000000..166919a
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAnegAwIBAgIBAzANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMTEyMDEzMDMwNVoXDTAyMTEyMDEzMDMwNVowWzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRowGAYDVQQDExFNMkNyeXB0
+byBDbGllbnQgMjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAn9qneRYTKPokme3obiJa2NTz1Z2kcF3NHVh60Qod
+/TV/q4olPrZdFR2TDWt63Lgnygcsgf3u9pnhcEGk6IvntwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFDKWFe6VWMhtRTE3/78+hAnSGxmvMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBABpE9xt1Hlq2dQZUXHuX
+HI57vc2mlWnhhM0wnNhsNZFwfXRHCZOo/JJBhEIT3Rgyz0ErrbOr1SN96HNDKXOD
+z6bh4NxB5DZ9sRPKEBj66zDsWJVMlom+Lkeal+GkVy36vpAyP1r+cTXyc9M2Gw/o
+FBMinMHH/BXvF5GJ+UleheZe
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAJ/ap3kWEyj6JJnt6G4iWtjU89WdpHBdzR1YetEKHf01f6uKJT62
+XRUdkw1rety4J8oHLIH97vaZ4XBBpOiL57cCAwEAAQJANXfspprUo9MvpPEn2pbR
+Lk/kk2IcW510e0laI0uwBj50djfHqvsU5ccuVLrxowngLGrFmM3G4lnMknR2NvH8
+0QIhAMsK0AwStUNM/KyvIMikHHBOE9PrK7ARgKvlKl+0ieWPAiEAyYwonIVAtr1f
+M8vmrc6TM2YxzSq4+jyYktaaNhYw11kCIA5pmhMBUPSSBm2LkNwtKgeewzGLw/If
+i+6nubZJbnBpAiEAvJQvy4PCsTkvQr+d7zJB+O2920IGId1gxMOXNtQ8jsECIGvn
+Uz54oonshmTg+Kj2DxnUKQEzFAmQLbtFslp1m47v
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBFTCBwAIBADBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGjAY
+BgNVBAMTEU0yQ3J5cHRvIENsaWVudCAyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBv
+c3QxLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCf2qd5FhMo+iSZ7ehuIlrY
+1PPVnaRwXc0dWHrRCh39NX+riiU+tl0VHZMNa3rcuCfKByyB/e72meFwQaToi+e3
+AgMBAAGgADANBgkqhkiG9w0BAQQFAANBAHI5KXfL6kIRoNjR8G9/uKiPUt4uVBKF
+ecGp87M5t2a92Z0KpWOMXSHZ0LLQKqwWzALvWcPPIj6S8F6ENdwpfMk=
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/x509/demo1.py b/demo/x509/demo1.py
new file mode 100644 (file)
index 0000000..f0aa282
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""X.509 certificate manipulation and such.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import os
+
+from M2Crypto import X509
+from M2Crypto.EVP import MessageDigest
+
+def demo1():
+    print 'Test 1: As DER...'
+    cert1 = X509.load_cert('server.pem')
+    der1 = cert1.as_der()
+    dgst1 = MessageDigest('sha1')
+    dgst1.update(der1)
+    print 'Using M2Crypto:\n', `dgst1.final()`, '\n'
+
+    cert2 = os.popen('openssl x509 -inform pem -outform der -in server.pem')
+    der2 = cert2.read()
+    dgst2 = MessageDigest('sha1')
+    dgst2.update(der2)
+    print 'Openssl command line:\n', `dgst2.final()`, '\n'
+
+
+def demo2():
+    print 'Test 2: As text...'
+    cert = X509.load_cert('client2.pem')
+    print 'version     ', cert.get_version()
+    print 'serial#     ', cert.get_serial_number()
+    print 'not before  ', cert.get_not_before()
+    print 'not after   ', cert.get_not_after()
+    issuer = cert.get_issuer()
+    #print 'issuer      ', issuer
+    print 'issuer.C    ', `issuer.C`
+    print 'issuer.SP   ', `issuer.SP`
+    print 'issuer.L    ', `issuer.L`
+    print 'issuer.O    ', `issuer.O`
+    print 'issuer.OU   ', `issuer.OU`
+    print 'issuer.CN   ', `issuer.CN`
+    print 'issuer.Email', `issuer.Email`
+    print 'subject     ', cert.get_subject()
+    #print cert.as_text(), '\n'
+
+def demo3():
+    cert = X509.load_cert('server.pem')
+    while 1:
+        x = cert.get_subject()
+
+if __name__ == "__main__":
+    #demo1()
+    demo2()
+    #demo3()
diff --git a/demo/x509/proxy_destroy.py b/demo/x509/proxy_destroy.py
new file mode 100644 (file)
index 0000000..c79bd2f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL MKRodriguez@lbl.gov
+############################################################################ 
+"""
+Script that destroys a proxy certificate file by overwriting its contents
+before the file is removed
+""" 
+
+import proxylib
+import optparse, os 
+
+USAGEHELP = "proxy_destroy.py file1 file2  Destroys files listed"
+JUNK = "LalalAlalaLalalALalalAlalaLalalALalalAlalaLalalALalalAlalaLalalA"
+
+def scrub_file(filename):
+    """
+    Overwrite the file with junk, before removing it
+    """
+    s = os.stat(filename)
+    proxy_file = file(filename, "w") 
+    size = s.st_size 
+    while size > 64: 
+        proxy_file.write(JUNK)
+        size -= 64
+    
+    proxy_file.flush()
+    proxy_file.close()
+    os.remove(filename) 
+
+
+def main():
+    parser = optparse.OptionParser() 
+    parser.set_usage(USAGEHELP)
+    opts, args = parser.parse_args() 
+    if len(args) is 0:
+        proxy_file = proxylib.get_proxy_filename() 
+        scrub_file(proxy_file) 
+    
+    for proxy_file in args:
+        scrub_file(proxy_file)
+
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxy_info.py b/demo/x509/proxy_info.py
new file mode 100644 (file)
index 0000000..ce9cdc3
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL MKRodriguez@lbl.gov
+############################################################################ 
+"""
+script that displays information about a proxy certificate
+"""
+
+
+import proxylib
+import time, datetime, calendar
+import sys, optparse
+
+FILEHELP = "Location of the proxy."
+
+def print_info(proxy_cert):
+    """
+    Print information about the proxy cert
+    """
+    cert = proxy_cert.getcert()
+    print "Subject: ", cert.get_subject().as_text()
+    print "Issuer: ", cert.get_issuer().as_text()
+    pubkey = cert.get_pubkey() 
+    size =  pubkey.size()
+    print "Strength: ", size * 8 
+    after = cert.get_not_after()
+    after_tuple = time.strptime(str(after),"%b  %d %H:%M:%S %Y %Z") 
+    expires = calendar.timegm(after_tuple)
+    now = datetime.timedelta(seconds=time.time()) 
+    expires = datetime.timedelta(seconds=expires) 
+    td = expires - now 
+    if td.days < 0:
+        print "Time left: Proxy has expired."
+    else: 
+        hours = td.seconds / 3600 
+        hours += td.days * 24 
+        minutes = (td.seconds % 3600) / 60
+        seconds =  (td.seconds % 3600) % 60
+        print "Time left: %d:%d:%d" % (hours, minutes, seconds) 
+        fraction = round((float(td.seconds) / float(3600 * 24)), 1) 
+        print "Days left: ", str(td.days) + str(fraction)[1:]      
+     
+   
+def main(): 
+    parser = optparse.OptionParser()
+    parser.add_option("-f", "--file", dest="filename", help=FILEHELP)
+    (opts, args) = parser.parse_args()
+    filename = opts.filename 
+    if filename is None:   
+        proxyfile = proxylib.get_proxy_filename()
+    else:
+        proxyfile = filename
+    proxy_cert = proxylib.Proxy()
+    try:
+        proxy_cert.read(proxyfile)
+    except IOError:
+        print "The file: " + proxyfile + " does not exist."
+        sys.exit(0)
+    print_info(proxy_cert)
+
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxy_init.py b/demo/x509/proxy_init.py
new file mode 100644 (file)
index 0000000..1ca5cbb
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL  MKRodriguez@lbl.gov 
+############################################################################ 
+"""
+script that generates a proxy certificate
+"""
+
+import proxylib 
+import optparse 
+import sys
+
+OUTHELP = "Location of the new proxy cert."
+CERTHELP = "Location of user certificate."
+KEYHELP  = "Location of the user key."
+VALIDHELP = "h:m Proxy certificate is valid for h hours and m minutes."
+FULLPROXY = "Creates a limited proxy"
+def main():
+    parser = optparse.OptionParser()
+    parser.add_option('-o', '--output', dest='output', help=OUTHELP)
+    parser.add_option('-c', '--cert' , dest='cert', help=CERTHELP)
+    parser.add_option('-k', '--key', dest='key', help=KEYHELP)
+    parser.add_option('-v', '--valid', dest='valid', help=VALIDHELP)
+    parser.add_option('-l', '--limited', action="store_true", 
+                      default=False, dest='limited', help=VALIDHELP)
+    (opts, args) = parser.parse_args()
+    kw = {} 
+    kw['cert'] = opts.cert
+    kw['key'] = opts.key
+    if opts.valid is None:
+        valid_tuple = (12, 0)
+    else:
+        valid = opts.valid.split(':') 
+        valid_tuple = tuple(map(int, valid))
+    kw['valid'] = valid_tuple 
+    kw['full'] = not opts.limited
+    try:
+        proxy_factory = proxylib.ProxyFactory(kw)
+    except IOError:
+        print "Can't find usercert or userkey. Use the -c or -k arguments"
+        sys.exit(0)
+    proxy_factory.generate()
+    proxy_cert = proxy_factory.getproxy()
+    if opts.output is None:
+        proxy_cert.write(proxylib.get_proxy_filename())
+    else:
+        proxy_cert.write(opts.output)
+    
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxylib.py b/demo/x509/proxylib.py
new file mode 100644 (file)
index 0000000..e0f3395
--- /dev/null
@@ -0,0 +1,327 @@
+############################################################################
+# Matt Rodriguez, LBNL
+#Copyright (c) 2003, The Regents of the University of California,
+#through Lawrence Berkeley National Laboratory
+#(subject to receipt of any required approvals from the U.S. Dept. of Energy).
+#All rights reserved.
+############################################################################
+"""
+API to generated proxy certificates
+""" 
+import os, sys 
+import struct
+import re
+import time, calendar, datetime
+
+import_regex = re.compile(r"\s*libssl.so.0.9.8\s*")
+errstr = "You must have the openssl 0.9.8 libraries in your LD_LIBRARY_PATH"""
+
+try:
+    from M2Crypto import BIO, X509, RSA, EVP, ASN1
+except ImportError, ex: 
+    if import_regex.match(str(ex)):
+        print errstr 
+        sys.exit(-1)
+    else:
+        raise ex 
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC  = MBSTRING_FLAG | 1
+KEY_USAGE_VALUE = "Digital Signature, Key Encipherment, Data Encipherment"
+PCI_VALUE_FULL = "critical, language:Inherit all"
+PCI_VALUE_LIMITED = "critical, language:1.3.6.1.4.1.3536.1.1.1.9"
+
+def create_write_file(fname, perm=0600):
+    """
+    Creates a file to write to while avoiding a possible race condition.
+    This is essential for writing out the proxy file. Need to make sure
+    there is no pre-existing file.
+    """
+    if os.path.exists(fname):
+        os.remove(fname)
+    # Make sure the file doesn't exist. Will throw an exception if
+    # it does. This would only happen if the code is attacked.
+    fd = os.open(fname, os.O_CREAT|os.O_EXCL|os.O_WRONLY, perm)
+    f = os.fdopen(fd, 'w')
+    return f
+
+
+class ProxyFactoryException(Exception):
+    """
+    Base class for exceptions in the ProxyFactory class
+    """
+
+class Proxy:
+    """
+    class that holds proxy certificate information,
+    consisting of an issuer cert a user cert and 
+    a key for the user cert
+    """
+    def __init__(self):
+        self._key = None
+        self._cert = None 
+        self._issuer = None
+
+    def read(self, proxypath=None):
+        """
+        reads in a proxy certificate information
+        """
+        if proxypath is None:
+            proxypath = get_proxy_filename()
+            
+        proxyfile = open(proxypath)
+        bio = BIO.File(proxyfile)    
+        self._cert = X509.load_cert_bio(bio)
+        self._key = RSA.load_key_bio(bio)
+        self._issuer = X509.load_cert_bio(bio)
+    
+    def getcert(self):
+        """
+        Returns a X509 instance 
+        """
+        return self._cert
+
+    def getkey(self):
+        """
+        Returns a RSA instance
+        """
+        return self._key
+
+    def getissuer(self):
+        """
+        Returns a X509 instance 
+        """ 
+        return self._issuer
+
+    def setcert(self, cert):
+        """
+        Sets the user cert should be a X509 instance
+        """
+        self._cert = cert
+
+    def setkey(self, key):
+        """
+        Sets the user key should be a RSA instance
+        """
+        self._key = key 
+
+    def setissuer(self, issuer):
+        """
+        Sets the issuer cert should be a X509 instance
+        """
+        self._issuer = issuer
+
+    def write(self, proxypath=None):
+        """
+        Writes the proxy information to a file
+        """
+        proxyfile = create_write_file(proxypath)
+        bio = BIO.File(proxyfile) 
+        bio.write(self._cert.as_pem())
+        self._key.save_key_bio(bio, cipher=None) 
+        bio.write(self._issuer.as_pem())
+        bio.close()
+        os.chmod(proxypath, 0600) 
+        
+
+class ProxyFactory:
+    """
+    Creates proxies
+    """
+    def __init__(self, kw={'cert':None,'key':None,'valid':(12,0),'full':True}):
+        
+        self._usercert = get_usercert(kw['cert']) 
+        self._userkey = get_userkey(kw['key']) 
+        self._proxycert = None
+        self._proxykey = None 
+        self._valid = kw['valid'] 
+        self._full = kw['full']
+        
+    def generate(self): 
+        """
+        generates a new proxy like grid-proxy-init 
+        """ 
+        if not self._check_valid():
+            raise ProxyFactoryException("The issuer cert is expired")
+        if self._proxycert is None:
+            self._proxycert = X509.X509()        
+            key = EVP.PKey()
+            self._proxykey = RSA.gen_key(512, 65537)
+            key.assign_rsa(self._proxykey, capture=0)
+            self._proxycert.set_pubkey(key)
+        self._proxycert.set_version(2)
+        self._set_times()
+        issuer_name = self._usercert.get_subject()
+        self._proxycert.set_issuer_name(issuer_name) 
+        serial_number = self._make_serial_number(self._proxycert) 
+        self._proxycert.set_serial_number(serial_number)
+        self._set_subject()
+        sign_pk = EVP.PKey()
+        sign_pk.assign_rsa(self._userkey) 
+        self._add_extensions() 
+        self._proxycert.sign(sign_pk, 'md5')  
+
+    def set_proxycert(self, proxycert):
+        """
+        This method is useful if you don't
+        want to pay the costs associated with
+        generating a new key pair.
+        """
+        self._proxycert = proxycert 
+
+    def getproxy(self): 
+        """
+        Return a proxy instance
+        """
+        proxy = Proxy() 
+        proxy.setissuer(self._usercert)
+        proxy.setcert(self._proxycert)
+        proxy.setkey(self._proxykey)
+        return proxy
+    
+    def _set_subject(self):
+        """
+        Internal method that sets the subject name
+        """
+        subject_name = X509.X509_Name() 
+        serial_number = self._make_serial_number(self._proxycert) 
+        issuer_name = self._usercert.get_subject()
+        issuer_name_txt = issuer_name.as_text() 
+        seq = issuer_name_txt.split(",")
+        for entry in seq:
+            name_component = entry.split("=") 
+            subject_name.add_entry_by_txt(field=name_component[0].strip(),
+                                         type=MBSTRING_ASC,
+                                         entry=name_component[1],len=-1,
+                                         loc=-1, set=0)
+        
+        
+        subject_name.add_entry_by_txt(field="CN", 
+                                      type=MBSTRING_ASC, 
+                                      entry=str(serial_number), 
+                                      len=-1, loc=-1, set=0)
+        
+        self._proxycert.set_subject_name(subject_name)
+    
+    def _set_times(self):
+        """
+        Internal function that sets the time on the proxy
+        certificate
+        """
+        not_before = ASN1.ASN1_UTCTIME()
+        not_after = ASN1.ASN1_UTCTIME()
+        not_before.set_time(int(time.time())) 
+        offset = (self._valid[0] * 3600) + (self._valid[1] * 60)
+        not_after.set_time(int(time.time()) + offset )
+        self._proxycert.set_not_before(not_before)
+        self._proxycert.set_not_after(not_after)
+
+    def _make_serial_number(self, cert):
+        """
+        Lifted from the globus code
+        """
+        message_digest = EVP.MessageDigest('sha1')
+        pubkey = cert.get_pubkey()
+        der_encoding = pubkey.as_der() 
+        message_digest.update(der_encoding)
+        digest = message_digest.final()
+        digest_tuple = struct.unpack('BBBB', digest[:4])
+        sub_hash = long(digest_tuple[0] + (digest_tuple[1] + ( digest_tuple[2] + 
+                       ( digest_tuple[3] >> 1) * 256 ) * 256) * 256) 
+        return sub_hash
+    
+    def _add_extensions(self):
+        """
+        Internal method that adds the extensions to the certificate
+        """
+        key_usage_ext = X509.new_extension("keyUsage", KEY_USAGE_VALUE, 1) 
+        self._proxycert.add_ext(key_usage_ext)
+        if self._full:
+            pci_ext = X509.new_extension("proxyCertInfo", 
+                                        PCI_VALUE_FULL, 1, 0)  
+        else:
+            pci_ext = X509.new_extension("proxyCertInfo", 
+                                        PCI_VALUE_LIMITED, 1, 0)  
+        self._proxycert.add_ext(pci_ext)
+    def _check_valid(self):
+        """
+        Internal method that ensures the issuer cert has
+        valid, not_before and not_after fields
+        """
+        before_time = self._usercert.get_not_before()
+        after_time = self._usercert.get_not_after()
+        before_tuple = time.strptime(str(before_time), "%b %d %H:%M:%S %Y %Z")
+        after_tuple = time.strptime(str(after_time), "%b %d %H:%M:%S %Y %Z")
+        starts =  datetime.timedelta(seconds=calendar.timegm(before_tuple))
+        expires = datetime.timedelta(seconds=calendar.timegm(after_tuple))
+        now = datetime.timedelta(seconds=time.time())
+        time_delta = expires - now
+        #cert has expired
+        if time_delta.days < 0:
+            return False
+        #cert is not yet valid, not likely but should still return False
+        time_delta = now - starts
+        if time_delta.days < 0:   
+            return False
+        
+        return True
+    
+#Utility Functions
+def get_proxy_filename():  
+    """
+    function that returns the default proxy path 
+    which is /tmp/x509up_uuid
+    """
+    if os.name == 'posix':
+        proxy_filename = "x509up_u" + (str(os.getuid())) 
+        proxypath = os.path.join("/tmp", proxy_filename) 
+    elif os.name == 'nt':
+        username = os.getenv("USERNAME")
+        if username is None:
+            raise RuntimeError("""USERNAME is not set in environment. Can't 
+            determine proxy file location""")
+             
+        proxy_filename = "x509up_u" + username
+        drive = os.path.splitdrive(os.getcwd())[0]
+        proxydir = drive + os.sep + "temp"
+        proxypath = os.path.join(proxydir, proxy_filename) 
+    else:
+        except_string = """get_proxy_filename is not supported on this platform
+                           Try explicitly specifying the location of the 
+                           proxyfile""" 
+        raise RuntimeError(except_string)
+    return proxypath
+
+def get_usercert(certfile=None):
+    """
+    function that returns a X509 instance which 
+    is the user cert that is expected to be a ~/.globus/usercert.pem
+    
+    A check is performed to ensure the certificate has valid
+    before and after times.
+    """
+    if certfile is None:
+        certfile = open(os.path.join(os.getenv("HOME"),
+                                    ".globus","usercert.pem"))
+    else:
+        certfile = open(certfile)
+    bio = BIO.File(certfile)
+    cert = X509.load_cert_bio(bio) 
+    return cert
+
+def get_userkey(keyfile=None):
+    """
+    function that returns a X509 instance which 
+    is the user cert that is expected to be a ~/.globus/userkey.pem
+    """
+    if keyfile is None:
+        keyfile = open(os.path.join(os.getenv("HOME"),
+                                   ".globus","userkey.pem")) 
+    else:
+        keyfile = open(keyfile)
+    bio = BIO.File(keyfile)
+    key = RSA.load_key_bio(bio)
+    return key
+
+
diff --git a/demo/x509/server-expired.pem b/demo/x509/server-expired.pem
new file mode 100644 (file)
index 0000000..80ef9dc
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/x509/server.pem b/demo/x509/server.pem
new file mode 100644 (file)
index 0000000..1ee9282
--- /dev/null
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/x509/x509auth.py b/demo/x509/x509auth.py
new file mode 100644 (file)
index 0000000..a1f6e4b
--- /dev/null
@@ -0,0 +1,675 @@
+#!/usr/bin/env python2
+#
+# vim: ts=4 sw=4 nowrap
+#
+# ChannelHandler
+#   ReceiveChannel
+#   SendChannel
+#
+# SocketDispatcher
+#   Loop
+#   Poll
+#   ReadEvent
+#   WriteEvent
+#
+import sys, re, time, thread, os
+import getopt
+import exceptions
+import select
+import string
+import socket
+import StringIO
+import traceback
+
+
+from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN, EINTR, EISCONN
+
+STDOUT = sys.stdout
+
+IDstdin  = 0
+IDstdout = 1
+IDstderr = 2
+
+
+
+
+class ExitNow (exceptions.Exception):
+    pass
+
+
+class AuthError(exceptions.Exception):
+    def __init__ (self, msg):
+        self.msg = msg
+    def __str__ (self):
+        return repr( self.msg )
+
+#----------------------------------------------------------------
+#
+# Class Security Certification
+#
+#----------------------------------------------------------------
+import M2Crypto
+import random
+import base64
+import sha
+
+class CertHandler:
+
+    def __init__ ( self ):
+        self.Nonce       = None
+        self.CurrentObj  = {}
+        #self.ServerName = socket.gethostbyaddr(socket.gethostname())[2][0]
+        self.ServerName  = 'AuthInstance'
+
+        self.ObjNames    = { 'C' : 'countryName', 'ST' : 'stateOrProvinceName', 'L' : 'localityName', 'O' : 'organizationName', 'OU' : 'organizationalUnitName',  'CN' : 'commonName', 'email' : 'emailAddress' }
+        self.ObjMap      = {}
+
+        self.Params     = { 'Digest'    : 'sha1',
+                            'Version'   : 1,
+                            'Serial'    : 1,
+                            'NotBefore' : ( 60 * 60 * 24 * 365 ),
+                            'NotAfter'  : ( 60 * 60 * 24 * 365 * 5 ),
+                            'Issuer'    : { 'countryName' : 'US', 'stateOrProvinceName' : 'florida', 'localityName'  : 'tampas', 'organizationName' : 'watersprings', 'organizationalUnitName' : 'security', 'commonName' : 'Certificate Authority', 'emailAddress' : 'admin@security' },
+                            'Subject'   : { 'countryName' : 'US', 'stateOrProvinceName' : 'florida', 'localityName'  : 'miami', 'organizationName' : 'watersprings', 'organizationalUnitName' : 'security', 'commonName' : 'Certificate Authority', 'emailAddress' : 'admin@security' }
+                          }
+        self.KeyEnv     = { 'RsaPubKey' : [ '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----' ],
+                            'RsaPKey'   : [ '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----' ],
+                            'X509Cert'  : [ '-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----' ]
+                          }
+        self.CertContainer ()
+
+        self.ObjFromContainer ( ObjName='CA' )
+        self.ObjFromContainer ( ObjName=self.ServerName )
+        self.ServerObj = self.ObjMap[ self.ServerName ]
+
+
+
+
+
+    def CertContainer (self):
+        self.PemMap     = { 'CA'            : { 'Subject'   : {'organizationalUnitName': 'security', 'organizationName': 'watersprings', 'commonName': 'Certificate Authority', 'stateOrProvinceName': 'florida', 'countryName': 'US', 'emailAddress': 'admin@security', 'localityName': 'miami'},
+                                                'RsaPKey'   : ['MIICXQIBAAKBgQDmAl+4+XdF34D3kBN58An29mA8/D+NUHVJW+XeE96uDJ9mw8f1', 'xguVYgfpMaiVihW/qDWZRu/NhWOfheKBVNstx5OcqIjY10vBvGAG17CQZhcon8eN', 'Kufg7XzON7e5WXXD8qyklhuesHtTEGGpZ1FfA+n+D/0JF3YfTBDeYyY2VQIDAQAB', 'AoGBAI53L/Uxx7fmzUoJ2pZvoKxwRIHhuDd+e3c5zbJ1WjsyJFWRtLw9tBUOCFpf', 'YM1nHzt8I97RulzxXxiC5B45ghu3S0s+B06oEOUxNLbjsai08DKBRFqM+IZIx11r', 'IM/tZsTdJg1KtKojRu63NDtOzR6a7ggTeMge5CDKpXVWpvVtAkEA+QF/q2NnsdmL', 'ak6ALl8QwMbvwujJcjLwvecHQJmB0jO9KF60Hh4VY8mTRIMZ/r9Wf+REsQcZtmhG', 'WRr12si5qwJBAOx4R0Wd/inoXOpvKheIoKgTg01FnLhT8uiLY59CuZRr6AcTELjC', 'Kvk6LyfhspYBkUwWAEwKxJ3kMeqXG+k8z/8CQQCy+GDKzqe5LKMHxWRb7/galuG9', 'NZOUgQiHdYXA6JRmgMl0Op07CGRXVIqEs7X7Y4rIYUj99ByG/muRn88VcTABAkBQ', 'Z6V0WoBtp4DQhfP+BIr8G4Zt49miI4lY4OyC3qFTgk1m+miZKgyKqeoW2Xtr3iSV', 'hnWbZZ3tQgZnCfKHoBHpAkAmf2OvfhLxaW1PwdjBdm9tFGVbzkLFDqdqww2aHRUx', 'sXonHyVG2EDm37qW7nzmAqUgQCueMhHREZQYceDrtLLO'],
+                                                'X509Cert'  : ['MIICpzCCAhCgAwIBAQIBATANBgkqhkiG9w0BAQUFADCBmTERMA8GA1UECxMIc2Vj', 'dXJpdHkxFTATBgNVBAoTDHdhdGVyc3ByaW5nczEeMBwGA1UEAxMVQ2VydGlmaWNh', 'dGUgQXV0aG9yaXR5MRAwDgYDVQQIEwdmbG9yaWRhMQswCQYDVQQGEwJVUzEdMBsG', 'CSqGSIb3DQEJARYOYWRtaW5Ac2VjdXJpdHkxDzANBgNVBAcTBnRhbXBhczAeFw0w', 'MzAzMzExMDQ2MDVaFw0wOTAzMjkxMDQ2MDVaMIGYMREwDwYDVQQLEwhzZWN1cml0', 'eTEVMBMGA1UEChMMd2F0ZXJzcHJpbmdzMR4wHAYDVQQDExVDZXJ0aWZpY2F0ZSBB', 'dXRob3JpdHkxEDAOBgNVBAgTB2Zsb3JpZGExCzAJBgNVBAYTAlVTMR0wGwYJKoZI', 'hvcNAQkBFg5hZG1pbkBzZWN1cml0eTEOMAwGA1UEBxMFbWlhbWkwgZ8wDQYJKoZI', 'hvcNAQEBBQADgY0AMIGJAoGBAOYCX7j5d0XfgPeQE3nwCfb2YDz8P41QdUlb5d4T', '3q4Mn2bDx/XGC5ViB+kxqJWKFb+oNZlG782FY5+F4oFU2y3Hk5yoiNjXS8G8YAbX', 'sJBmFyifx40q5+DtfM43t7lZdcPyrKSWG56we1MQYalnUV8D6f4P/QkXdh9MEN5j', 'JjZVAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAK7f4YodUnT7Ygp7BWBBDHSq/r+tY', 'H69ly3W23U5VupaIglNiNQoMqnZpVVfcuIYltajrux5TSH4gPbenCg163Ua8RvF6', 'E2JElccprbKiCf9tf8l6Rxpsall4EF+CazP56DiUD1NfGLhWp9V2ga9SoynEo1P1', 'eztMBfk01atBJ/s=']
+                                              },
+                            'AuthInstance'  : { 'Subject'   : {'commonName': 'AuthInstance', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@AuthInstance'},
+                                                'RsaPKey'   : ['MIICXQIBAAKBgQCwfB6CoOQTJTd6D4ua1G/H9hwqpdVUMMjG3O8Y93vYGesZdwtT', '1iEQX/6TWACBxa7jOC8hHUHe2lsPu7imHv8dDiD59Rzets7BM88HsJTemYrxSv5G', 'uh8FloB1KEtSHeCZSlDT/tzSX4M0JfVPmtx+0FsyDOVZ6jXjRIyIKgqDlwIDAQAB', 'AoGAYc8YFatXW6j3mwU8iL2NidPC/nvTxAoZa+UL+dlG4JhUrFNGitsUjf+1ljFi', 'bomBiFod/Is7c2euqgSOrDpnheYlogv2QpnP80YUpiv9OruaB9I1zqJ7QM7PrkrH', 'm1C36DzyzVY+4DMvTV29do4Mf6CKT8xf6hXlLK/NbqwO9NkCQQDYwxwCTWxrkX08', '+0c5KaTYxfqCByxOqoiKl97p6wHxNtlzdLeFoSZD0n3Q1c2v0DIXhcBPRPPaZBWC', 'yTayMkRzAkEA0G6I5mHQVNIx18Xmc75urC0QWrum9cj5VcyRvl3KCzB2kQoXkx6v', 'y0JN6YS37rSp8vmvIFNO/oHWSuEJlFYfTQJAajWv07D8Hvj61JaLH4c4Lr9TL8M0', 'Apesr7wajaOJIBgwFFJsWh3MEg9hdqJMVok9AimXQUAX/DpuD9dn5Yib4QJBAIdt', 'Kno2V7ylDkmahk/yDcrFRPkPMD5GpOrAjnnYSqzWglNe8U5gA+zXWfQ+jZwFut7q', 'qIUiXBM1nVzttuGwy4kCQQC3MHppypSWoFqd+TaxK3MX/HoZqaoRELXdeiniOt3Z', 'gFMJ4m6D9lL4segWDoDpequjDYxv2cl+wS1+qDOyeG3J'],
+                                                'X509Cert'  : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTE1', 'N1oXDTA5MDMyOTEwNTE1N1owUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAQXV0', 'aEluc3RhbmNlMREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMQXV0aEluc3Rh', 'bmNlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwfB6CoOQTJTd6D4ua1G/H', '9hwqpdVUMMjG3O8Y93vYGesZdwtT1iEQX/6TWACBxa7jOC8hHUHe2lsPu7imHv8d', 'DiD59Rzets7BM88HsJTemYrxSv5Guh8FloB1KEtSHeCZSlDT/tzSX4M0JfVPmtx+', '0FsyDOVZ6jXjRIyIKgqDlwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFsXewlXnKpH', 'uSwxavmLPUqRk7o1D5E3ByTddBe3BY5NqEXk7Y2SJFtumiuUY5/sWB/aO8Xbqj/b', '/7Cwg9+bc9QqxeeIe/YvtFOmv1ELh2BC1Nof7zSa5rLa/+gPYCoogS4mLRMuUfRk', 'tVHhpoxL1B+UXp4jNeKeTgquOjpUiyBR']
+                                              },
+                            '192.168.1.20' : {  'Subject'   : {'commonName': '192.168.1.20', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@192.168.1.20'},
+                                                'RsaPKey'   : ['MIICWwIBAAKBgQDD285BGY+FHhfpRvcqupN8X2lPwUNq4G7k5kit5cyuQLfN0+eQ', 'I+VFZdtfJhCZC54dEIvNgA4I7563pRUD0S9rmN6kh/M0GgrKZjYNO+CvvG2dts26', 'MGK0eUQaSsvDf9phEA+0mSv9dsUrdyBTJBn4mXvApekYHt+mNLfCVLkM1QIDAQAB', 'AoGAMqcFB3cJ0/59Zpowz/8ip3axcKvluJ1EcLRRtY+JyML6BiQ4beGqqLD38/qP', 'LlV/1bpyvXnRp2P5IztxXORbo77IzDVzl62YesQATnecSCMLTaeOusy2EZZsjE0k', 'V2cR1rZvzyJPY+Fi8X54hiB+5IcKkPRX9LVw7+yBbBh4sKECQQD0Yi0/DGa3IetR', '9F+/jgN/VIcTd5KwMBW3Bw/+Bh78ZlZGaucpRiR1IQuD7sLTnhNS6RMJUxv10jnS', 'BGW9pjX5AkEAzSslOGFyJ5Aoy48rgC2kKwq6nFKJ/PmY92cnm0nqmwb2npbOtDxz', 'sPUdb7oYmUU/nVCJh3yb+KJIw2g9XxnUvQJAG8ybNwPTH1vlZ+Izjhe6gB5+axF8', 'BzzBC5vrDstldPKzN7lraD+JYCWNKMndMbNWoWTP/IyOrqzmVOSZKjShCQJAbzuE', 'C2QxaqeqpmnxkKWuCrPfZl8NdryvpPolK/jQG8qTrHlgibD4nCjYE7nWGkrD6Xs/', 'hNgXC56YSnDaTRQJFQJAD5GFACv9QgcMZhy1hza0yGDMSQ0WR8/y3CJhi3DPOuAf', 'MetGM1kLQR8bDFrl7yEs+Nufk8QTsE5ngZ7dGFgmuA=='],
+                                                'X509Cert'  : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTMw', 'NVoXDTA5MDMyOTEwNTMwNVowUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAMTky', 'LjE2OC4xLjIwMREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMMTkyLjE2OC4x', 'LjIwMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDD285BGY+FHhfpRvcqupN8', 'X2lPwUNq4G7k5kit5cyuQLfN0+eQI+VFZdtfJhCZC54dEIvNgA4I7563pRUD0S9r', 'mN6kh/M0GgrKZjYNO+CvvG2dts26MGK0eUQaSsvDf9phEA+0mSv9dsUrdyBTJBn4', 'mXvApekYHt+mNLfCVLkM1QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAA5pWiXJOHCJ', 'P6kHcBITJuwv94zZ0dbHby1ljUfG/8z3Fmr8JRUcTuTtgVYH9M0O9QujIR8YLWSV', '0GeD3nkLRn0+ezam0CW0dF/Ph5vNXFP4a0DSEVv7T0G21VFmbUV3xrVeaXARFuLa', 'AtqRoSyBMajd3g0WNXDCgGEH7LvzJ5EP']
+                                              },
+                            '192.168.1.26' : {  'Subject'   : {'commonName': '192.168.1.26', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@192.168.1.26'},
+                                                'RsaPKey'   : ['MIICXQIBAAKBgQC5ILRHC3wFoqG9Egb96N3iGEnVrgvQikHyXYc/jFMUgB79rVJp', 'hY1MziGkSjSyc3RFMshkjHlMlARMPCNtomIikqAQaO4Eke2SYWyaOBoTdkeOy+yZ', 't/POpoGp3nRmKGed6NNcdMd5BO01GiatUb7X/Se3Yyvmj5UcEmv/hZQGFwIDAQAB', 'AoGBALdR5FMp0zE9X437iQLsErQuOwcmpzplfnJDHYfXK/nz+TxY4m/tuQNiZ7vp', 'Y4+Gdo+Dfx7aX89uD2dycd7B2wwTziBGIEjhusD8gtralVjhBDjCowSOkezWTeY+', '2h40NB4e1uypOZb0PXWvAL/l9xN7NBGioq9zmShT5c+FFO8RAkEA4k3QSaT1ScGI', '5II5JolvPnv6yS+0dCQTn1SC2ABWbH75NDUHMGAdNIf1sqhaLSQnY9GuXhb8XqX6', 'UUhoypUHzwJBANFrqnuEuTNKR0HVD31/2trPYLfZL6/9RUsR4mlvxPb0tX+T5LVL', '5he43zbura/lZqNxt0ZVeD03LanPN7bvZzkCQQCMAToIJa6+x6YKQOpchhA1pvwb', 'NZE9fQhKvT0JpwPQsak4/EmLSxsmYarGsdLANKrN3W4ztaLCZ4r6eIKkOhkPAkBz', 'ke4wYitucbRnUTRONuvJSx599x6JCcVey0zekO7qtlsfP7e8kVk2iDCu+QLjCj8d', 'Pdk9uFc1uSi7CH8ftniJAkBLNYF0kfGC+CaTuyfnIwiBZ/tjmm4UvHfwtlaZHJYc', 'QIjimBxVA7mujrv3xIBTiDMdxUhq9YIaKIEdlveaTwPK'],
+                                                'X509Cert'  : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTQx', 'NloXDTA5MDMyOTEwNTQxNlowUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAMTky', 'LjE2OC4xLjI2MREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMMTkyLjE2OC4x', 'LjI2MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5ILRHC3wFoqG9Egb96N3i', 'GEnVrgvQikHyXYc/jFMUgB79rVJphY1MziGkSjSyc3RFMshkjHlMlARMPCNtomIi', 'kqAQaO4Eke2SYWyaOBoTdkeOy+yZt/POpoGp3nRmKGed6NNcdMd5BO01GiatUb7X', '/Se3Yyvmj5UcEmv/hZQGFwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAMPd5SXzpwZ+', '40SdOv/PeQ5cjieDm6QjndWE/T8nG2I5h6YRWbZPohsCClQjrTyZCMXwcUiCimuJ', 'BaMigI/YqP5THVv58Gu8DpoVZppz7uhUNS5hsuV9lxZUh1bRkUtL6n0qSTEdM34I', 'NJBJKGlf0skULg9BT4LJYTPGWJ0KosUl']
+                                              }
+                          }
+
+    def CreateObj ( self, ObjName='CA' ):
+        self.ObjMap[ObjName]   = {  'RsaPKey'    : None,
+                                    'PsaPubKey'  : None,
+                                    'EvpPKey'    : None,
+                                    'EvpPubKey'  : None,
+                                    'X509Req'    : None,
+                                    'X509Cert'   : None,
+                                }
+        self.CurrentObj = self.ObjMap[ObjName]
+
+    def ObjFromContainer (self, ObjName='CA' ):
+        if not self.ObjMap.has_key( ObjName ):
+            self.CreateObj ( ObjName=ObjName )
+            self.PKeyFromPemRepr ( ObjName=ObjName )
+            self.CertFromPemRepr ( ObjName=ObjName )
+
+    def PKeyFromPemRepr ( self, ObjName=None, PemPKey=None ):
+        def callback (): return ''
+        if self.PemMap.has_key( ObjName ):
+            UsedPemPKey = self.KeyEnv['RsaPKey'][0] + '\n' + string.join( self.PemMap[ObjName]['RsaPKey'], '\n' ) + '\n' + self.KeyEnv['RsaPKey'][1] + '\n'
+        else:
+            if not PemPKey:
+                raise AuthError( 'no such Object "%s" in container - abort!' % ObjName )
+            else:
+                UsedPemPKey = PemPKey
+        self.CurrentObj['RsaPKey']   = M2Crypto.RSA.load_key_string( UsedPemPKey, callback )
+        self.CurrentObj['EvpPKey']   = M2Crypto.EVP.PKey ( md='sha1' )
+        self.CurrentObj['EvpPKey'].assign_rsa ( self.CurrentObj['RsaPKey'] )
+        self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.new_pub_key( self.CurrentObj['RsaPKey'].pub () )
+        
+    def CertFromPemRepr ( self, ObjName=None, PemCert=None ):
+        if self.PemMap.has_key( ObjName ):
+            UsedPemCert = self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[ObjName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n'
+        else:
+            UsedPemCert = PemCert
+        self.CurrentObj['X509Cert']  = M2Crypto.X509.load_cert_string( PemCert )
+        self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+        #self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.rsa_from_pkey( self.CurrentObj['EvpPubKey'] )
+
+    def ObjNameFromPemCert ( self, PemCert=None ):
+        """
+        generate objmap structure and fill it with values from PemCert
+        return ObjName string
+        """
+        X509Cert   = M2Crypto.X509.load_cert_string( PemCert )
+        Subject    = X509Cert.get_subject ()
+        SubjectTxt = Subject.print_ex ()
+        SubjectTxtList = re.split('[\n\r]', SubjectTxt )
+        SubjectMap = {}
+        for Entry in SubjectTxtList:
+            ( Key, Value ) = re.split('=', Entry)
+            if self.ObjNames.has_key( Key ):
+                SubjectMap[ self.ObjNames[Key] ] = Value
+            else:
+                SubjectMap[ Key ] = Value
+        if not SubjectMap.has_key( 'commonName' ):
+            return False
+        ObjName = SubjectMap['commonName']
+        if not self.ObjMap.has_key( ObjName ):
+            self.CreateObj( ObjName=ObjName )
+            self.CurrentObj = self.ObjMap[ObjName]
+            self.CurrentObj['X509Cert']  = X509Cert
+            self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+            self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.rsa_from_pkey( self.CurrentObj['EvpPubKey'] )
+        else:
+            self.CurrentObj = self.ObjMap[ ObjName ]
+        return ObjName
+        
+
+
+    def ServerCert ( self ):
+        self.ObjFromContainer( ObjName='X509Auth' )
+
+
+    def CreatePKey ( self ):
+        def PassPhraseFkt (): return ''
+        RsaKeyParams    = { 'KeyLength'       : 1024,
+                            'PubExponent'     : 0x10001,           # -> 65537
+                            'keygen_callback' : PassPhraseFkt
+                          }
+        self.CurrentObj['RsaPKey'] = M2Crypto.RSA.gen_key( RsaKeyParams['KeyLength'], RsaKeyParams['PubExponent'], RsaKeyParams['keygen_callback'] )
+        self.CurrentObj['EvpPKey'] = M2Crypto.EVP.PKey ( md=self.Params['Digest'] )
+        self.CurrentObj['EvpPKey'].assign_rsa ( self.CurrentObj['RsaPKey'] )
+        #print self.EvpPKey
+
+
+    def CreateCert ( self, SignEvpPKey=None ):
+        """
+        generate new x509 certificate
+        SignEvpKey    pkey to sign x509 certification, if None  this x509 cert will be self signed
+        """
+        self.CurrentObj = self.ObjMap['CA']
+        self.CreatePKey ()
+        X509Cert = M2Crypto.X509.X509 ()
+        X509Cert.set_version ( self.Params['Version'] )
+        X509Cert.set_serial_number ( self.Params['Serial'] )
+        X509Cert.set_not_before ( int( time.time() - self.Params['NotBefore'] ))        # 1 year in the past
+        X509Cert.set_not_after ( int( time.time() + self.Params['NotAfter'] ))          # 5 years in the future
+        X509Cert.set_issuer ( self.Params['Issuer'] )
+        X509Cert.set_subject ( self.Params['Subject'] )
+        X509Cert.set_pubkey ( self.CurrentObj['EvpPKey'] )
+        if SignEvpPKey:
+            X509Cert.sign ( SignEvpPKey, self.Params['Digest'] )
+        else:
+            X509Cert.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+        self.CurrentObj['X509Cert'] = X509Cert
+        self.DumpOutInternalPemRepr( ObjName='CA' )
+
+
+    def CreateObjCert (self, ObjName):
+        """
+        generate Obj with new PKey and Request, signed by 'CA'
+        ObjName    the primary key to identify key-pair
+        """
+        # new obj
+        if not self.ObjMap.has_key( ObjName ):
+            self.ObjMap[ObjName] = {}
+        self.CurrentObj = self.ObjMap[ObjName]
+        if not self.CurrentObj.has_key( 'Subject' ):
+            self.CurrentObj['Subject'] = { 'organizationalUnitName' : 'security', 'commonName' : ObjName, 'emailAddress' : 'ioclient@' + ObjName }
+        # new pkey      
+        self.CreatePKey ()
+        # new request
+        self.CreateReq ( SignEvpPKey=self.ObjMap['CA']['EvpPKey'] )
+        # new certification
+        if not self.Req2Cert ( SignEvpPKey=self.ObjMap['CA']['EvpPKey'] ):
+            print "300 error occured while verifying - abort!"
+        # shipout x509 certification
+        self.DumpOutInternalPemRepr( ObjName=ObjName )
+
+
+    def CreateReq ( self, SignEvpPKey=None ):
+        X509Req = M2Crypto.X509.Request ()
+        if self.Params['Version']:
+            X509Req.set_version ( self.Params['Version'] )
+        X509Req.set_subject ( self.CurrentObj['Subject'] )
+        X509Req.set_pubkey ( self.CurrentObj['EvpPKey'] )
+        if SignEvpPKey:
+            X509Req.sign ( SignEvpPKey, self.Params['Digest'] )
+        else:
+            X509Req.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+        self.CurrentObj['X509Req'] = X509Req
+
+
+    def Req2Cert ( self, SignEvpPKey=None ):
+        X509Cert = M2Crypto.X509.X509 ()
+        Version = self.CurrentObj['X509Req'].get_version ()
+        X509Cert.set_version ( Version )
+        X509Cert.set_serial ( self.Params['Serial'] )
+        X509Cert.set_not_before ( int( time.time() - self.Params['NotBefore'] ))        # 1 year in the past
+        X509Cert.set_not_after ( int( time.time() + self.Params['NotAfter'] ))          # 5 years in the future
+        Issuer = self.ObjMap['CA']['X509Cert'].get_issuer ()
+        X509Cert.set_issuer_name ( Issuer )
+        X509Name_Subject = self.CurrentObj['X509Req'].get_subject ()
+        X509Cert.set_subject_name ( X509Name_Subject )
+        PKey = self.CurrentObj['X509Req'].get_pubkey ()
+        EvpPKey = M2Crypto.EVP.PKey( PKey )
+        X509Cert.set_pubkey ( EvpPKey )
+        if SignEvpPKey:
+            X509Cert.sign ( SignEvpPKey, self.Params['Digest'] )
+        else:
+            X509Cert.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+        self.CurrentObj['X509Cert'] = X509Cert
+        if self.VerifyCert ( SignEvpPKey ):
+            return True
+        else:
+            return False
+
+        
+
+
+    #--------------------------------
+    # CertHandler  Verifying
+    #--------------------------------
+    def ExtractPublicKeyFromCert ( self ):
+        self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+
+    def VerifyCert ( self, EvpPKey ):
+        if dir(EvpPKey).count('_ptr'):
+            Result = self.CurrentObj['X509Cert'].verify ( EvpPKey._ptr() )
+        else:
+            Result = self.CurrentObj['X509Cert'].verify ( EvpPKey )
+        if Result:
+            return True
+        return False
+
+
+
+    #--------------------------------
+    # CertHandler  DumpOut
+    #--------------------------------
+    def DumpOutInternalPemRepr( self, ObjName='unknown', File='PyReprPem.txt' ):
+        if File:
+            open( File, 'w').write("\t\t\t\t'%s' : { " % ( ObjName ))
+        else:
+            sys.stdout.write("\t\t\t\t'%s' : { " % ( ObjName ))
+        self.ShowX509CertSubject ( File )
+        self.RsaPKey2PemRepr ( File, Cipher=None )      # unprotectd pkey representation
+        self.X509Cert2PemRepr ( File )
+        if File:
+            open( File, 'a').write("\t\t\t\t\t  }\n")
+        else:
+            sys.stdout.write("\t\t\t\t\t  }\n")
+
+    def ShowX509CertIssuer ( self, File=None ):
+        IssuerName = self.CurrentObj['X509Cert'].get_issuer ()
+        print IssuerName.print_ex ()
+
+    def ShowX509CertSubject ( self, File=None ):
+        Subject = self.CurrentObj['X509Cert'].get_subject ()
+        SubjectTxt = Subject.print_ex ()
+        SubjectTxtList = re.split('[\n\r]', SubjectTxt )
+        SubjectMap = {}
+        for Entry in SubjectTxtList:
+            ( Key, Value ) = re.split('=', Entry)
+            if self.ObjNames.has_key( Key ):
+                SubjectMap[ self.ObjNames[Key] ] = Value
+            else:
+                SubjectMap[ Key ] = Value
+        if File:
+            open( File, 'a').write("'Subject'    : %s,\n" % ( repr( SubjectMap ) ))
+        else:
+            sys.stdout.write("Subject: %s\n" % ( repr( SubjectMap ) ))
+
+    def RsaPKey2PemRepr ( self, File=None, Cipher=None ):
+        """
+        converting pkey to PEM representation
+        Cipher     if set to None, the pkey will be unprotected!!!!!   possible other value: 'des_ede3_cbc'
+        """
+        PemRsaPKey = self.CurrentObj['RsaPKey'].repr_key_pem ( cipher=Cipher )
+        PemRsaPKeyList = re.split('[\n\r]', PemRsaPKey)
+        if File:
+            open( File, 'a').write("\t\t\t\t\t\t\t\t'RsaPKey'   : %s,\n" % ( repr(PemRsaPKeyList[1:-2]) ))
+        else:
+            sys.stdout.write("\t\t\t\t\t\t\t\t'RsaPKey'   : %s,\n" % ( repr(PemRsaPKeyList[1:-2]) ))
+
+    def X509Cert2PemRepr ( self, File=None ):
+        PemCert = self.CurrentObj['X509Cert'].repr_cert_pem ()
+        #print PemCert
+        PemCertList = re.split('[\n\r]', PemCert)
+        if File:
+            open( File, 'a').write("\t\t\t\t\t\t\t\t'X509Cert'  : %s\n" % ( repr(PemCertList[1:-2]) ))
+        else:
+            sys.stdout.write("\t\t\t\t\t\t\t\t'X509Cert'  : %s\n" % ( repr(PemCertList[1:-2]) ))
+
+
+
+    #--------------------------------
+    # CertHandler  encryption / decryption
+    #--------------------------------
+    def CreateNonce ( self ):
+        """
+        creating some randomised data
+        return new Nonce string
+        """
+        random.seed ()
+        RawNonce = "%s_%f_%f" % ( os.getpid(), time.time(), random.random() )
+        sha1=M2Crypto.EVP.MessageDigest('sha1')
+        sha1.update( RawNonce )
+        NonceDecrypted = sha1.digest()
+        return NonceDecrypted
+
+    def NonceEncryptPrivate ( self, NonceDecrypted, RsaPKey=None ):
+        """
+        creating private encrypted string from NonceDecrypted
+        """
+        padding = M2Crypto.RSA.pkcs1_padding
+        if not RsaPKey:
+            UsedRsaPKey = self.ServerObj['RsaPKey']
+        else:
+            UsedRsaPKey = RsaPKey
+        NoncePrivEncrypted = UsedRsaPKey.private_encrypt ( NonceDecrypted, padding )
+        return NoncePrivEncrypted
+
+    def NonceEncryptPublic ( self, NonceDecrypted, RsaPubKey=None ):
+        """
+        creating public encrypted string from NonceDecrypted
+        """
+        padding = M2Crypto.RSA.pkcs1_padding
+        if not RsaPubKey:
+            UsedRsaPubKey = self.ServerObj['RsaPubKey']
+        else:
+            UsedRsaPubKey = RsaPubKey
+        NoncePubEncrypted = UsedRsaPubKey.public_encrypt ( NonceDecrypted, padding )
+        return NoncePubEncrypted
+
+    def NonceDecryptPublic ( self, NoncePrivEncrypted, RsaPubKey=None ):
+        """
+        creating decrypted string from NoncePrivEncrypted
+        """
+        padding = M2Crypto.RSA.pkcs1_padding
+        if not RsaPubKey:
+            UsedRsaPubKey = self.ServerObj['RsaPubKey']
+        else:
+            UsedRsaPubKey = RsaPubKey
+        try:
+            NonceDecrypted = UsedRsaPubKey.public_decrypt ( NoncePrivEncrypted, padding )
+        except:
+            raise AuthError('decrypting of public key failed - abort!')
+        return NonceDecrypted
+
+    def NonceDecryptPrivate ( self, NoncePubEncrypted, RsaPKey=None ):
+        padding = M2Crypto.RSA.pkcs1_padding
+        if not RsaPKey:
+            UsedRsaPKey = self.ServerObj['RsaPKey']
+        else:
+            UsedRsaPKey = RsaPKey
+        NonceDecrypted = UsedRsaPKey.private_decrypt ( NoncePubEncrypted, padding )
+        return NonceDecrypted
+
+    def NonceVerify ( self, DecryptedNonce=None ):
+        if  self.CurrentObj['Nonce']['Decrypted'] == DecryptNonce:
+            return True
+        return False
+
+    #--------------------------------
+    # CertHandler  authentication request
+    #--------------------------------
+    def ClientInit ( self, ObjName=None ):
+        """
+        generating AuthString 
+            Nonce     messagedigest 'sha1', encrypted with own instance private key
+            Cert      own instance X509 cert, PEM encoded
+            any linefeed charaters stripped out of the base64 code
+        return generated Nonce and AuthString
+        """
+        if ObjName:
+            if self.PemMap.has_key( ObjName ):
+                UsedObjName = ObjName
+            else:
+                UsedObjName = self.ServerName
+        else:
+            UsedObjName = self.ServerName
+
+        NonceDecrypted     = self.CreateNonce ()
+        NoncePrivEncrypted = re.sub('\012', '', base64.encodestring( self.NonceEncryptPrivate ( NonceDecrypted, RsaPKey=self.ServerObj['RsaPKey'] )) )
+        PemCert            = re.sub('\012', '', base64.encodestring( self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[UsedObjName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n' ))
+        InitString         = re.sub('\012', '', base64.encodestring('%s:%s' % ( NoncePrivEncrypted, PemCert )))
+        return ( NonceDecrypted, InitString )
+
+
+    def ClientInitVerify ( self, InitString ):
+        """
+        return decrypted Nonce from AuthString and ObjName from AuthString X509 Cert
+        """
+        try:
+            PemBaseString = base64.decodestring( InitString )
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+        try:
+            ( Base64Nonce, Base64Cert ) = re.split(':', PemBaseString )
+        except:
+            raise AuthError( 'cannot split PemBaseString into parts - abort!' )
+        try:
+            NoncePrivEncrypted = base64.decodestring( Base64Nonce )
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+        try:
+            PemCert = base64.decodestring( Base64Cert )
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+        try:
+            X509Cert = M2Crypto.X509.load_cert_string( PemCert )
+        except:
+            raise AuthError( 'cannot extract X509 cert from PEM representation - abort!' )
+        EvpPKey = self.ObjMap['CA']['EvpPKey']
+        if dir(EvpPKey).count('_ptr'):
+            Result = X509Cert.verify ( EvpPKey._ptr() )
+        else:
+            Result = X509Cert.verify ( EvpPKey )
+        if Result != 1:
+            raise AuthError( 'verification of X509 cert with Certification Authority "CA" failed with code %d - abort!' % ( Result ))
+        ClientObjName = self.ObjNameFromPemCert( PemCert=PemCert )
+        try:
+            NonceDecrypted = self.NonceDecryptPublic( NoncePrivEncrypted, RsaPubKey=self.CurrentObj['RsaPubKey'] )
+        except:
+            raise AuthError( 'wrong public key for encoding nonce - abort!' )
+
+        return ( NonceDecrypted, ClientObjName )
+
+
+
+
+    def ServerInit ( self, ClientObjName, ClientNonce ):
+        """
+        NonceServer       new Nonce from server encrypted with client publickey and base64 encoded
+        NonceBounce       the authrequest nonce encrypted with server privatekey and base64 encoded
+        PemServerCert     server X509 certification PEM encoded and base64 encoded
+        """
+        if not self.ObjMap.has_key( ClientObjName ):
+            if not self.PemMap.has_key( ClientObjName ):
+                raise AuthError( 'cannot find ClientObjName - abort!' )
+            else:
+                self.ObjFromContainer( ObjName=ClientObjName )
+        else:
+            self.CurrentObj = self.ObjMap[ClientObjName]
+
+        NonceDecrypted  = self.CreateNonce ()
+        NonceServer     = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( NonceDecrypted, RsaPubKey=self.CurrentObj['RsaPubKey'] )) )
+        NonceBounce     = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( ClientNonce, RsaPubKey=self.CurrentObj['RsaPubKey'] )) )
+        PemServerCert   = re.sub('\012', '', base64.encodestring( self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[self.ServerName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n' ) )
+
+        InitString      = re.sub('\012', '', base64.encodestring('%s:%s:%s' % ( NonceServer, NonceBounce, PemServerCert )) )
+        return ( NonceDecrypted, InitString )
+
+
+    def ServerInitVerify ( self, InitString, ObjName=None ):
+        NonceDecrypted = ''
+        ObjName        = ''
+        try:
+            PemBaseString = base64.decodestring( InitString )
+        except:
+            return False
+
+        ( NonceServer, NonceBounce, ServerCert ) = re.split(':', PemBaseString )        
+        NoncePubServer     = base64.decodestring( NonceServer )             # NonceServer
+        NoncePubBounce     = base64.decodestring( NonceBounce )             # NonceBounce
+        PemServerCert      = base64.decodestring( ServerCert  )             # PemServerCert
+
+        try:
+            X509Cert = M2Crypto.X509.load_cert_string( PemServerCert )
+        except:
+            return False
+
+        # verify X509 cert 
+        EvpPKey = self.ObjMap['CA']['EvpPKey']
+        if dir(EvpPKey).count('_ptr'):
+            Result = X509Cert.verify ( EvpPKey._ptr() )
+        else:
+            Result = X509Cert.verify ( EvpPKey )
+        if not Result:
+            return False
+
+        # verify Nonce from Server encrypted with my own publickey
+        try:
+            NonceDecrypted = self.NonceDecryptPrivate( NoncePubServer, RsaPKey=self.ServerObj['RsaPKey']  )
+        except:
+            return False
+
+        ServerObjName = self.ObjNameFromPemCert( PemCert=PemServerCert )
+
+        # verify Nonce bounced from Server encrypted with server privatekey
+        try:
+            NonceBounceDecrypted = self.NonceDecryptPrivate( NoncePubBounce, RsaPKey=self.CurrentObj['RsaPKey'] )
+        except:
+            return False
+
+        return ( NonceDecrypted, NonceBounceDecrypted, ServerObjName )
+
+
+
+    def ReplyInit ( self, ReplyObjName, ReplyBounce ): 
+        NonceDecrypted     = self.CreateNonce ()
+        NoncePubInit       = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( NonceDecrypted, RsaPubKey=self.ObjMap[ ReplyObjName ]['RsaPubKey'] )) )
+        NoncePubBounce     = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( ReplyBounce,    RsaPubKey=self.ObjMap[ ReplyObjName ]['RsaPubKey'] )) )
+        ReplyString        = re.sub('\012', '', base64.encodestring('%s:%s' % ( NoncePubInit, NoncePubBounce )) )
+        return ( NonceDecrypted, ReplyString )
+
+
+    def ReplyVerify ( self, ReplyString ):
+        try:
+            PemBaseString = base64.decodestring( ReplyString )
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+        ( NoncePubInit, NoncePubBounce ) = re.split(':', PemBaseString )
+
+        try:
+            NoncePubInit = base64.decodestring( NoncePubInit )      # new Nonce from Remote, encrypted with own publickey
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+        try:
+            NoncePubBounce = base64.decodestring( NoncePubBounce )      # bounced Nonce from Remote, encrypted with Remote privatekey
+        except base64.binascii.Error, msg:
+            raise base64.binascii.Error( msg )
+
+        # verify Nonce from Remote encrypted with my own publickey
+        try:
+            NonceRemote = self.NonceDecryptPrivate( NoncePubInit, RsaPKey=self.ServerObj['RsaPKey'] )
+        except:
+            raise AuthError( 'cannot encode nonce with own private key - abort!' )
+
+        # verify Nonce bounced from Remote encrypted with Remote privatekey
+        try:
+            NonceBounced = self.NonceDecryptPrivate( NoncePubBounce, RsaPKey=self.ServerObj['RsaPKey'] )
+        except:
+            raise AuthError( 'wrong public key for encoding nonce - abort!' )
+
+        return ( NonceRemote, NonceBounced )
+
+
+
+
+    #-------------------------------------------------------------------------------------------
+    # TEST 
+    #-------------------------------------------------------------------------------------------
+    def CreateCAForContainer ( self ):
+        self.CreateCert ()
+
+    def CreateForContainer ( self, ObjName ):
+        """
+        create new pkey pair  and  x509 cert for specified objname
+        result will be written in file "PyReprPem.txt"
+        """
+        self.ObjFromContainer ( ObjName='AuthInstance' )
+        self.CreateObjCert ( ObjName=ObjName )
+
+
+    def Test ( self ):
+        #self.CreateCert ()
+        #self.ExtractPublicKeyFromCert ()
+        #self.VerifyCert ()
+
+        ( ClientInitNonce, ClientInitString ) = self.ClientInit ()
+        ( ClientSendNonce, ClientObjName )    = self.ClientInitVerify ( InitString=ClientInitString )
+        ( ServerInitNonce, ServerInitString ) = self.ServerInit ( ClientObjName=ClientObjName, ClientNonce=ClientSendNonce )
+        ( ServerSendNonce, ClientBounceNonce, ServerObjName )    = self.ServerInitVerify ( InitString=ServerInitString )
+        if ClientInitNonce == ClientBounceNonce :
+            print '100 Test  Nonce bounced True'
+        else:
+            print '100 Test  Nonce bounced False - abort!'
+
+        ( ReplyInitNonce, ReplyInitString )   = self.ReplyInit ( ReplyObjName=ClientObjName, ReplyBounce=ServerSendNonce )
+        ( ReplySendNonce, NonceBounced )      = self.ReplyVerify ( ReplyString=ReplyInitString )
+        if ServerInitNonce == NonceBounced :
+            print '100 Test  Nonce bounced True'
+        else:
+            print '100 Test  Nonce bounced False - abort!'
+
+        ( Reply2InitNonce, Reply2InitString )   = self.ReplyInit ( ReplyObjName=ClientObjName, ReplyBounce=ServerSendNonce )
+        ( Reply2SendNonce, Nonce2Bounced )      = self.ReplyVerify ( ReplyString=Reply2InitString )
+        if ServerInitNonce == Nonce2Bounced :
+            print '100 Test  Nonce bounced True'
+        else:
+            print '100 Test  Nonce bounced False - abort!'
+
+        #self.NonceEncryptPrivate ()
+        #self.NonceDecryptPublic ()
+        #self.NonceVerify ()
+
+
+#-----------------------------------------------------------------------------------------------
+# MAIN
+#
+# x509auth.py --ca              
+# will create a file "PyReprPem.txt" in the current directory
+# append the contents of the file to the CertContainer in this script
+#
+# x509auth.py --cert <ObjName>  
+# creates a file "PyReprPem.txt" in the current directory
+# append the contents of the file to the CertContainer in this script
+#
+# x509auth.py --test            
+# running authentification tests with bounced nonce
+#
+#-----------------------------------------------------------------------------------------------
+if __name__ == '__main__':
+    run = CertHandler ()
+
+    if len( sys.argv ) > 1:
+        if sys.argv[1] == '--test':
+            run.Test ()
+        elif sys.argv[1] == '--ca':
+            run.CreateCert()
+        elif sys.argv[1] == '--cert':
+            run.CreateForContainer( sys.argv[2] )
+
+    sys.exit( 0 )
+
diff --git a/doc/ZServerSSL-HOWTO.html b/doc/ZServerSSL-HOWTO.html
new file mode 100644 (file)
index 0000000..78c15a9
--- /dev/null
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<base href="http://localhost:9080/home/m2/zserverssl-011-howto/" />
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.2.8: http://docutils.sourceforge.net/" />
+<title>ZServerSSL HOWTO</title>
+<meta name="author" content="Ng Pheng Siong" />
+<meta name="date" content="2003-06-22" />
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="zserverssl-howto">
+<h1 class="title">ZServerSSL HOWTO</h1>
+<table class="docinfo" frame="void" rules="none">
+<col class="docinfo-name" />
+<col class="docinfo-content" />
+<tbody valign="top">
+<tr><th class="docinfo-name">Author:</th>
+<td>Ng Pheng Siong</td></tr>
+<tr class="field"><th class="docinfo-name">Id:</th><td class="field-body">ZServerSSL-HOWTO,v 1.1 2003/06/22 17:40:13 ngps Exp</td>
+</tr>
+<tr><th class="docinfo-name">Date:</th>
+<td>2003-06-22</td></tr>
+<tr class="field"><th class="docinfo-name">Web-Site:</th><td class="field-body"><a class="reference" href="http://chandlerproject.org/Projects/MeTooCrypto">http://chandlerproject.org/Projects/MeTooCrypto</a></td>
+</tr>
+</tbody>
+</table>
+<div class="contents topic" id="contents">
+<p class="topic-title"><a name="contents">Contents</a></p>
+<ul class="simple">
+<li><a class="reference" href="#introduction" id="id2" name="id2">Introduction</a></li>
+<li><a class="reference" href="#preparation" id="id3" name="id3">Preparation</a></li>
+<li><a class="reference" href="#installation" id="id4" name="id4">Installation</a></li>
+<li><a class="reference" href="#testing" id="id5" name="id5">Testing</a><ul>
+<li><a class="reference" href="#https" id="id6" name="id6">HTTPS</a></li>
+<li><a class="reference" href="#webdav-over-https" id="id7" name="id7">WebDAV-over-HTTPS</a></li>
+<li><a class="reference" href="#webdav-source-over-https" id="id8" name="id8">WebDAV-Source-over-HTTPS</a></li>
+<li><a class="reference" href="#python-with-m2crypto" id="id9" name="id9">Python with M2Crypto</a><ul>
+<li><a class="reference" href="#id1" id="id10" name="id10">HTTPS</a></li>
+<li><a class="reference" href="#xmlrpc-over-https" id="id11" name="id11">XMLRPC-over-HTTPS</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a class="reference" href="#conclusion" id="id12" name="id12">Conclusion</a></li>
+</ul>
+</div>
+<div class="section" id="introduction">
+<h1><a class="toc-backref" href="#id2" name="introduction">Introduction</a></h1>
+<p>ZServerSSL adds to Zope's ZServer the following:</p>
+<ul class="simple">
+<li>HTTPS server</li>
+<li>WebDAV-source-over-HTTPS server</li>
+</ul>
+<p>With the HTTPS server, ZServerSSL also provides WebDAV-over-HTTPS
+and XMLRPC-over-HTTPS access to Zope.</p>
+<p>These instructions apply to both Un*x and Windows installations of
+Zope 2.6.1. To avoid cluttering the presentation, Windows pathnames
+are shown in Un*x fashion.</p>
+</div>
+<div class="section" id="preparation">
+<h1><a class="toc-backref" href="#id3" name="preparation">Preparation</a></h1>
+<ol class="arabic simple">
+<li>Download M2Crypto 0.11, contained in the file <tt class="literal"><span class="pre">m2crypto-0.11.zip</span></tt>.</li>
+<li>Unpack <tt class="literal"><span class="pre">m2crypto-0.11.zip</span></tt>. This will create a directory
+<tt class="literal"><span class="pre">m2crypto-0.11</span></tt>. Henceforth, we refer to this directory as <tt class="literal"><span class="pre">$M2</span></tt>.</li>
+<li>Install M2Crypto per the instructions in <tt class="literal"><span class="pre">$M2/INSTALL</span></tt>.</li>
+</ol>
+<p>The ZServerSSL distribution is in <tt class="literal"><span class="pre">$M2/demo/Zope</span></tt>. We shall refer to
+this directory as <tt class="literal"><span class="pre">$ZSSL</span></tt>.</p>
+</div>
+<div class="section" id="installation">
+<h1><a class="toc-backref" href="#id4" name="installation">Installation</a></h1>
+<p>Below, we refer to your Zope top-level directory as <tt class="literal"><span class="pre">$ZOPE</span></tt>.</p>
+<ol class="arabic">
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/z2s.py</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>.</p>
+</li>
+<li><p class="first">Depending on your operating system, modify <tt class="literal"><span class="pre">$ZOPE/start</span></tt> or
+<tt class="literal"><span class="pre">$ZOPE/start.bat</span></tt> to invoke <tt class="literal"><span class="pre">$ZOPE/z2s.py</span></tt>, instead of
+<tt class="literal"><span class="pre">$ZOPE/z2.py</span></tt>. The files <tt class="literal"><span class="pre">$ZSSL/starts</span></tt> and
+<tt class="literal"><span class="pre">$ZSSL/starts.bat</span></tt> serve as examples.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/dh1024.pem</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains
+Diffie-Hellman parameters for use by the SSL protocol.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/randpool.dat</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains seed
+material for the OpenSSL PRNG. Alternatively, create
+<tt class="literal"><span class="pre">$ZOPE/randpool.dat</span></tt> thusly:</p>
+<pre class="literal-block">
+$ dd if=/dev/urandom of=randpool.dat bs=1024 count=1
+</pre>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ca.pem</span></tt> to <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains an example
+Certification Authority (CA) certificate. For information on
+operating your own CA, see
+<a class="reference" href="http://svn.osafoundation.org/m2crypto/trunk/doc/howto.ca.html">howto.ca.html</a> or one of numerous
+similar documents available on the web.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/server.pem</span></tt> to <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains an RSA
+key pair and its X.509v3 certificate issued by the above CA. You
+may also create your own key/certificate bundle.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/HTTPS_Server.py</span></tt> to <tt class="literal"><span class="pre">$ZOPE/ZServer</span></tt>.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/__init__.py</span></tt> to <tt class="literal"><span class="pre">$ZOPE/ZServer</span></tt>. This
+overwrites the existing <tt class="literal"><span class="pre">$ZOPE/ZServer/__init__.py</span></tt>. Alternatively,
+apply the following patch to <tt class="literal"><span class="pre">$ZOPE/ZServer/__init__.py</span></tt>:</p>
+<pre class="literal-block">
+--- __init__.py.org     Sat Jun 21 23:20:41 2003
++++ __init__.py Tue Jan  7 23:30:53 2003
+&#64;&#64; -84,6 +84,7 &#64;&#64;
+ import asyncore
+ from medusa import resolver, logger
+ from HTTPServer import zhttp_server, zhttp_handler
++from HTTPS_Server import zhttps_server, zhttps_handler
+ from PCGIServer import PCGIServer
+ from FCGIServer import FCGIServer
+ from FTPServer import FTPServer
+</pre>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/medusa/https_server.py</span></tt> to
+<tt class="literal"><span class="pre">$ZOPE/ZServer/medusa</span></tt>.</p>
+</li>
+<li><p class="first">Stop Zope, if it is running.</p>
+</li>
+<li><p class="first">Start Zope with ZServerSSL thusly:</p>
+<pre class="literal-block">
+./starts -X -f 9021 -w 9080 -W 9081 -y 9443 -Y 9444
+</pre>
+<p>This starts the following:</p>
+<ul class="simple">
+<li>an FTP server on port 9021</li>
+<li>a HTTP server on port 9080</li>
+<li>a WebDAV-source server on port 9081</li>
+<li>a HTTPS server on port 9443</li>
+<li>a WebDAV-source-over-HTTPS server on port 9444</li>
+</ul>
+</li>
+</ol>
+</div>
+<div class="section" id="testing">
+<h1><a class="toc-backref" href="#id5" name="testing">Testing</a></h1>
+<p>Below, we assume your Zope server is running on <tt class="literal"><span class="pre">localhost</span></tt>.</p>
+<div class="section" id="https">
+<h2><a class="toc-backref" href="#id6" name="https">HTTPS</a></h2>
+<p>This testing is done with Mozilla 1.1 on FreeBSD.</p>
+<ol class="arabic simple">
+<li>With a browser, connect to <a class="reference" href="https://localhost:9443/">https://localhost:9443/</a>. Browse
+around. Check out your browser's HTTPS informational screens.</li>
+<li>Connect to <a class="reference" href="https://localhost:9443/manage">https://localhost:9443/manage</a>. Verify that you can
+access Zope's management functionality.</li>
+</ol>
+</div>
+<div class="section" id="webdav-over-https">
+<h2><a class="toc-backref" href="#id7" name="webdav-over-https">WebDAV-over-HTTPS</a></h2>
+<p>This testing is done with Cadaver 0.21.0 on FreeBSD.</p>
+<pre class="literal-block">
+$ cadaver https://localhost:9443/
+WARNING: Untrusted server certificate presented:
+Issued to: M2Crypto, SG
+Issued by: M2Crypto, SG
+Do you wish to accept the certificate? (y/n) y
+dav:/&gt; ls
+Listing collection `/': succeeded.
+Coll:   Channels                               0  Jun 19 00:04
+Coll:   Control_Panel                          0  Jun  6 00:13
+Coll:   Examples                               0  Jun  6 00:12
+Coll:   catalog                                0  Jun 12 11:53
+Coll:   ngps                                   0  Jun 16 15:34
+Coll:   portal                                 0  Jun 21 15:21
+Coll:   skunk                                  0  Jun 18 21:18
+Coll:   temp_folder                            0  Jun 22 17:57
+Coll:   zope                                   0  Jun 20 15:27
+        acl_users                              0  Dec 30  1998
+        browser_id_manager                     0  Jun  6 00:12
+        default.css                         3037  Jun 21 16:38
+        error_log                              0  Jun  6 00:12
+        index_html                           313  Jun 12 13:36
+        portal0                                0  Jun 21 15:21
+        session_data_manager                   0  Jun  6 00:12
+        standard_error_message              1365  Jan 21  2001
+        standard_html_footer                  50  Jun 12 12:30
+        standard_html_header                  80  Jan 21  2001
+        standard_template.pt                 282  Jun  6 00:12
+        zsyncer                                0  Jun 17 15:28
+dav:/&gt; quit
+Connection to `localhost' closed.
+$ 
+</pre>
+</div>
+<div class="section" id="webdav-source-over-https">
+<h2><a class="toc-backref" href="#id8" name="webdav-source-over-https">WebDAV-Source-over-HTTPS</a></h2>
+<p>This testing is done with Mozilla 1.1 on FreeBSD.</p>
+<ol class="arabic simple">
+<li>Open the Mozilla Composer window.</li>
+<li>Click &quot;File&quot;, &quot;Open Web Location&quot;. A dialog box appears.</li>
+<li>Enter <tt class="literal"><span class="pre">https://localhost:9444/index_html</span></tt> for the URL.</li>
+<li>Select &quot;Open in new Composer window.&quot;</li>
+<li>Click &quot;Open&quot;. A new Composer window will open with <tt class="literal"><span class="pre">index_html</span></tt>
+loaded.</li>
+</ol>
+</div>
+<div class="section" id="python-with-m2crypto">
+<h2><a class="toc-backref" href="#id9" name="python-with-m2crypto">Python with M2Crypto</a></h2>
+<p>This testing is done with M2Crypto 0.11 and Python 2.2.2 on FreeBSD.</p>
+<div class="section" id="id1">
+<h3><a class="toc-backref" href="#id10" name="id1">HTTPS</a></h3>
+<pre class="doctest-block">
+&gt;&gt;&gt; from M2Crypto import Rand, SSL, m2urllib
+&gt;&gt;&gt; url = m2urllib.FancyURLopener()
+&gt;&gt;&gt; url.addheader('Connection', 'close')
+&gt;&gt;&gt; u = url.open('https://127.0.0.1:9443/')
+send: 'GET / HTTP/1.1\r\nHost: 127.0.0.1:9443\r\nAccept-Encoding: identity\r\nUser-agent: Python-urllib/1.15\r\nConnection: close\r\n\r\n'
+reply: 'HTTP/1.1 200 OK\r\n'
+header: Server: ZServerSSL/0.11
+header: Date: Sun, 22 Jun 2003 13:42:34 GMT
+header: Connection: close
+header: Content-Type: text/html
+header: Etag: 
+header: Content-Length: 535
+&gt;&gt;&gt; while 1:
+...     data = u.read()
+...     if not data: break
+...     print data
+... 
+</pre>
+<pre class="literal-block">
+&lt;html&gt;&lt;head&gt;
+&lt;base href=&quot;https://127.0.0.1:9443/&quot; /&gt;
+&lt;title&gt;Zope&lt;/title&gt;&lt;/head&gt;&lt;body bgcolor=&quot;#FFFFFF&quot;&gt;
+
+&lt;h1&gt;NgPS Desktop Portal&lt;/h1&gt;
+
+&amp;nbsp;&amp;nbsp;So many hacks.&lt;br&gt;
+&amp;nbsp;&amp;nbsp;So little time.&lt;br&gt;
+
+&lt;h2&gt;Link Farm&lt;/h2&gt;
+&lt;ul&gt;
+&lt;li&gt;&lt;a href=&quot;http://localhost:8080/portal&quot;&gt;Portal&lt;/a&gt;&lt;/li&gt;
+&lt;li&gt;&lt;a href=&quot;http://localhost/&quot;&gt;Local Apache Home Page&lt;/a&gt;&lt;/li&gt;
+&lt;/ul&gt;
+
+&lt;hr&gt;&lt;a href=&quot;http://www.zope.org/Credits&quot; target=&quot;_top&quot;&gt;&lt;img src=&quot;https://127.0.0.1:9443/p_/ZopeButton&quot; width=&quot;115&quot; height=&quot;50&quot; border=&quot;0&quot; alt=&quot;Powered by Zope&quot; /&gt;&lt;/a&gt;&lt;/body&gt;&lt;/html&gt;
+</pre>
+<pre class="doctest-block">
+&gt;&gt;&gt; u.close()
+&gt;&gt;&gt; 
+</pre>
+</div>
+<div class="section" id="xmlrpc-over-https">
+<h3><a class="toc-backref" href="#id11" name="xmlrpc-over-https">XMLRPC-over-HTTPS</a></h3>
+<pre class="doctest-block">
+&gt;&gt;&gt; from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+&gt;&gt;&gt; zs = Server('https://127.0.0.1:9443/', SSL_Transport())
+&gt;&gt;&gt; print zs.propertyMap()
+[{'type': 'string', 'id': 'title', 'mode': 'w'}]
+&gt;&gt;&gt; 
+</pre>
+</div>
+</div>
+</div>
+<div class="section" id="conclusion">
+<h1><a class="toc-backref" href="#id12" name="conclusion">Conclusion</a></h1>
+<p>Well, it works! ;-)</p>
+</div>
+</div>
+</body>
+</html>
diff --git a/doc/howto.ca.html b/doc/howto.ca.html
new file mode 100644 (file)
index 0000000..74142d5
--- /dev/null
@@ -0,0 +1,891 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Creating your own CA with OpenSSL</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.64
+"></HEAD
+><BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="ARTICLE"
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Creating your own CA with OpenSSL</A
+></H1
+><H3
+CLASS="AUTHOR"
+><A
+NAME="AEN4"
+>Pheng Siong Ng</A
+></H3
+><DIV
+CLASS="AFFILIATION"
+><DIV
+CLASS="ADDRESS"
+><P
+CLASS="ADDRESS"
+>ngps@post1.com</P
+></DIV
+></DIV
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2000, 2001 by Ng Pheng Siong.</P
+><DIV
+CLASS="REVHISTORY"
+><TABLE
+WIDTH="100%"
+BORDER="0"
+><TR
+><TH
+ALIGN="LEFT"
+VALIGN="TOP"
+COLSPAN="3"
+><B
+>Revision History</B
+></TH
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+>Revision $Revision: 1.1 $</TD
+><TD
+ALIGN="LEFT"
+>$Date: 2003/06/22 16:41:18 $</TD
+><TD
+ALIGN="LEFT"
+></TD
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+COLSPAN="3"
+></TD
+></TR
+></TABLE
+></DIV
+><HR></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+>This is a HOWTO on creating your own <I
+CLASS="EMPHASIS"
+>certification
+    authority</I
+> (<I
+CLASS="EMPHASIS"
+>CA</I
+>) with OpenSSL. 
+    </P
+><P
+>I last created a CA about a year ago, when I began work on <A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+> and needed
+    certificates for the SSL bits. I accepted the tools' default settings
+    then, e.g., certificate validity of 365 days; this meant that my
+    certificates, including my CA's certificate, have now expired.
+    </P
+><P
+>Since I am using these certificates for M2Crypto's demonstration
+    programs (and I have forgotten the passphrase to the CA's private key),
+    I decided to discard the old CA and start afresh. I also decided to
+    document the process, hence this HOWTO.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="PROCEDURE"
+>The Procedure</A
+></H1
+><P
+>I use <TT
+CLASS="FILENAME"
+>CA.pl</TT
+>, a Perl program written by
+    Steve Hanson and bundled with OpenSSL.
+    </P
+><P
+>The following are the steps to create a CA:
+    </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Choose a directory to do your CA work. All commands are executed
+    within this directory. Let's call the directory <TT
+CLASS="FILENAME"
+>demo</TT
+>.
+    </P
+></LI
+><LI
+><P
+>Copy <TT
+CLASS="FILENAME"
+>CA.pl</TT
+> and <TT
+CLASS="FILENAME"
+>openssl.cnf</TT
+>
+    into <TT
+CLASS="FILENAME"
+>demo</TT
+>. 
+    </P
+></LI
+><LI
+><P
+>Apply the following patch to <TT
+CLASS="FILENAME"
+>CA.pl</TT
+>, which
+    allows it to generate a CA certificate with a validity period of 1095 days, 
+    i.e., 3 years:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    --- CA.pl.org   Sat Mar 31 12:40:13 2001
+    +++ CA.pl       Sat Mar 31 12:41:15 2001
+    @@ -97,7 +97,7 @@
+                    } else {
+                        print "Making CA certificate ...\n";
+                        system ("$REQ -new -x509 -keyout " .
+    -                       "${CATOP}/private/$CAKEY -out ${CATOP}/$CACERT $DAYS");
+    +                       "${CATOP}/private/$CAKEY -out ${CATOP}/$CACERT -days 1095");
+                        $RET=$?;
+                    }
+                }
+    </PRE
+></LI
+><LI
+><P
+>Create a new CA like this:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -newca
+    </B
+></TT
+>
+    A certificate filename (or enter to create) <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+    
+    Making CA certificate ...
+    Using configuration from openssl.cnf
+    Generating a 1024 bit RSA private key
+    ............++++++
+    ......................++++++
+    writing new private key to './demoCA/private/cakey.pem'
+    Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+    Verifying password - Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase again&gt;</I
+></TT
+></B
+></TT
+>
+    -----
+    You are about to be asked to enter information that will be incorporated
+    into your certificate request.
+    What you are about to enter is what is called a Distinguished Name or a DN.
+    There are quite a few fields but you can leave some blank
+    For some fields there will be a default value,
+    If you enter '.', the field will be left blank.
+    -----
+    Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+    State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>.
+    Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>DemoCA</I
+></TT
+></B
+></TT
+>
+    Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>DemoCA Certificate Master</I
+></TT
+></B
+></TT
+>
+    Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>certmaster@democa.dom</I
+></TT
+></B
+></TT
+>
+    </PRE
+><P
+>This creates a new CA in the directory <TT
+CLASS="FILENAME"
+>demoCA</TT
+>.
+    The CA's self-signed certificate is in
+    <TT
+CLASS="FILENAME"
+>demoCA/cacert.pem</TT
+> and its RSA key pair is in
+    <TT
+CLASS="FILENAME"
+>demoCA/private/cakey.pem</TT
+>.
+    </P
+><P
+><TT
+CLASS="FILENAME"
+>demoCA/private/cakey.pem</TT
+> looks like this:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>cat demoCA/private/cakey.pem
+    </B
+></TT
+>
+    -----BEGIN RSA PRIVATE KEY-----
+    Proc-Type: 4,ENCRYPTED
+    DEK-Info: DES-EDE3-CBC,19973A9DBBB601BA
+
+    eOq9WFScNiI4/UWEUaSnGTKpJv2JYuMD3HwQox2Q3Cd4zGqVjJ6gF3exa5126cKf
+    X/bMVnwbPpuFZPiAIvaLyCjT6pYeXTBbSzs7/GQnvEOv+nYnDUFWi0Qm92qLk0uy
+    pFi/M1aWheN3vir2ZlAw+DW0bOOZhj8tC7Co7lMYb0YE271b6/YRPZCwQ3GXAHUJ
+    +aMYxlUDrK45aCUa/1CZDzTgk7h9cDgx2QJSIvYMYytCfI3zsuZMJS8/4OXLL0bI
+    lKmAc1dwB3DqGJt5XK4WJesiNfdxeCNEgAcYtEAgYZTPIApU+kTgTCIxJl2nMW7j
+    ax+Q1z7g+4MpgG20WD633D4z4dTlDdz+dnLi0rvuvxiwt+dUhrqiML1tyi+Z6EBH
+    jU4/cLBWev3rYfrlp4x8J9mDte0YKOk3t0wQOHqRetTsIfdtjnFp/Hu3qDmTCWjD
+    z/g7PPoO/bg/B877J9WBPbL/1hXXFYo88M+2aGlPOgDcFdiOqbLb2DCscohMbbVr
+    A4mgiy2kwWfIE73qiyV7yyG8FlRvr1iib+jbT3LTGf743utYAAs7HNGuOUObhoyt
+    jYvBD7ACn35P5YX7KTqvqErwdijxYCaNBCnvmRtmYSaNw9Kv1UJTxc5Vx7YLwIPk
+    E9KyBgKI7vPOjWBZ27+zOvNycmv1ciNtpALAw4bWtXnhCDVTHaVDy34OkheMzNCg
+    2cjcBFzOkMIjcI03KbTQXOFIQGlsTWXGzkNf/zBQ+KksT1MCj+zBXSCvlDASMckg
+    kef21pGgUqPF14gKGfWX3sV4bjc1vbrRwq6zlG3nMuYqR5MtJJY9eQ==
+    -----END RSA PRIVATE KEY-----
+    </PRE
+></LI
+><LI
+><P
+>Next, generate a certificate request. 
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -newreq
+    </B
+></TT
+>
+    Using configuration from openssl.cnf
+    Generating a 1024 bit RSA private key
+    ..........++++++
+    ..............++++++
+    writing new private key to 'newreq.pem'
+    Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;another secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+    Verifying password - Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;another secret passphrase again&gt;</I
+></TT
+></B
+></TT
+>
+    -----
+    You are about to be asked to enter information that will be incorporated
+    into your certificate request.
+    What you are about to enter is what is called a Distinguished Name or a DN.
+    There are quite a few fields but you can leave some blank
+    For some fields there will be a default value,
+    If you enter '.', the field will be left blank.
+    -----
+    Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+    State or Province Name (full name) [Some-State]:.<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+    Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>localhost</I
+></TT
+></B
+></TT
+>
+    Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>admin@server.example.dom</I
+></TT
+></B
+></TT
+>
+    
+    Please enter the following 'extra' attributes
+    to be sent with your certificate request
+    A challenge password []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+    An optional company name []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+    Request (and private key) is in newreq.pem
+    </PRE
+><P
+>The certificate request and private key in <TT
+CLASS="FILENAME"
+>newreq.pem</TT
+> looks like this:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>cat newreq.pem
+    </B
+></TT
+>
+    -----BEGIN RSA PRIVATE KEY-----
+    Proc-Type: 4,ENCRYPTED
+    DEK-Info: DES-EDE3-CBC,41B2874DF3D02DD4
+    
+    mg611EoVkLEooSTv+qTM0Ddmm/M1jE/Jy5RD/sc3LSMhuGu9xc26OgsTJmkQuIAh
+    J/B4lAw8G59VTG6DykeEtrG0rUBx4bggc7PKbFuiN423YjJODWcHvVgnPOzXMQt+
+    lY4tPl5+217MRHyx2NsWGrpkQNdu3GeSPOVMl3jeQiaXupONbwQ7rj42+X/VtAJP
+    W4D1NNwu8aGCPyShsEXHc/fI1WDpphYWke97pOjIZVQESFZOPty5HjIYZux4U+td
+    W81xODtq2ecJXc8fn2Wpa9y5VD1LT7oJksOuL1+Z04OVaeUe4x0swM17HlBm2kVt
+    fe/C/L6kN27MwZhE331VjtTjSGl4/gknqQDbLOtqT06f3OISsDJETm2itllyhgzv
+    C6Fi3N03rGFmKectijC+tws5k+P+HRG6sai33usk8xPokJqA+HYSWPz1XVlpRmv4
+    kdjQOdST7ovU62mOTgf3ARcduPPwuzTfxOlYONe5NioO1APVHBrInQwcpLkpOTQR
+    vI4roIN+b75/nihUWGUJn/nbbBa2Yl0N5Gs1Tyiy9Z+CcRT2TfWKBBFlEUIFl7Mb
+    J9fTV3DI+k+akbR4il1NkQ8EcSmCr3WpA0I9n0EHI7ZVpVaHxc0sqaPFl8YGdFHq
+    1Qk53C/w6+qPpDzT3yKFmG2LZytAAM1czvb6RbNRJJP2ZrpBwn/h99sUTo/yPfxY
+    nueYmFJDm0uVNtG0icXGNUfSfnjKNTtHPAgyKGetRIC3kgJz/bo2w7EI6iEjBAzK
+    l5TRm4x6ZJxwuXXMiJCehMMd8TC8ybwWO4AO19B3ebFFeTVsUgxSGA==
+    -----END RSA PRIVATE KEY-----
+    -----BEGIN CERTIFICATE REQUEST-----
+    MIIBnTCCAQYCAQAwXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIw
+    EAYDVQQDEwlsb2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5l
+    eGFtcGxlLmRvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAr1nYY1Qrll1r
+    uB/FqlCRrr5nvupdIN+3wF7q915tvEQoc74bnu6b8IbbGRMhzdzmvQ4SzFfVEAuM
+    MuTHeybPq5th7YDrTNizKKxOBnqE2KYuX9X22A1Kh49soJJFg6kPb9MUgiZBiMlv
+    tb7K3CHfgw5WagWnLl8Lb+ccvKZZl+8CAwEAAaAAMA0GCSqGSIb3DQEBBAUAA4GB
+    AHpoRp5YS55CZpy+wdigQEwjL/wSluvo+WjtpvP0YoBMJu4VMKeZi405R7o8oEwi
+    PdlrrliKNknFmHKIaCKTLRcU59ScA6ADEIWUzqmUzP5Cs6jrSRo3NKfg1bd09D1K
+    9rsQkRc9Urv9mRBIsredGnYECNeRaK5R1yzpOowninXC
+    -----END CERTIFICATE REQUEST-----
+    </PRE
+><P
+>Decoding the certificate request gives the following:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl req -text -noout &#60; newreq.pem
+    </B
+></TT
+>
+    Using configuration from /usr/local/pkg/openssl/openssl.cnf
+    Certificate Request:
+        Data:
+            Version: 0 (0x0)
+            Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+            Subject Public Key Info:
+                Public Key Algorithm: rsaEncryption
+                RSA Public Key: (1024 bit)
+                    Modulus (1024 bit):
+                        00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+                        91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+                        6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+                        21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+                        c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+                        4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+                        6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+                        6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+                        0b:6f:e7:1c:bc:a6:59:97:ef
+                    Exponent: 65537 (0x10001)
+            Attributes:
+                a0:00
+        Signature Algorithm: md5WithRSAEncryption
+            7a:68:46:9e:58:4b:9e:42:66:9c:be:c1:d8:a0:40:4c:23:2f:
+            fc:12:96:eb:e8:f9:68:ed:a6:f3:f4:62:80:4c:26:ee:15:30:
+            a7:99:8b:8d:39:47:ba:3c:a0:4c:22:3d:d9:6b:ae:58:8a:36:
+            49:c5:98:72:88:68:22:93:2d:17:14:e7:d4:9c:03:a0:03:10:
+            85:94:ce:a9:94:cc:fe:42:b3:a8:eb:49:1a:37:34:a7:e0:d5:
+            b7:74:f4:3d:4a:f6:bb:10:91:17:3d:52:bb:fd:99:10:48:b2:
+            b7:9d:1a:76:04:08:d7:91:68:ae:51:d7:2c:e9:3a:8c:27:8a:
+            75:c2
+    </PRE
+></LI
+><LI
+><P
+>Now, sign the certificate request:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -sign
+    </B
+></TT
+>
+    Using configuration from openssl.cnf
+    Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;CA's passphrase&gt;</I
+></TT
+></B
+></TT
+>
+    Check that the request matches the signature
+    Signature ok
+    The Subjects Distinguished Name is as follows
+    countryName           :PRINTABLE:'SG'
+    organizationName      :PRINTABLE:'M2Crypto'
+    commonName            :PRINTABLE:'localhost'
+    emailAddress          :IA5STRING:'admin@server.example.dom'
+    Certificate is to be certified until Mar 31 02:57:30 2002 GMT (365 days)
+    Sign the certificate? [y/n]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>y</I
+></TT
+></B
+></TT
+>
+    
+    
+    1 out of 1 certificate requests certified, commit?  [y/n]<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>y</I
+></TT
+></B
+></TT
+>
+    Write out database with 1 new entries
+    Data Base Updated
+    Signed certificate is in newcert.pem
+    </PRE
+><P
+><TT
+CLASS="FILENAME"
+>newcert.pem</TT
+> looks like this:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>cat newcert.pem
+    </B
+></TT
+>
+    Certificate:
+        Data:
+            Version: 3 (0x2)
+            Serial Number: 1 (0x1)
+            Signature Algorithm: md5WithRSAEncryption
+            Issuer: C=SG, O=DemoCA, CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+            Validity
+                Not Before: Mar 31 02:57:30 2001 GMT
+                Not After : Mar 31 02:57:30 2002 GMT
+            Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+            Subject Public Key Info:
+                Public Key Algorithm: rsaEncryption
+                RSA Public Key: (1024 bit)
+                    Modulus (1024 bit):
+                        00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+                        91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+                        6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+                        21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+                        c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+                        4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+                        6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+                        6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+                        0b:6f:e7:1c:bc:a6:59:97:ef
+                    Exponent: 65537 (0x10001)
+            X509v3 extensions:
+                X509v3 Basic Constraints: 
+    Certificate:
+        Data:
+            Version: 3 (0x2)
+            Serial Number: 1 (0x1)
+            Signature Algorithm: md5WithRSAEncryption
+            Issuer: C=SG, O=DemoCA, CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+            Validity
+                Not Before: Mar 31 02:57:30 2001 GMT
+                Not After : Mar 31 02:57:30 2002 GMT
+            Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+            Subject Public Key Info:
+                Public Key Algorithm: rsaEncryption
+                RSA Public Key: (1024 bit)
+                    Modulus (1024 bit):
+                        00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+                        91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+                        6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+                        21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+                        c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+                        4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+                        6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+                        6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+                        0b:6f:e7:1c:bc:a6:59:97:ef
+                    Exponent: 65537 (0x10001)
+            X509v3 extensions:
+                X509v3 Basic Constraints: 
+                    CA:FALSE
+                Netscape Comment: 
+                    OpenSSL Generated Certificate
+                X509v3 Subject Key Identifier: 
+                    B3:D6:89:88:2F:B1:15:40:EC:0A:C0:30:35:3A:B7:DA:72:73:1B:4D
+                X509v3 Authority Key Identifier: 
+                    keyid:F9:6A:A6:34:97:6B:BC:BB:5A:17:0D:19:FC:62:21:0B:00:B5:0E:29
+                    DirName:/C=SG/O=DemoCA/CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+                    serial:00
+    
+        Signature Algorithm: md5WithRSAEncryption
+    </PRE
+></LI
+><LI
+><P
+>In certain situations, e.g., where your certificate and
+    private key are to be used in an unattended SSL server, you may wish to
+    not encrypt the private key, i.e., leave the key in the clear. This
+    decision should be governed by your site's security policy and threat
+    model, of course.  
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl rsa &#60; newkey.pem &#62; newkey2.pem
+    </B
+></TT
+>
+    read RSA key
+    Enter PEM pass phrase:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+    writing RSA key
+    </PRE
+><P
+><TT
+CLASS="FILENAME"
+>newkey2.pem</TT
+> looks like this:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>cat newkey2.pem
+    </B
+></TT
+>
+    -----BEGIN RSA PRIVATE KEY-----
+    MIICXgIBAAKBgQCvWdhjVCuWXWu4H8WqUJGuvme+6l0g37fAXur3Xm28RChzvhue
+    7pvwhtsZEyHN3Oa9DhLMV9UQC4wy5Md7Js+rm2HtgOtM2LMorE4GeoTYpi5f1fbY
+    DUqHj2ygkkWDqQ9v0xSCJkGIyW+1vsrcId+DDlZqBacuXwtv5xy8plmX7wIDAQAB
+    AoGAbAkU8w3W1Qu15Hle1bJSL7GMReoreqeblOBmMAZz4by0l6sXZXJpjWXo86f/
+    +dASMYTMPC4ZTYtv06N07AFbjL+kDfqDMTfzQkYMHp1LAq1Ihbq1rHWSBH5n3ekq
+    KiY8JKpv8DR5Po1iKaXJFuDByGDENJwYbSRSpSK3P+vkWWECQQDkEUE/ZPqqqZkQ
+    2iWRPAsCbEID8SAraQl3DdCLYs/GgARfmmj4yUHEwkys9Jo1H8k4BdxugmaUwNi5
+    YQ/CVzrXAkEAxNO80ArbGxPUmr11GHG/bGBYj1DUBkHZSc7dgxZdtUCLGNxQnNsg
+    Iwq3n6j1sUzS3UW6abQ8bivYNOUcMKJAqQJBANQxFaLU4b/NQaODQ3aoBZpAfP9L
+    5eFdvbet+7zjt2r5CpikgkwOfAmDuXEltx/8LevY0CllW+nErx9zJgVrwUsCQQCu
+    76H5JiznPBDSF2FjgHWqVVdgyW4owY3mU739LHvNBLicN/RN9VPy0Suy8/CqzKT9
+    lWPBXzf2k3FuUdNkRlFBAkEAmpXoybuiFR2S5Bma/ax96lVs0/VihhfC1zZP/X/F
+    Br77+h9dIul+2DnyOl50zu0Sdzst1/7ay4JSDHyiBCMGSQ==
+    -----END RSA PRIVATE KEY-----
+    </PRE
+></LI
+></OL
+></DIV
+><P
+>That's it! The certificate, <TT
+CLASS="FILENAME"
+>newcert.pem</TT
+>, and
+    the private key - <TT
+CLASS="FILENAME"
+>newkey.pem</TT
+> (encrypted) or
+    <TT
+CLASS="FILENAME"
+>newkey2.pem</TT
+> (unencrypted) - are now ready to be used.
+    You may wish to rename the files to more intuitive names.
+    </P
+><P
+>You should also keep the CA's certificate <TT
+CLASS="FILENAME"
+>demo/cacert.pem
+    </TT
+> handy for use when developing and deploying SSL or S/MIME 
+    applications.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="CONCLUSION"
+>Conclusion</A
+></H1
+><P
+>We've walked through the basic steps in the creation of a CA and
+    certificates using the tools that come with OpenSSL. We did not cover more
+    advanced topics such as constraining a certificate to be SSL-only or 
+    S/MIME-only.
+    </P
+><P
+>There exist several HOWTOs similar to this one on the net. This one
+    is written specifically to facilitate discussions in my other HOWTOs
+    on developing SSL and S/MIME applications in 
+    <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+> using 
+    <A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+>.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ID-KLUDGE"
+></A
+></H1
+><P
+>    <TT
+CLASS="LITERAL"
+>$Id:howto.ca.html 583 2007-10-01 19:23:12Z heikki $</TT
+>
+    </P
+></DIV
+></DIV
+></BODY
+></HTML
+>
\ No newline at end of file
diff --git a/doc/howto.smime.html b/doc/howto.smime.html
new file mode 100644 (file)
index 0000000..21bc5cb
--- /dev/null
@@ -0,0 +1,1570 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Programming S/MIME in Python with M2Crypto</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.64
+"></HEAD
+><BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="ARTICLE"
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Programming S/MIME in Python with M2Crypto</A
+></H1
+><H3
+CLASS="AUTHOR"
+><A
+NAME="AEN4"
+>Pheng Siong Ng</A
+></H3
+><DIV
+CLASS="AFFILIATION"
+><DIV
+CLASS="ADDRESS"
+><P
+CLASS="ADDRESS"
+>ngps@post1.com</P
+></DIV
+></DIV
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2000, 2001 by Ng Pheng Siong.</P
+><DIV
+CLASS="REVHISTORY"
+><TABLE
+WIDTH="100%"
+BORDER="0"
+><TR
+><TH
+ALIGN="LEFT"
+VALIGN="TOP"
+COLSPAN="3"
+><B
+>Revision History</B
+></TH
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+>Revision $Id: howto.smime.html 646 2008-10-29 03:12:56Z heikki $</TD
+><TD
+ALIGN="LEFT"
+></TD
+><TD
+ALIGN="LEFT"
+></TD
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+COLSPAN="3"
+></TD
+></TR
+></TABLE
+></DIV
+><HR></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+><A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+> 
+    is a <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+>
+    interface to <A
+HREF="http://www.openssl.org"
+TARGET="_top"
+>OpenSSL</A
+>. It makes 
+    available to the Python programmer SSL functionality to implement clients 
+    and servers, S/MIME v2, RSA, DSA, DH, symmetric ciphers, message digests and 
+    HMACs.
+    </P
+><P
+>This document demonstrates programming S/MIME with M2Crypto.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SMIME"
+>S/MIME</A
+></H1
+><P
+>S/MIME - Secure Multipurpose Internet Mail Extensions
+    [<SPAN
+CLASS="CITATION"
+>RFC 2311, RFC 2312</SPAN
+>] - provides a 
+    consistent way to send and receive secure MIME data.  Based on the popular 
+    Internet MIME standard, S/MIME provides the following cryptographic security 
+    services for electronic messaging applications - 
+    <I
+CLASS="EMPHASIS"
+>authentication</I
+>, <I
+CLASS="EMPHASIS"
+>message integrity </I
+> 
+    and <I
+CLASS="EMPHASIS"
+>non-repudiation of origin </I
+> (using <I
+CLASS="EMPHASIS"
+>digital 
+    signatures</I
+>), and <I
+CLASS="EMPHASIS"
+>privacy </I
+> and <I
+CLASS="EMPHASIS"
+>data 
+    security</I
+> (using <I
+CLASS="EMPHASIS"
+>encryption</I
+>).  
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="KEYS-AND-CERTIFICATES"
+>Keys and Certificates</A
+></H1
+><P
+>To create an S/MIME-signed message, you need an RSA key pair
+    (this consists of a public key and a private key) and an X.509
+    certificate of said public key.
+    </P
+><P
+>To create an S/MIME-encrypted message, you need an X.509
+    certificate for each recipient.
+    </P
+><P
+>To create an S/MIME-signed <I
+CLASS="EMPHASIS"
+>and</I
+> -encrypted 
+    message, first create a signed message, then encrypt the signed
+    message with the recipients' certificates.
+    </P
+><P
+>You may generate key pairs and obtain certificates by using a
+    commercial <I
+CLASS="EMPHASIS"
+>certification authority</I
+> service.
+    </P
+><P
+>You can also do so using freely-available software. For many 
+    purposes, e.g., automated S/MIME messaging by system administration 
+    processes, this approach is cheap and effective.
+    </P
+><P
+>We now work through using OpenSSL to generate key pairs and
+    certificates. This assumes you have OpenSSL installed properly on your
+    system.
+    </P
+><P
+>First, we generate an X.509 certificate to be used for signing:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out signer.pem
+    </B
+></TT
+>
+    Using configuration from /usr/local/pkg/openssl/openssl.cnf
+    Generating a 1024 bit RSA private key
+    ..++++++
+    ....................++++++
+    writing new private key to 'privkey.pem'
+    -----
+    You are about to be asked to enter information that will be incorporated
+    into your certificate request.
+    What you are about to enter is what is called a Distinguished Name or a DN.
+    There are quite a few fields but you can leave some blank
+    For some fields there will be a default value,
+    If you enter '.', the field will be left blank.
+    -----
+    Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+    State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+    Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>S/MIME Sender</I
+></TT
+></B
+></TT
+>
+    Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>sender@example.dom</I
+></TT
+></B
+></TT
+>
+    </PRE
+><P
+>This generates a 1024-bit RSA key pair, unencrypted, into
+    <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+>; it also generates a self-signed X.509
+    certificate for the public key into <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>. The
+    certificate is valid for 365 days, i.e., a year.
+    </P
+><P
+>Let's rename <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+> so that we know it is
+    a companion of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>'s:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>mv privkey.pem signer_key.pem</B
+></TT
+>
+    </PRE
+><P
+>To verify the content of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>, execute the
+    following:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl x509 -noout -text -in signer.pem
+    </B
+></TT
+>
+    Certificate:
+        Data:
+            Version: 3 (0x2)
+            Serial Number: 0 (0x0)
+            Signature Algorithm: md5WithRSAEncryption
+            Issuer: C=SG, O=M2Crypto, CN=S/MIME Sender/Email=sender@example.dom
+            Validity
+                Not Before: Mar 24 12:56:16 2001 GMT
+                Not After : Mar 24 12:56:16 2002 GMT
+            Subject: C=SG, O=M2Crypto, CN=S/MIME Sender/Email=sender@example.dom
+            Subject Public Key Info:
+                Public Key Algorithm: rsaEncryption
+                RSA Public Key: (1024 bit)
+                    Modulus (1024 bit):
+                        00:a9:d6:e2:b5:11:3b:ae:3c:e2:17:31:70:e1:6e:
+                        01:f4:19:6d:bd:2a:42:36:2b:37:34:e2:83:1d:0d:
+                        11:2e:b4:99:44:db:10:67:be:97:5f:5b:1a:26:33:
+                        46:23:2f:95:04:7a:35:da:9d:f9:26:88:39:9e:17:
+                        cd:3e:eb:a8:19:8d:a8:2a:f1:43:da:55:a9:2e:2c:
+                        65:ed:04:71:42:ce:73:53:b8:ea:7e:c7:f0:23:c6:
+                        63:c5:5e:68:96:64:a7:b4:2a:94:26:76:eb:79:ea:
+                        e3:4e:aa:82:09:4f:44:87:4a:12:62:b5:d7:1f:ca:
+                        f2:ce:d5:ba:7e:1f:48:fd:b9
+                    Exponent: 65537 (0x10001)
+            X509v3 extensions:
+                X509v3 Subject Key Identifier: 
+                    29:FB:38:B6:BF:E2:40:BB:FF:D5:71:D7:D5:C4:F0:83:1A:2B:C7:99
+                X509v3 Authority Key Identifier: 
+                    keyid:29:FB:38:B6:BF:E2:40:BB:FF:D5:71:D7:D5:C4:F0:83:1A:2B:C7:99
+                    DirName:/C=SG/O=M2Crypto/CN=S/MIME Sender/Email=sender@example.dom
+                    serial:00
+    
+                X509v3 Basic Constraints: 
+                    CA:TRUE
+        Signature Algorithm: md5WithRSAEncryption
+            68:c8:6b:1b:fa:7c:9a:39:35:76:18:15:c9:fd:89:97:62:db:
+            7a:b0:2d:13:dd:97:e8:1b:7a:9f:22:27:83:24:9d:2e:56:ec:
+            97:89:3c:ef:16:55:80:5a:18:7c:22:d0:f6:bb:e3:a4:e8:59:
+            30:ff:99:5a:93:3e:ea:bc:ee:7f:8d:d6:7d:37:8c:ac:3d:74:
+            80:ce:7a:99:ba:27:b9:2a:a3:71:fa:a5:25:ba:47:17:df:07:
+            56:96:36:fd:60:b9:6c:96:06:e8:e3:7b:9f:4b:6a:95:71:a8:
+            34:fc:fc:b5:88:8b:c4:3f:1e:24:f6:52:47:b2:7d:44:67:d9:
+            83:e8
+    </PRE
+><P
+>Next, we generate a self-signed X.509 certificate for the
+    recipient. Note that <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+> will be
+    recreated.
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out recipient.pem
+    </B
+></TT
+>
+    Using configuration from /usr/local/pkg/openssl/openssl.cnf
+    Generating a 1024 bit RSA private key
+    .....................................++++++
+    .................++++++
+    writing new private key to 'privkey.pem'
+    -----
+    You are about to be asked to enter information that will be incorporated
+    into your certificate request.
+    What you are about to enter is what is called a Distinguished Name or a DN.
+    There are quite a few fields but you can leave some blank
+    For some fields there will be a default value,
+    If you enter '.', the field will be left blank.
+    -----
+    Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+    State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+    Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+    Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>S/MIME Recipient</I
+></TT
+></B
+></TT
+>
+    Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>recipient@example.dom</I
+></TT
+></B
+></TT
+>
+    </PRE
+><P
+>Again, rename <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+>:
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>mv privkey.pem recipient_key.pem</B
+></TT
+>
+    </PRE
+><P
+>In the examples to follow, S/MIME Sender,
+    <TT
+CLASS="EMAIL"
+>&#60;<A
+HREF="mailto:sender@example.dom"
+>sender@example.dom</A
+>&#62;</TT
+>, shall be the sender of S/MIME messages,
+    while S/MIME Recipient, <TT
+CLASS="EMAIL"
+>&#60;<A
+HREF="mailto:recipient@example.dom"
+>recipient@example.dom</A
+>&#62;</TT
+>, shall be the
+    recipient of S/MIME messages.
+    </P
+><P
+>Armed with the key pairs and certificates, we are now ready to begin 
+    programming S/MIME in Python. 
+    </P
+><DIV
+CLASS="NOTE"
+><BLOCKQUOTE
+CLASS="NOTE"
+><P
+><B
+>Note: </B
+>The private keys generated above are
+    <I
+CLASS="EMPHASIS"
+>not passphrase-protected</I
+>, i.e., they are
+    <I
+CLASS="EMPHASIS"
+>in the clear</I
+>. Anyone who has access to such a key can
+    generate S/MIME-signed messages with it, and decrypt S/MIME messages
+    encrypted to it's corresponding public key.
+    </P
+><P
+>We may passphrase-protect the keys, if we so choose. M2Crypto will 
+    prompt the user for the passphrase when such a key is being loaded.
+    </P
+></BLOCKQUOTE
+></DIV
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="M2CRYPTO-SMIME"
+>M2Crypto.SMIME</A
+></H1
+><P
+>The Python programmer accesses M2Crypto's S/MIME functionality
+    through class <TT
+CLASS="CLASSNAME"
+>SMIME</TT
+> in the module
+    <TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME</TT
+>. Typically, an <TT
+CLASS="CLASSNAME"
+>SMIME</TT
+> 
+    object is instantiated; the object is then set up for the intended
+    operation: sign, encrypt, decrypt or verify; finally, the operation is invoked on 
+    the object.
+    </P
+><P
+><TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME</TT
+> makes extensive use of
+    <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO</TT
+>: <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO</TT
+>
+    is a Python abstraction of the <TT
+CLASS="CLASSNAME"
+>BIO</TT
+> abstraction in
+    OpenSSL. A commonly used <TT
+CLASS="CLASSNAME"
+>BIO</TT
+> abstraction in M2Crypto is
+    <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO.MemoryBuffer</TT
+>, which implements a
+    memory-based file-like object, similar to Python's own 
+    <TT
+CLASS="CLASSNAME"
+>StringIO</TT
+>.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SIGN"
+>Sign</A
+></H1
+><P
+>The following code demonstrates how to generate an S/MIME-signed
+    message. <TT
+CLASS="FILENAME"
+>randpool.dat</TT
+> contains random data which is
+    used to seed OpenSSL's pseudo-random number generator via M2Crypto.
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, Rand, SMIME
+
+    def makebuf(text):
+        return BIO.MemoryBuffer(text)
+
+    # Make a MemoryBuffer of the message.
+    buf = makebuf('a sign of our times')
+
+    # Seed the PRNG.
+    Rand.load_file('randpool.dat', -1)
+
+    # Instantiate an SMIME object; set it up; sign the buffer.
+    s = SMIME.SMIME()
+    s.load_key('signer_key.pem', 'signer.pem')
+    p7 = s.sign(buf)
+    </PRE
+><P
+><TT
+CLASS="VARNAME"
+>p7</TT
+> now contains a <I
+CLASS="EMPHASIS"
+>PKCS #7 signature
+    blob</I
+> wrapped in an <TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME.PKCS7</TT
+>
+    object. Note that <TT
+CLASS="VARNAME"
+>buf</TT
+> has been consumed by
+    <TT
+CLASS="FUNCTION"
+>sign()</TT
+> and has to be recreated if it is to be used
+    again.
+    </P
+><P
+>We may now send the signed message via SMTP. In these examples, we
+    shall not do so; instead, we'll render the S/MIME output in
+    mail-friendly format, and pretend that our messages are sent and 
+    received correctly.
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    # Recreate buf.
+    buf = makebuf('a sign of our times')
+
+    # Output p7 in mail-friendly format.
+    out = BIO.MemoryBuffer()
+    out.write('From: sender@example.dom\n')
+    out.write('To: recipient@example.dom\n')
+    out.write('Subject: M2Crypto S/MIME testing\n')
+    s.write(out, p7, buf)
+
+    print out.read()
+
+    # Save the PRNG's state.
+    Rand.save_file('randpool.dat')
+    </PRE
+><P
+>Here's the output:
+    </P
+><PRE
+CLASS="SCREEN"
+>    From: sender@example.dom
+    To: recipient@example.dom
+    Subject: M2Crypto S/MIME testing
+    MIME-Version: 1.0
+    Content-Type: multipart/signed ; protocol="application/x-pkcs7-signature" ; micalg=sha1 ; boundary="----3C93156FC7B4EBF49FE9C7DB7F503087"
+    
+    This is an S/MIME signed message
+    
+    ------3C93156FC7B4EBF49FE9C7DB7F503087
+    a sign of our times
+    ------3C93156FC7B4EBF49FE9C7DB7F503087
+    Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+    Content-Transfer-Encoding: base64
+    Content-Disposition: attachment; filename="smime.p7s"
+    
+    MIIE8AYJKoZIhvcNAQcCoIIE4TCCBN0CAQExCzAJBgUrDgMCGgUAMCIGCSqGSIb3
+    DQEHAaAVBBNhIHNpZ24gb2Ygb3VyIHRpbWVzoIIC5zCCAuMwggJMoAMCAQICAQAw
+    DQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+    MRYwFAYDVQQDEw1TL01JTUUgU2VuZGVyMSEwHwYJKoZIhvcNAQkBFhJzZW5kZXJA
+    ZXhhbXBsZS5kb20wHhcNMDEwMzMxMTE0MDMzWhcNMDIwMzMxMTE0MDMzWjBbMQsw
+    CQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBT
+    ZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTCBnzANBgkq
+    hkiG9w0BAQEFAAOBjQAwgYkCgYEA5c5Tj1CHTSOxa1q2q0FYiwMWYHptJpJcvtZm
+    UwrgU5sHrA8OnCM0cDXEj0KPf3cfNjHffB8HWMzI4UEgNmFXQNsxoGZ+iqwxLlNj
+    y9Mh7eFW/Bjq5hNXbouSlQ0rWBRkoxV64y+t6lQehb32WfYXQbKFxFJSXzSxOx3R
+    8YhSPd0CAwEAAaOBtjCBszAdBgNVHQ4EFgQUXOyolL1t4jaBwZFRM7MS8nBLzUow
+    gYMGA1UdIwR8MHqAFFzsqJS9beI2gcGRUTOzEvJwS81KoV+kXTBbMQswCQYDVQQG
+    EwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIx
+    ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbYIBADAMBgNVHRMEBTAD
+    AQH/MA0GCSqGSIb3DQEBBAUAA4GBAHo3DrCHR86fSTVAvfiXdSswWqKtCEhUHRdC
+    TLFGl4hDk2GyZxaFuqZwiURz/H7nMicymI2wkz8H/wyHFg8G3BIehURpj2v/ZWXY
+    eovbgS7EZALVVkDj4hNl/IIHWd6Gtv1UODf7URbxtl3hQ9/eTWITrefT1heuPnar
+    8czydsOLMYIBujCCAbYCAQEwYDBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJD
+    cnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNl
+    bmRlckBleGFtcGxlLmRvbQIBADAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzEL
+    BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAxMDMzMTExNDUwMlowIwYJKoZI
+    hvcNAQkEMRYEFOoeRUd8ExIYXfQq8BTFuKWrSP3iMFIGCSqGSIb3DQEJDzFFMEMw
+    CgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsO
+    AwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIGAQpU8hFUtLCF6hO2t
+    ec9EYJ/Imqqiiw+BxWxkUUVT81Vbjwdn9JST6+sztM5JRP2ZW+b4txEjZriYC8f3
+    kv95YMTGbIsuWkJ93GrbvqoJ/CxO23r9WWRnZEm/1EZN9ZmlrYqzBTxnNRmP3Dhj
+    cW8kzZwH+2/2zz2G7x1HxRWH95A=
+    
+    ------3C93156FC7B4EBF49FE9C7DB7F503087--
+    </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="VERIFY"
+>Verify</A
+></H1
+><P
+>Assume the above output has been saved into <TT
+CLASS="FILENAME"
+>sign.p7</TT
+>. 
+    Let's now verify the signature:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import SMIME, X509
+    
+    # Instantiate an SMIME object.
+    s = SMIME.SMIME()
+    
+    # Load the signer's cert.
+    x509 = X509.load_cert('signer.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    
+    # Load the signer's CA cert. In this case, because the signer's
+    # cert is self-signed, it is the signer's cert itself.
+    st = X509.X509_Store()
+    st.load_info('signer.pem')
+    s.set_x509_store(st)
+    
+    # Load the data, verify it.
+    p7, data = SMIME.smime_load_pkcs7('sign.p7')
+    v = s.verify(p7)
+    print v
+    print data
+    print data.read()
+    </PRE
+><P
+>Here's the output of the above program:
+    </P
+><PRE
+CLASS="SCREEN"
+>    a sign of our times
+    &lt;M2Crypto.BIO.BIO instance at 0x822012c&gt;
+    a sign of our times
+    </PRE
+><P
+>Suppose, instead of loading <TT
+CLASS="FILENAME"
+>signer.pem</TT
+> above,
+    we load <TT
+CLASS="FILENAME"
+>recipient.pem</TT
+>. That is, we do a global
+    substitution of <TT
+CLASS="LITERAL"
+>recipient.pem</TT
+> for
+    <TT
+CLASS="LITERAL"
+>signer.pem</TT
+> in the above program. Here's the modified 
+    program's output:
+    </P
+><PRE
+CLASS="SCREEN"
+>    Traceback (most recent call last):
+      File "./verify.py", line 22, in ?
+        v = s.verify(p7)
+      File "/usr/local/home/ngps/prog/m2/M2Crypto/SMIME.py", line 205, in verify
+        raise SMIME_Error, Err.get_error()
+    M2Crypto.SMIME.SMIME_Error: 312:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:pk7_smime.c:213:Verify error:self signed certificate
+    </PRE
+><P
+>As displayed, the error is generated by line 213 of OpenSSL's
+    <TT
+CLASS="FILENAME"
+>pk7_smime.c</TT
+> (as of OpenSSL 0.9.6); if you are a 
+    C programmer, you may wish to look up the C source to explore OpenSSL's 
+    S/MIME implementation and understand why the error message is worded thus.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ENCRYPT"
+>Encrypt</A
+></H1
+><P
+>We now demonstrate how to generate an S/MIME-encrypted message:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, Rand, SMIME, X509
+    
+    def makebuf(text):
+        return BIO.MemoryBuffer(text)
+    
+    # Make a MemoryBuffer of the message.
+    buf = makebuf('a sign of our times')
+    
+    # Seed the PRNG.
+    Rand.load_file('randpool.dat', -1)
+
+    # Instantiate an SMIME object.
+    s = SMIME.SMIME()
+    
+    # Load target cert to encrypt to.
+    x509 = X509.load_cert('recipient.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    
+    # Set cipher: 3-key triple-DES in CBC mode.
+    s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+    
+    # Encrypt the buffer.
+    p7 = s.encrypt(buf)
+        
+    # Output p7 in mail-friendly format.
+    out = BIO.MemoryBuffer()
+    out.write('From: sender@example.dom\n')
+    out.write('To: recipient@example.dom\n')
+    out.write('Subject: M2Crypto S/MIME testing\n')
+    s.write(out, p7)
+    
+    print out.read()
+
+    # Save the PRNG's state.
+    Rand.save_file('randpool.dat')
+    </PRE
+><P
+>Here's the output of the above program:
+    </P
+><PRE
+CLASS="SCREEN"
+>    From: sender@example.dom
+    To: recipient@example.dom
+    Subject: M2Crypto S/MIME testing
+    MIME-Version: 1.0
+    Content-Disposition: attachment; filename="smime.p7m"
+    Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+    Content-Transfer-Encoding: base64
+    
+    MIIBVwYJKoZIhvcNAQcDoIIBSDCCAUQCAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+    BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+    ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+    KoZIhvcNAQEBBQAEgYCBaXZ+qjpBEZwdP7gjfzfAtQitESyMwo3i+LBOw6sSDir6
+    FlNDPCnkrTvqDX3Rt6X6vBtTCYOm+qiN7ujPkOU61cN7h8dvHR8YW9+0IPY80/W0
+    lZ/HihSRgwTNd7LnxUUcPx8YV1id0dlmP0Hz+Lg+mHf6rqaR//JcYhX9vW4XvjA7
+    BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECMN+qya6ADywgBgHr9Jkhwn5Gsdu7BwX
+    nIQfYTYcdL9I5Sk=
+    </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="DECRYPT"
+>Decrypt</A
+></H1
+><P
+>Assume the above output has been saved into <TT
+CLASS="FILENAME"
+>encrypt.p7</TT
+>. 
+    Decrypt the message thusly:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, SMIME, X509
+    
+    # Instantiate an SMIME object.
+    s = SMIME.SMIME()
+    
+    # Load private key and cert.
+    s.load_key('recipient_key.pem', 'recipient.pem')
+    
+    # Load the encrypted data.
+    p7, data = SMIME.smime_load_pkcs7('encrypt.p7')
+    
+    # Decrypt p7.
+    out = s.decrypt(p7)
+        
+    print out
+    </PRE
+><P
+>Here's the output:
+    </P
+><PRE
+CLASS="SCREEN"
+>    a sign of our times
+    </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SIGN-AND-ENCRYPT"
+>Sign and Encrypt</A
+></H1
+><P
+>Here's how to generate an S/MIME-signed/encrypted message:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, Rand, SMIME, X509
+    
+    def makebuf(text):
+        return BIO.MemoryBuffer(text)
+    
+    # Make a MemoryBuffer of the message.
+    buf = makebuf('a sign of our times')
+    
+    # Seed the PRNG.
+    Rand.load_file('randpool.dat', -1)
+    
+    # Instantiate an SMIME object.
+    s = SMIME.SMIME()
+    
+    # Load signer's key and cert. Sign the buffer.
+    s.load_key('signer_key.pem', 'signer.pem')
+    p7 = s.sign(buf)
+    
+    # Load target cert to encrypt the signed message to.
+    x509 = X509.load_cert('recipient.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    
+    # Set cipher: 3-key triple-DES in CBC mode.
+    s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+    
+    # Create a temporary buffer.
+    tmp = BIO.MemoryBuffer()
+    
+    # Write the signed message into the temporary buffer.
+    s.write(tmp, p7, buf)
+    
+    # Encrypt the temporary buffer.
+    p7 = s.encrypt(tmp)
+        
+    # Output p7 in mail-friendly format.
+    out = BIO.MemoryBuffer()
+    out.write('From: sender@example.dom\n')
+    out.write('To: recipient@example.dom\n')
+    out.write('Subject: M2Crypto S/MIME testing\n')
+    s.write(out, p7)
+    
+    print out.read()
+    
+    # Save the PRNG's state.
+    Rand.save_file('randpool.dat')
+    </PRE
+><P
+>Here's the output of the above program:
+    </P
+><PRE
+CLASS="SCREEN"
+>    From: sender@example.dom
+    To: recipient@example.dom
+    Subject: M2Crypto S/MIME testing
+    MIME-Version: 1.0
+    Content-Disposition: attachment; filename="smime.p7m"
+    Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+    Content-Transfer-Encoding: base64
+    
+    MIIIwwYJKoZIhvcNAQcDoIIItDCCCLACAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+    BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+    ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+    KoZIhvcNAQEBBQAEgYBlZlGupFphwhsGtIAPvDExN61qisz3oem88xoXkUW0SzoR
+    B9zJFFAuQTWzdNJgrKKYikhWjDojaAc/PFl1K5dYxRgtZLB36ULJD/v/yWmxnjz8
+    TvtK+Wbal2P/MH2pZ4LVERXa/snTElhCawUlwtiFz/JvY5CiF/dcwd+AwFQq4jCC
+    B6UGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIRF525UfwszaAggeA85RmX6AXQMxb
+    eBDz/LJeCgc3RqU1UwIsbKMquIs1S46Ebbm5nP75izPnujOkJ2hv+LNzqOWADmOl
+    +CnGEq1qxTyduIgUDA2nBgCL/gVyVy+/XC9dtImUUTxtxLgYtB0ujkBNsOaENOlM
+    fv4SGM3jkR+K/xlYG6HHzZGbfYyNGj2Y7yMZ1rL1m8SnRNmkCysKGTrudeNf6wT9
+    J6wO9DzLTioz3ZnVr3LjsSKIb4tIp4ugqNJaLuW7m3FtZ3MAgxN68hBbJs8TZ8tL
+    V/0jwUqS+grcgZEb9ymfcedxahtDUfHjRkpDpsxZzVVGkSBNcbQu92oByQVnRQ8m
+    wrYLp3/eawM5AvuV7HNpTT5ZR+1t8luishHN9899IMP2Vyg0Ub67FqFypYmM2cm2
+    sjAI4KpfvT00XFNvgLuYwYEKs9syGTO7hiHNQKcF44F5LYv6nTFwmFQB11dAtY9V
+    ull4D2CLDx9OvyNyKwdEZB5dyV0r/uKIdkhST60V2Q9KegpzgFpoZtSKM/HPYSVH
+    1Bc9f3Q/GqZCvNZZCMx8UvRjQR8dRWDSmPJ0VXG1+wJ+fCmSPP3AuQ1/VsgPRqx2
+    56VrpGPpGut40hV8xQFbWIZ2whwWLKPFAHj8B79ZtFUzUrU6Z2rNpvv8inHc/+S/
+    b6GR5s8/gucRblvd7n3OFNX5UJmPmcw9zWbu/1Dr9DY8l0nAQh21y5FGSS8B1wdE
+    oD2M3Lp7JbwjQbRtnDhImqul2S4yu+m+wDD1aR2K4k3GAI7KKgOBWT0+BDClcn8A
+    4Ju6/YUbj33YlMPJgnGijLnolFy0hNW7TmWqR+8tSI3wO5eNKg4qwBnarqc3vgCV
+    quVxINAXyGQCO9lzdw6hudk8/+BlweGdqhONaIWbK5z1L/SfQo6LC9MTsj7FJydq
+    bc+kEbfZS8aSq7uc9axW6Ti0eAPJ8EVHtwhSBgZQRweKFBXs6HbbhMIdc4N0M7Oq
+    UiFXaF6s4n2uihVP6TqXtHEjTpZoC7pC+HCYiuKXUJtaqtXBOh+y3KLvHk09YL6D
+    XmTDg+UTiFsh4jKKm/BhdelbR5JbpJcj5AId76Mfr8+F/1g9ePOvsWHpQr/oIQTo
+    xEkaxCmzEgP0b6caMWfMUQrbVGxBBNcqKc/ir9fGGOPHATzzq/xLcQYvK1tZhd/D
+    ah/gpMPndsyvVCEuFPluWyDiM0VkwHgC2/3pJIYFHaxK64IutmPsy393rHMEB4kN
+    AHau6kWK+yL9qEVH1pP2zvswQ12P7gjt3T/G3bGsmvlXkEfztfjkXo6XnjcBNf5y
+    G+974AKLcjnk1gzIgarz+lAMY57Gkw4oNDMrTqVQ2OJQlvOSbllPXzH+aAiavB8W
+    ZPECLLwHxD4B1AuaiAArgKl935u/TOB+yQOR8JgGsUzROyJqHJ/SC51HkebgCkL1
+    aggtjgPlIBEXLZAlhpWLZ9lAQyrQpvCVJYwaOvfMmvRav4NAFNoZ2/Q7S4Tn1z+U
+    XX+f+GD58P4MPMhU5IKnz4yH4nlHnAiTEvcs85TZUAXze9g/uBOwZITeGtyLi52S
+    aETIr4v7SgXMepX7ThQ1Pv/jddsK/u4j2F34u0XktwCP+UrbfkE2mocdXvdzxbmd
+    tZSznK2qwgVSsPOs9MhUaepbnjmNBFFBrULhrUtSglM/VX/rWNiyh0aw4XYyHhIt
+    9ZNlfEjKjJ67VEMBxBJ/ieUCouRGCxPYD1j65VT7oB3ZiyPu2F2nlUIcYNqPg1Sd
+    QBCrdaOXdJ0uLwyTAUeVE+wMbgscLvWsfZcCCJHAvw9NHFMUcnrdWxAYMVETNUOn
+    uryVAK7VfOldaz6z3NOSOi6nonNeHpR/sipBa4ik5xCRLT9e0S2QJgRvO9GyfAqz
+    3DIzHtxIGePFzTiUYUTxS3i2gnMX2PEe3ChTLlYWD3jNeAKz0iOzpDphIF2xHLLQ
+    1tCAqBmq/vUzALyDFFdFuTIqQZys4z/u4Dmyq9uXs421eN3v2hkVHvDy8uT2Ot29
+    lg4Q5YezR1EjaW//9guL1BXbcKrTEdtxeNqtem7SpZOMTSwD2lhB8z65GrX90Cyt
+    EMmaRSGYEdf5h1afL1SmKOMskbqxe1D2jG/vsXC7XX7xO/ioy0BdiJcYN1JiMOHJ
+    EOzFol5I20YkiV6j+cenfQFwc/NkaSxEkR8AUHJSbvUmRQRl6r0nnsFpZdR1w7pv
+    wkaT+eOpZynO4mY/ZtF6MpXJsixi6L4ZYXEbS6yHf+XGFfB0okILylmwv2bf6+Mq
+    nqXlmGj3Jwq7X9/+2BDqvfpFFX5lSmItKZAobLdssjFR6roJxOqRsGia2aZ+0+U5
+    VhgdITtnElgtHBaeZU5rHDswgdeLVBP+rGWnKxpJ+pLtNNi25sPYRcWFL6Erd25u
+    eXiY8GEIr+u7rqBWpc9HR34sAPRs3ubbCUleT748keCbx247ImBtiDctZxcc1O86
+    +0QjHP6HUT7FSo/FmT7a120S3Gd2jixGh06l/9ij5Z6mJa7Rm7TTbSjup/XISnOT
+    MKWcbI1nfVOhCv3xDq2eLae+s0oVoc041ceRazqFM2TL/Z6UXRME
+    </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="DECRYPT-AND-VERIFY"
+>Decrypt and Verify</A
+></H1
+><P
+>Suppose the above output has been saved into
+    <TT
+CLASS="FILENAME"
+>se.p7</TT
+>. The following demonstrates how to decrypt and
+    verify it:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, SMIME, X509
+    
+    # Instantiate an SMIME object.
+    s = SMIME.SMIME()
+    
+    # Load private key and cert.
+    s.load_key('recipient_key.pem', 'recipient.pem')
+    
+    # Load the signed/encrypted data.
+    p7, data = SMIME.smime_load_pkcs7('se.p7')
+    
+    # After the above step, 'data' == None.  
+    # Decrypt p7. 'out' now contains a PKCS #7 signed blob.
+    out = s.decrypt(p7)
+    
+    # Load the signer's cert.
+    x509 = X509.load_cert('signer.pem')
+    sk = X509.X509_Stack()
+    sk.push(x509)
+    s.set_x509_stack(sk)
+    
+    # Load the signer's CA cert. In this case, because the signer's
+    # cert is self-signed, it is the signer's cert itself.
+    st = X509.X509_Store()
+    st.load_info('signer.pem')
+    s.set_x509_store(st)
+    
+    # Recall 'out' contains a PKCS #7 blob.
+    # Transform 'out'; verify the resulting PKCS #7 blob.
+    p7_bio = BIO.MemoryBuffer(out)
+    p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+    v = s.verify(p7)
+    
+    print v
+    </PRE
+><P
+>The output is as follows:
+    </P
+><PRE
+CLASS="SCREEN"
+>    a sign of our times
+    </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SMTP"
+>Sending S/MIME messages via SMTP</A
+></H1
+><P
+>In the above examples, we've assumed that our S/MIME messages 
+    are sent and received automagically. The following is a Python function that 
+    generates S/MIME-signed/encrypted messages and sends them via SMTP:
+    </P
+><PRE
+CLASS="PROGRAMLISTING"
+>    from M2Crypto import BIO, SMIME, X509
+    import smtplib, string, sys
+    
+    def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+    
+        msg_bio = BIO.MemoryBuffer(msg)
+        sign = from_key
+        encrypt = to_certs
+    
+        s = SMIME.SMIME()
+        if sign:
+            s.load_key(from_key, from_cert)
+            p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+            msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+    
+        if encrypt:
+            sk = X509.X509_Stack()
+            for x in to_certs:
+                sk.push(X509.load_cert(x))
+            s.set_x509_stack(sk)
+            s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+            tmp_bio = BIO.MemoryBuffer()
+            if sign:
+                s.write(tmp_bio, p7)
+            else:
+                tmp_bio.write(msg)
+            p7 = s.encrypt(tmp_bio)
+    
+        out = BIO.MemoryBuffer()
+        out.write('From: %s\r\n' % from_addr)
+        out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+        out.write('Subject: %s\r\n' % subject) 
+        if encrypt:
+            s.write(out, p7)
+        else:
+            if sign:
+                s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+            else:
+                out.write('\r\n')
+                out.write(msg)
+        out.close()
+    
+        smtp = smtplib.SMTP()
+        smtp.connect(smtpd)
+        smtp.sendmail(from_addr, to_addrs, out.read())
+        smtp.quit()
+    </PRE
+><P
+>This function sends plain, S/MIME-signed, S/MIME-encrypted,
+    and S/MIME-signed/encrypted messages, depending on the parameters
+    <TT
+CLASS="PARAMETER"
+><I
+>from_key</I
+></TT
+> and <TT
+CLASS="PARAMETER"
+><I
+>to_certs</I
+></TT
+>. The
+    function's output interoperates with Netscape Messenger.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="VERIFYING-ORIGIN"
+>Verifying origin of S/MIME messages</A
+></H1
+><P
+>In our examples above that decrypt or verify messages, we skipped
+    a step: verifying that the <TT
+CLASS="LITERAL"
+>from</TT
+> address of the message 
+    matches the <TT
+CLASS="LITERAL"
+>email address</TT
+> attribute in the sender's 
+    certificate.
+    </P
+><P
+>The premise of current X.509 certification practice is that the
+    CA is supposed to verify your identity, and to issue a certificate with 
+    <TT
+CLASS="LITERAL"
+>email address</TT
+> that matches your actual mail address. 
+    (Verisign's March 2001 failure in identity verification resulting in
+    Microsoft certificates being issued to spoofers notwithstanding.)
+    </P
+><P
+>If you run your own CA, your certification practice is up to you, of
+    course, and it would probably be part of your security policy.
+    </P
+><P
+>Whether your S/MIME messaging application needs to verify the 
+    <TT
+CLASS="LITERAL"
+>from</TT
+> addresses of S/MIME messages depends on your
+    security policy and your system's threat model, as always.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="NETSCAPE-MESSENGER"
+>Interoperating with Netscape Messenger</A
+></H1
+><P
+>Suppose S/MIME Recipient uses Netscape Messenger. To enable Messenger 
+    to handle S/MIME messages from S/MIME Sender, S/MIME Recipient needs 
+    to configure Messenger with his private key and certificate, as well as S/MIME
+    Sender's certificate.
+    </P
+><DIV
+CLASS="NOTE"
+><BLOCKQUOTE
+CLASS="NOTE"
+><P
+><B
+>Note: </B
+>Configuring Messenger's POP or IMAP settings so that it retrieves
+    mail correctly is beyond the scope of this HOWTO.  
+    </P
+></BLOCKQUOTE
+></DIV
+><P
+>The following steps demonstrate how to import S/MIME Recipient's
+    private key and certificate for Messenger:
+    </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Transform S/MIME Recipient's private key and certificate into
+    <I
+CLASS="EMPHASIS"
+>PKCS #12</I
+> format.
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl pkcs12 -export -in recipient.pem -inkey recipient_key.pem -name "S/MIME Recipient" -out recipient.p12
+    </B
+></TT
+>
+    Enter Export Password:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+    Verifying password - Enter Export Password:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+    </PRE
+></LI
+><LI
+><P
+>Start Messenger.
+    </P
+></LI
+><LI
+><P
+>Click on the (open) "lock" icon at the bottom left corner of
+    Messenger's window. This brings up the "Security Info" dialog box.
+    </P
+></LI
+><LI
+><P
+>Click on "Yours" under "Certificates".
+    </P
+></LI
+><LI
+><P
+>Select "Import a certificate", then pick
+    <TT
+CLASS="FILENAME"
+>recipient.p12</TT
+> from the ensuing file selection dialog
+    box.
+    </P
+></LI
+></OL
+></DIV
+><P
+>Next, you need to import <TT
+CLASS="FILENAME"
+>signer.pem</TT
+> as a CA
+    certificate, so that Messenger will mark messages signed by S/MIME Sender as
+    "trusted":
+    </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Create a DER encoding of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>.
+    </P
+><PRE
+CLASS="SCREEN"
+>    <TT
+CLASS="USERINPUT"
+><B
+>openssl x509 -inform pem -outform der -in signer.pem -out signer.der
+    </B
+></TT
+>
+    </PRE
+></LI
+><LI
+><P
+>Install <TT
+CLASS="FILENAME"
+>signer.der</TT
+> into Messenger as MIME type
+    <TT
+CLASS="LITERAL"
+>application/x-x509-ca-cert</TT
+>. You do this by downloading
+    <TT
+CLASS="FILENAME"
+>signer.der</TT
+> via Navigator from a HTTP or HTTPS server,
+    with the correct MIME type mapping. (You may use 
+    <TT
+CLASS="FILENAME"
+>demo/ssl/https_srv.py</TT
+>, bundled with M2Crypto, for
+    this purpose.) Follow the series of dialog boxes to accept
+    <TT
+CLASS="FILENAME"
+>signer.der</TT
+> as a CA for certifying email users.
+    </P
+></LI
+></OL
+></DIV
+><P
+>S/MIME Recipient is now able to decrypt and read S/MIME Sender's
+    messages with Messenger. Messenger will indicate that S/MIME Sender's
+    messages are signed, encrypted, or encrypted <I
+CLASS="EMPHASIS"
+>and</I
+>
+    signed, as the case may be, via the "stamp" icon on the message window's
+    top right corner.
+    </P
+><P
+>Clicking on the "stamp" icon brings you to the Security Info dialog
+    box. Messenger informs you that the message is, say, encrypted with
+    168-bit DES-EDE3-CBC and that it is digitally signed by the private key
+    corresponding to the public key contained in the certificate
+    <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="MICROSOFT-OUTLOOK"
+>Interoperating with Microsoft Outlook</A
+></H1
+><P
+>I do not know how to do this, as I do not use Outlook. (Nor do I use
+    Netscape Messenger, actually. I use Mutt, top dog of MUAs. ;-) Information on 
+    how to configure Outlook with keys and certificates so that it handles 
+    S/MIME mail is gratefully accepted.
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ZSMIME"
+>ZSmime</A
+></H1
+><P
+>ZSmime is a <A
+HREF="http://www.zope.org"
+TARGET="_top"
+>Zope</A
+>
+    <I
+CLASS="EMPHASIS"
+>product</I
+> that enables Zope to generate 
+    S/MIME-signed/encrypted messages. ZSmime demonstrates how to invoke 
+    M2Crypto in a web application server extension.
+    </P
+><P
+>ZSmime has its own <A
+HREF="http://sandbox.rulemaker.net/ngps/zope/zsmime/howto.html"
+TARGET="_top"
+>HOWTO</A
+>
+    explaining its usage. (That HOWTO has some overlap in content with
+    this document.)
+    </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="RESOURCES"
+>Resources</A
+></H1
+><P
+></P
+><UL
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>IETF S/MIME Working Group - 
+    <A
+HREF="http://www.imc.org/ietf-smime"
+TARGET="_top"
+>    http://www.imc.org/ietf-smime</A
+>
+    </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME and OpenPGP - 
+    <A
+HREF="http://www.imc.org/smime-pgpmime.html"
+TARGET="_top"
+>    http://www.imc.org/smime-pgpmime.html</A
+>
+    </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME Freeware Library - 
+    <A
+HREF="http://www.getronicsgov.com/hot/sfl_home.htm"
+TARGET="_top"
+>    http://www.getronicsgov.com/hot/sfl_home.htm</A
+>
+    </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>Mozilla Network Security Services -
+    <A
+HREF="http://www.mozilla.org/projects/security/pkg/nss"
+TARGET="_top"
+>    http://www.mozilla.org/projects/security/pkg/nss</A
+>
+    </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME Cracking Screen Saver -
+    <A
+HREF="http://www.counterpane.com/smime.html"
+TARGET="_top"
+>    http://www.counterpane.com/smime.html</A
+>
+    </P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ID-KLUDGE"
+></A
+></H1
+><P
+>    <TT
+CLASS="LITERAL"
+>$Id:howto.smime.html 583 2007-10-01 19:23:12Z heikki $</TT
+>
+    </P
+></DIV
+></DIV
+></BODY
+></HTML
+>
\ No newline at end of file
diff --git a/doc/howto.ssl.html b/doc/howto.ssl.html
new file mode 100644 (file)
index 0000000..340f264
--- /dev/null
@@ -0,0 +1,206 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Programming SSL in Python with M2Crypto</TITLE
+>
+
+</HEAD>
+<BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+>
+
+<DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Programming SSL in Python with M2Crypto</A
+></H1
+>
+<P>
+Ng Pheng Siong (ngps@netmemetic.com) and Heikki Toivonen (heikki@osafoundation.org)
+</P
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2001, 2002 by Ng Pheng Siong.</P
+>
+<P
+CLASS="COPYRIGHT"
+>Portions Copyright &copy; 2006 by Open Source Applications Foundation.</P
+>
+</DIV>
+                       
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+><A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+> 
+    is a <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+>
+    interface to <A
+HREF="http://www.openssl.org"
+TARGET="_top"
+>OpenSSL</A
+>. It makes 
+    available to the Python programmer SSL functionality to implement clients 
+    and servers, S/MIME v2, RSA, DSA, DH, symmetric ciphers, message digests and 
+    HMACs.
+    </P
+><P
+>This document demonstrates programming HTTPS with M2Crypto.
+    </P
+></DIV
+>
+       
+
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="history"
+>A bit of history</A
+></H1
+>
+
+<p> M2Crypto was created during the time of Python 1.5, which features
+    a module httplib providing client-side HTTP functionality. M2Crypto sports
+    a httpslib based on httplib.
+    </p>
+
+    <p>
+    Beginning with version 2.0, Python's socket module provided
+    (rudimentary) SSL support. Also in the same version, httplib was
+    enhanced with class HTTPConnection, which is more sophisticated than
+    the old class HTTP, and HTTPSConnection, which does HTTPS.
+    </p>
+
+    <p>
+    Subsequently, M2Crypto.httpslib grew a compatible (but not identical)
+    class HTTPSConnection. 
+    </p>
+    
+    <p>
+    The primary interface difference between the two HTTPSConnection
+    classes is that M2Crypto's version accepts an M2Crypto.SSL.Context
+    instance as a parameter, whereas Python 2.x's SSL support does not
+    permit Pythonic control of the SSL context.
+    </p>
+
+    <p> Within the implementations, Python's
+    <tt>HTTPSConnection</tt> employs a
+    <tt>FakeSocket</tt> object, which collects all input from
+    the SSL connection before returning it to the application as a
+    <tt>StringIO</tt> buffer, whereas M2Crypto's
+    <tt>HTTPSConnection</tt> uses a buffering
+    <tt>M2Crypto.BIO.IOBuffer</tt> object that works over the
+    underlying M2Crypto.SSL.Connection directly.  </p>
+
+       <p>Since then M2Crypto has gained a Twisted wrapper that allows securing
+       Twisted SSL connections with M2Crypto.</p>
+</DIV
+>
+
+
+<DIV CLASS="SECT1" id="secure" name="secure">
+<H1 CLASS="SECT1">Secure SSL</H1>
+       
+<p>It is recommended that you read the book Network Security with OpenSSL by John Viega, Matt Messier and Pravir Chandra,
+ISBN 059600270X.</p>   
+
+<p>Using M2Crypto does not automatically make an SSL connection secure. There are various steps that need to be made
+before we can make that claim. Let's see how a simple client can establish a secure connection:</p>
+
+<pre>
+ctx = SSL.Context()
+ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
+if ctx.load_verify_locations('ca.pem') != 1: raise Exception('No CA certs')
+s = SSL.Connection(ctx)
+s.connect(server_address)
+# Normal protocol (for example HTTP) commands follow
+</pre>
+
+<p>The first line creates an SSL context. The defaults allow any SSL version (except SSL version 2 which has known
+weaknesses) and sets the allowed ciphers to secure ones.</p>
+       
+<p>The second line tells M2Crypto to perform certificate validation. The flags shown above are typical for clients,
+and requires the server to send a certificate. The depth parameter tells how long certificate chains are allowed -
+9 is pretty common default, although probably too long in practice.</p>
+       
+<p>The third line loads the allowed root (certificate authority or CA) certificates.
+Most Linux distributions come with CA certificates in suitable format. You
+could also download the <a href="http://mxr.mozilla.org/seamonkey/source//security/nss/lib/ckfw/builtins/certdata.txt?raw=1">certdata.txt</a>
+file from the <a href="http://www.mozilla.org/projects/security/pki/nss/">NSS</a>
+project and convert it
+with the little M2Crypto utility script <a href="http://svn.osafoundation.org/m2crypto/trunk/demo/x509/certdata2pem.py">demo/x509/certdata2pem.py</a>.</p>     
+
+<p>The fourth line creates an SSL connection object with the secure context.</p>
+       
+<p>The fifth line connects to the server. During this time we perform the last security step: just after connection, but before
+exchanging any data, we compare the commonName (or subjectAltName DNS field) field in the certificate the server returned to the 
+server address we tried to connect to. This happens automatically with SSL.Connection and the Twisted wrapper class, and anything
+that uses those. In all other cases you must do the check manually. It is recommended you call the SSL.Checker to do the actual check.</p>
+
+<p>SSL servers are different in that they typically do not require the client to send a certificate, so there is usually no certificate
+checking. Also, it is typically useless to perform host name checking.</p>     
+
+</DIV>
+
+<DIV CLASS="SECT1">
+<H1 CLASS="SECT1">Code Samples</H1>
+
+<p>The best samples of how to use the various SSL objects are in the tests directory, and the test_ssl.py file specifically.
+There are additional samples in the demo directory, but they are not quaranteed to be up to date.</p>
+       
+<p>NOTE: The tests and demos
+may not be secure as is. Use the information above on how to make them secure.</p>
+</DIV>
+       
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="SSLDUMP"
+>ssldump</A
+></H1
+>
+<P>ssldump "is an SSLv3/TLS network protocol analyser. It identifies
+    TCP connections on the chosen network interface and attempts to interpret
+    them as SSLv3/TLS traffic. When it identifies SSLv3/TLS traffic, it
+    decodes the records and displays them in a textual form to stdout. If
+    provided with the appropriate keying material, it will also decrypt the
+    connections and display the application data traffic.  
+    </P>
+    
+    <P>
+    If linked with OpenSSL, ssldump can display certificates in decoded form
+    and decrypt traffic (provided that it has the appropriate keying
+    material)."
+    </P>
+
+    <P>ssldump is written by Eric Rescorla.
+    </P>
+</DIV
+>
+
+</BODY>
+</HTML>
diff --git a/epydoc.conf b/epydoc.conf
new file mode 100644 (file)
index 0000000..2afa556
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright 2009 Heikki Toivonen. All rights reserved.
+# epydoc --no-private -v --config=epydoc.conf
+[epydoc]
+
+name = M2Crypto
+url = http://chandlerproject.org/Projects/MeTooCrypto
+
+output = html
+target = doc/api
+graph = all
+sourcecode = no
+private = no
+
+modules = ./M2Crypto
+
+# Private
+exclude-introspect = M2Crypto.__m2crypto
+
+# Variable shadows module, which causes the module to double in the doc
+# with second instance showing '. Exclude so we only get one (with ').
+exclude = M2Crypto.PGP.PublicKey
+exclude = M2Crypto.PGP.PublicKeyRing
+exclude = M2Crypto.PGP.packet
+exclude = M2Crypto.SSL.Cipher
+exclude = M2Crypto.SSL.Connection
+exclude = M2Crypto.SSL.Context
+exclude = M2Crypto.SSL.SSLServer
+exclude = M2Crypto.SSL.ssl_dispatcher
+exclude = M2Crypto.SSL.timeout
diff --git a/fedora_setup.sh b/fedora_setup.sh
new file mode 100755 (executable)
index 0000000..76d1f59
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# This script is meant to work around the differences on Fedora Core-based
+# distributions (Redhat, CentOS, ...) compared to other common Linux
+# distributions.
+# 
+# Usage: ./fedora_setup.sh [setup.py options]
+#
+
+arch=`uname -m`
+for i in SWIG/_{ec,evp}.i; do
+  sed -i -e "s/opensslconf\./opensslconf-${arch}\./" "$i"
+done
+
+SWIG_FEATURES=-cpperraswarn python setup.py $*
+
diff --git a/pack.py b/pack.py
new file mode 100644 (file)
index 0000000..0005026
--- /dev/null
+++ b/pack.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+# Clean up M2Crypto source base.
+
+import glob, os, os.path, sys
+
+def zap(arg, dirname, names):
+    for f in glob.glob(dirname + arg):
+        try:
+            os.remove(f)
+        except:
+            pass
+
+if __name__ == "__main__":
+    start = sys.argv[1]
+
+    os.path.walk(start, zap, "/*.pyc")
+
+    if os.name == 'nt':
+        zap_m2 = ("__m2cryptoc.pyd","_m2crypto.py")
+    elif os.name == 'posix':
+        zap_m2 = ("__m2crypto.so","_m2crypto.py")
+    for x in zap_m2:
+        try:
+            os.remove("%s/M2Crypto/%s" % (start, x))
+        except:
+            pass
+
+    zap_swig = ("_m2crypto_wrap*", "_m2crypto.c", "_m2crypto.py", "vc60.pdb")
+    for x in zap_swig:
+        for z in glob.glob("%s/SWIG/%s" % (start, x)):
+            try:
+                os.remove(z)
+            except:
+                pass
+
+
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..e7c49eb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+"""
+Distutils/setuptools installer for M2Crypto.
+
+Copyright (c) 1999-2004, Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008-2011 Heikki Toivonen. All rights reserved.
+"""
+
+import os, sys
+try:
+    from setuptools import setup
+    from setuptools.command import build_ext
+except ImportError:
+    from distutils.core import setup
+    from distutils.command import build_ext
+
+from distutils.core import Extension
+
+
+class _M2CryptoBuildExt(build_ext.build_ext):
+    '''Specialization of build_ext to enable swig_opts to inherit any 
+    include_dirs settings made at the command line or in a setup.cfg file'''
+    user_options = build_ext.build_ext.user_options + \
+            [('openssl=', 'o', 'Prefix for openssl installation location')]
+
+    def initialize_options(self):
+        '''Overload to enable custom openssl settings to be picked up'''
+
+        build_ext.build_ext.initialize_options(self)
+        
+        # openssl is the attribute corresponding to openssl directory prefix
+        # command line option
+        if os.name == 'nt':
+            self.libraries = ['ssleay32', 'libeay32']
+            self.openssl = 'c:\\pkg'
+        else:
+            self.libraries = ['ssl', 'crypto']
+            self.openssl = '/usr'
+       
+    
+    def finalize_options(self):
+        '''Overloaded build_ext implementation to append custom openssl
+        include file and library linking options'''
+
+        build_ext.build_ext.finalize_options(self)
+
+        opensslIncludeDir = os.path.join(self.openssl, 'include')
+        opensslLibraryDir = os.path.join(self.openssl, 'lib')
+        
+        self.swig_opts = ['-I%s' % i for i in self.include_dirs + \
+                          [opensslIncludeDir]]
+        self.swig_opts.append('-includeall')
+        #self.swig_opts.append('-D__i386__') # Uncomment for early OpenSSL 0.9.7 versions, or on Fedora Core if build fails
+        #self.swig_opts.append('-DOPENSSL_NO_EC') # Try uncommenting if you can't build with EC disabled
+        
+        self.include_dirs += [os.path.join(self.openssl, opensslIncludeDir),
+                              os.path.join(os.getcwd(), 'SWIG')]        
+            
+        if sys.platform == 'cygwin':
+            # Cygwin SHOULD work (there's code in distutils), but
+            # if one first starts a Windows command prompt, then bash,
+            # the distutils code does not seem to work. If you start
+            # Cygwin directly, then it would work even without this change.
+            # Someday distutils will be fixed and this won't be needed.
+            self.library_dirs += [os.path.join(self.openssl, 'bin')]
+               
+        self.library_dirs += [os.path.join(self.openssl, opensslLibraryDir)]
+
+
+if sys.version_info < (2,4):
+
+    # This copy of swig_sources is from Python 2.2.
+
+    def swig_sources (self, sources):
+
+        """Walk the list of source files in 'sources', looking for SWIG
+        interface (.i) files.  Run SWIG on all that are found, and
+        return a modified 'sources' list with SWIG source files replaced
+        by the generated C (or C++) files.
+        """
+
+        new_sources = []
+        swig_sources = []
+        swig_targets = {}
+
+        # XXX this drops generated C/C++ files into the source tree, which
+        # is fine for developers who want to distribute the generated
+        # source -- but there should be an option to put SWIG output in
+        # the temp dir.
+
+        if self.swig_cpp:
+            target_ext = '.cpp'
+        else:
+            target_ext = '.c'
+
+        for source in sources:
+            (base, ext) = os.path.splitext(source)
+            if ext == ".i":             # SWIG interface file
+                new_sources.append(base + target_ext)
+                swig_sources.append(source)
+                swig_targets[source] = new_sources[-1]
+            else:
+                new_sources.append(source)
+
+        if not swig_sources:
+            return new_sources
+
+        swig = self.find_swig()
+        swig_cmd = [swig, "-python", "-ISWIG"]
+        if self.swig_cpp:
+            swig_cmd.append("-c++")
+
+        swig_cmd += self.swig_opts 
+
+        for source in swig_sources:
+            target = swig_targets[source]
+            self.announce("swigging %s to %s" % (source, target))
+            self.spawn(swig_cmd + ["-o", target, source])
+
+        return new_sources
+    
+    build_ext.build_ext.swig_sources = swig_sources
+
+
+m2crypto = Extension(name = 'M2Crypto.__m2crypto',
+                     sources = ['SWIG/_m2crypto.i'],
+                     extra_compile_args = ['-DTHREADING'],
+                     #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries
+                     )
+
+setup(name = 'M2Crypto',
+      version = '0.21.1',
+      description = 'M2Crypto: A Python crypto and SSL toolkit',
+      long_description = '''\
+M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+functionality to implement clients and servers; HTTPS extensions to Python's
+httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+used to provide SSL for Twisted.''',
+      license = 'BSD-style license',
+      platforms = ['any'],
+      author = 'Ng Pheng Siong',
+      author_email = 'ngps at sandbox rulemaker net',
+      maintainer = 'Heikki Toivonen',
+      maintainer_email = 'heikki@osafoundation.org',
+      url = 'http://chandlerproject.org/Projects/MeTooCrypto',
+      packages = ['M2Crypto', 'M2Crypto.SSL', 'M2Crypto.PGP'],
+      classifiers = [
+          'Development Status :: 5 - Production/Stable',
+          'Intended Audience :: Developers',
+          'Operating System :: OS Independent',
+          'Programming Language :: C',
+          'Programming Language :: Python',
+          'Topic :: Security :: Cryptography',
+          'Topic :: Software Development :: Libraries :: Python Modules',
+      ],
+
+      ext_modules = [m2crypto],
+      test_suite='tests.alltests.suite',
+      cmdclass = {'build_ext': _M2CryptoBuildExt}
+      )
diff --git a/tests/README b/tests/README
new file mode 100644 (file)
index 0000000..28eac67
--- /dev/null
@@ -0,0 +1,62 @@
+This directory contains unit tests for M2Crypto.
+
+To run all tests, make sure you have installed setuptools and then issue the
+following command from the M2Crypto root directory:
+
+python setup.py test
+
+To run tests in a single file, for example test_ssl.py, do this:
+
+python setup.py test --test-suite=tests.test_ssl
+
+
+Look also in the demo directory for other samples.
+
+
+To create new test certificates:
+
+mkdir certs
+cd certs
+
+Making the CA. You may want to use a locally edited openssl.cnf to
+make sure that X509v3 Basic Constraints CA:TRUE gets set (by default
+it may be false). By default duration may only be just one year; should
+set this for at least 3 years.
+
+CA.sh -newca
+cp demoCA/cacert.pem ../ca.pem
+
+Making the server certificate and private key. make sure commonName
+field is localhost.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../server.pem
+openssl rsa <newkey.pem >>../server.pem
+
+Making the x509 certificate and key.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../x509.pem
+openssl rsa <newkey.pem >>../x509.pem
+openssl x509 -in ../x509.pem -out ../x509.der -outform DER
+
+Making the signer certificate. Make sure the email address is 
+signer@example.com.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../signer.pem
+openssl rsa <newkey.pem >../signer_key.pem
+
+Making the recipient certificate. Make sure the email address is 
+recipient@example.com.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../recipient.pem
+openssl rsa <newkey.pem >../recipient_key.pem
+
+
+Finally run the tests and edit for new values.
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/alltests.py b/tests/alltests.py
new file mode 100644 (file)
index 0000000..b35e58f
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+def suite():
+    from M2Crypto import m2
+    import os
+    import unittest
+    
+    def my_import(name):
+        # See http://docs.python.org/lib/built-in-funcs.html#l2h-6
+        components = name.split('.')
+        try:
+            # python setup.py test
+            mod = __import__(name)
+            for comp in components[1:]:
+                mod = getattr(mod, comp)
+        except ImportError:
+            # python tests/alltests.py
+            mod = __import__(components[1])
+        return mod
+
+    modules_to_test = [
+        'tests.test_asn1',
+        'tests.test_bio',
+        'tests.test_bio_membuf',
+        'tests.test_bio_file',
+        'tests.test_bio_iobuf',
+        'tests.test_bio_ssl',
+        'tests.test_bn',
+        'tests.test_authcookie',
+        'tests.test_dh',
+        'tests.test_dsa',
+        'tests.test_engine',
+        'tests.test_evp',
+        'tests.test_obj',
+        'tests.test_pgp',
+        'tests.test_rand',
+        'tests.test_rc4',
+        'tests.test_rsa',
+        'tests.test_smime',
+        'tests.test_ssl_offline',
+        'tests.test_threading',
+        'tests.test_x509']
+    if os.name == 'posix':
+        modules_to_test.append('tests.test_ssl')
+    elif os.name == 'nt':
+        modules_to_test.append('tests.test_ssl_win')
+    if m2.OPENSSL_VERSION_NUMBER >= 0x90800F and m2.OPENSSL_NO_EC == 0:
+        modules_to_test.append('tests.test_ecdh')
+        modules_to_test.append('tests.test_ecdsa')
+        modules_to_test.append('tests.test_ec_curves')
+    alltests = unittest.TestSuite()
+    for module in map(my_import, modules_to_test):
+        alltests.addTest(module.suite())
+    return alltests
+
+
+def dump_garbage():
+    import gc
+    print '\nGarbage:'
+    gc.collect()
+    if len(gc.garbage):
+    
+        print '\nLeaked objects:'
+        for x in gc.garbage:
+            s = str(x)
+            if len(s) > 77: s = s[:73]+'...'
+            print type(x), '\n  ', s
+    
+        print 'There were %d leaks.' % len(gc.garbage)
+    else:
+        print 'Python garbage collector did not detect any leaks.'
+        print 'However, it is still possible there are leaks in the C code.'
+
+
+def runall(report_leaks=0):
+    report_leaks = report_leaks
+    
+    if report_leaks:
+        import gc
+        gc.enable()
+        gc.set_debug(gc.DEBUG_LEAK & ~gc.DEBUG_SAVEALL)
+    
+    import os, unittest
+    from M2Crypto import Rand
+    
+    try:
+        Rand.load_file('tests/randpool.dat', -1) 
+        unittest.TextTestRunner(verbosity=2).run(suite())
+        Rand.save_file('tests/randpool.dat')
+    finally:
+        if os.name == 'posix':
+            from test_ssl import zap_servers
+            zap_servers()
+
+    if report_leaks:
+        dump_garbage()
+    
+
+if __name__ == '__main__':
+    runall(0)
diff --git a/tests/ca.pem b/tests/ca.pem
new file mode 100644 (file)
index 0000000..5450a68
--- /dev/null
@@ -0,0 +1,62 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            d1:b6:bf:af:06:17:8c:bd
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Validity
+            Not Before: Jul 28 04:30:50 2009 GMT
+            Not After : Jul 27 04:30:50 2012 GMT
+        Subject: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:c8:9b:59:18:c2:bf:21:68:dc:d4:62:30:1f:43:
+                    29:52:85:8d:36:fc:20:7f:11:1b:c6:f3:e6:c2:7a:
+                    d0:17:0e:6e:78:43:21:e9:e2:df:9f:31:87:e8:7a:
+                    37:88:1f:a4:56:a1:e9:cb:13:7b:1b:c0:28:cf:5a:
+                    db:a3:e7:50:6c:c6:55:76:e3:61:e8:73:4b:c2:8c:
+                    ee:1c:29:c1:ee:2d:fd:e2:30:34:69:06:ea:d0:af:
+                    bd:c5:db:86:70:92:26:0a:33:1b:70:a9:e7:6e:a4:
+                    2e:ee:4a:8a:f3:b2:6c:c9:97:28:39:28:28:3f:c5:
+                    90:4d:4e:83:0a:0e:cd:98:93
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+            X509v3 Authority Key Identifier: 
+                keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+                DirName:/C=US/ST=California/O=M2Crypto/CN=Heikki Toivonen
+                serial:D1:B6:BF:AF:06:17:8C:BD
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+        c8:11:af:7d:6d:fb:1c:82:0d:c0:e7:41:f4:b2:a5:b0:69:6d:
+        18:e3:04:aa:49:e6:4a:69:6d:c3:e3:8b:ab:d1:18:ac:72:ef:
+        48:9e:49:c7:57:75:2d:00:1e:08:9f:c3:dc:ca:5f:91:38:0d:
+        ac:f8:1f:cc:fc:f7:c2:5b:ce:d7:0c:cf:b2:fe:c9:a9:ce:b8:
+        07:45:17:1c:cf:b3:07:f9:1f:69:6a:94:03:be:62:62:9c:af:
+        a2:24:25:2d:1f:63:0a:91:6b:bb:e3:6c:ec:20:de:80:d3:04:
+        b4:5e:42:1f:27:bc:1f:79:98:18:ba:fb:8a:34:24:a9:40:1e:
+        b9:7b
+-----BEGIN CERTIFICATE-----
+MIICzjCCAjegAwIBAgIJANG2v68GF4y9MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzA1MFoXDTEyMDcy
+NzA0MzA1MFowTzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9IZWlra2kgVG9pdm9uZW4wgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAMibWRjCvyFo3NRiMB9DKVKFjTb8IH8RG8bz
+5sJ60BcObnhDIeni358xh+h6N4gfpFah6csTexvAKM9a26PnUGzGVXbjYehzS8KM
+7hwpwe4t/eIwNGkG6tCvvcXbhnCSJgozG3Cp526kLu5KivOybMmXKDkoKD/FkE1O
+gwoOzZiTAgMBAAGjgbEwga4wHQYDVR0OBBYEFK1kRXSPg8cs1deghZEQQJqcls/u
+MH8GA1UdIwR4MHaAFK1kRXSPg8cs1deghZEQQJqcls/uoVOkUTBPMQswCQYDVQQG
+EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEChMITTJDcnlwdG8xGDAW
+BgNVBAMTD0hlaWtraSBUb2l2b25lboIJANG2v68GF4y9MAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADgYEAyBGvfW37HIINwOdB9LKlsGltGOMEqknmSmltw+OL
+q9EYrHLvSJ5Jx1d1LQAeCJ/D3MpfkTgNrPgfzPz3wlvO1wzPsv7Jqc64B0UXHM+z
+B/kfaWqUA75iYpyvoiQlLR9jCpFru+Ns7CDegNMEtF5CHye8H3mYGLr7ijQkqUAe
+uXs=
+-----END CERTIFICATE-----
diff --git a/tests/der_encoded_seq.b64 b/tests/der_encoded_seq.b64
new file mode 100644 (file)
index 0000000..ece83d7
--- /dev/null
@@ -0,0 +1,17 @@
+MIIDwTCCA70wggKloAMCAQICAg5QMA0GCSqGSIb3DQEBBQUAMGkxEzARBgoJkiaJk/IsZAEZFgNv
+cmcxGDAWBgoJkiaJk/IsZAEZFghET0VHcmlkczEgMB4GA1UECxMXQ2VydGlmaWNhdGUgQXV0aG9y
+aXRpZXMxFjAUBgNVBAMTDURPRUdyaWRzIENBIDEwHhcNMDUwMTEyMDA1MTAyWhcNMDYwMTEyMDA1
+MTAyWjBhMRMwEQYKCZImiZPyLGQBGRMDb3JnMRgwFgYKCZImiZPyLGQBGRMIZG9lZ3JpZHMxETAP
+BgNVBAsTCFNlcnZpY2VzMR0wGwYDVQQDExRob3N0L2Jvc3Nob2cubGJsLmdvdjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJ+M2ICaiIkr1YcPzXlOSA/DTzduZPq+ALwbmU+nHatL8wXr
+8SmwMq/aIG9egGnJzsTlofkSA5D7HAX3nLKkU0A+k0Eig2QzZ5iPjrtAr2m9/b/EsF2I58yOcEDT
+EZuHQWCg19YE29n3UwqXB6pgpuMcFfFqLz6NydN9i0T7Md8OGwYpNzK6TsOCtyF+nin5mZleDyXF
+BtkXeIDHmq8sYmsELv1fbAwYzpAZqvz3k0umXSMwnyq+z6WpawybNuLrhRzqMrMZYzq0Db/hS4xr
+TeXOFuRQ/IcgWt0rLmOIRYKrVfLujFcAQnaPDW8llK2kgi6OrPlTo8e8JVEcz5d7bKUCAwEAAaN3
+MHUwEQYJYIZIAYb4QgEBBAQDAgbAMA4GA1UdDwEB/wQEAwIE8DAfBgNVHSMEGDAWgBTKGR0Sjm6k
+OF1C1DEOCNvZjRcNXTAvBgNVHREEKDAmgg9ib3NzaG9nLmxibC5nb3aBE01LUm9kcmlndWV6QGxi
+bC5nb3YwDQYJKoZIhvcNAQEFBQADggEBAKZkj+amwHCSdcMC05NBv1Vck46tGEsAEf9WiNvf3B1N
+O+B0d+egcbXME+LgrxWvOIf4k2DKvORt/oz5BavEbmD1+/NUseBEA01bzjXXgmWwW48CK5cb6Qam
+wq1uMa/asUgyLfpVkgA9ThwHIr2/g4HEIqUZLaqotLS5+EzZKhOuWh9OJh+AR6WCxOjTQw1xCJd0
+T/sSlHdtlH47xBkDL5DKe0pn9CgGY+ihehcRpzXMB8tKgxzsE8NuB49IUrVC+wIgzEXEeaSPFenL
+XdWxAKaFWRXyPHrsrtsenxnRkM6y8qxWN6UV5UqpvdE/cy+5QORbzWtfVPYqtNVZoO+N3rc=
\ No newline at end of file
diff --git a/tests/dhparams.pem b/tests/dhparams.pem
new file mode 100644 (file)
index 0000000..edb320a
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAIgAcu3gpJeO8aS6N+sTMa655BMzBNlK66q62VH9RqmHwFJSjjCs3ZsF
+aVsTkO3Mt0gULn1drXsK6Lc6pA8s0eQN8ggyEPVr6ND0jN2jr2qc1XlqD7jxzdxe
+igB66pLvrWvAulrGxg4QMsQjqcpwZZ2ndRpYSmErIi4M1r19nTgjAgEC
+-----END DH PARAMETERS-----
diff --git a/tests/dsa.param.pem b/tests/dsa.param.pem
new file mode 100644 (file)
index 0000000..6300c4f
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN DSA PARAMETERS-----
+MIIBHgKBgQDqYu3smvs8KHBx4XX8otDHQUdtCyWKIRC52eQUSYeWejvdaqm11rIq
+QMFBJDnvicM4avyOIU2d/ZOfLzN7aXFw3Ep67amsLpj82N+n4lASUJwKdOJyzyLG
+9IS41RRek8B3lRAs/zqk1U6P5EaE/uIG4+avYmkSDpB4kmnRGhTYeQIVALKQpdTy
+uat9aoq9mFm8dVA2VxAhAoGAausAkdN4Hj6S5nfPMjJTnwV4u7hVY6b6eAZTJmxs
+ykr3XlKqM3PS0hKhjatp7f+mRNFxYvGrWPLAwnPOIp/iNn7lM7U41ceUj1O3KrD/
+Cg4LQ/bADvY48eytdjMLZDpL3Jwootfs4i8WIb0RnIxg8nClhkY2K2/+4voft+Xi
+DlA=
+-----END DSA PARAMETERS-----
diff --git a/tests/dsa.priv.pem b/tests/dsa.priv.pem
new file mode 100644 (file)
index 0000000..cd5578c
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PRIVATE KEY-----
+MIIBSgIBADCCASsGByqGSM44BAEwggEeAoGBAOpi7eya+zwocHHhdfyi0MdBR20L
+JYohELnZ5BRJh5Z6O91qqbXWsipAwUEkOe+Jwzhq/I4hTZ39k58vM3tpcXDcSnrt
+qawumPzY36fiUBJQnAp04nLPIsb0hLjVFF6TwHeVECz/OqTVTo/kRoT+4gbj5q9i
+aRIOkHiSadEaFNh5AhUAspCl1PK5q31qir2YWbx1UDZXECECgYBq6wCR03gePpLm
+d88yMlOfBXi7uFVjpvp4BlMmbGzKSvdeUqozc9LSEqGNq2nt/6ZE0XFi8atY8sDC
+c84in+I2fuUztTjVx5SPU7cqsP8KDgtD9sAO9jjx7K12MwtkOkvcnCii1+ziLxYh
+vRGcjGDycKWGRjYrb/7i+h+35eIOUAQWAhQ/vK8oLKHdEu7W77fbZ3+jQvvt6Q==
+-----END PRIVATE KEY-----
diff --git a/tests/dsa.pub.pem b/tests/dsa.pub.pem
new file mode 100644 (file)
index 0000000..34c8d45
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN PUBLIC KEY-----
+MIIBtzCCASsGByqGSM44BAEwggEeAoGBAOpi7eya+zwocHHhdfyi0MdBR20LJYoh
+ELnZ5BRJh5Z6O91qqbXWsipAwUEkOe+Jwzhq/I4hTZ39k58vM3tpcXDcSnrtqawu
+mPzY36fiUBJQnAp04nLPIsb0hLjVFF6TwHeVECz/OqTVTo/kRoT+4gbj5q9iaRIO
+kHiSadEaFNh5AhUAspCl1PK5q31qir2YWbx1UDZXECECgYBq6wCR03gePpLmd88y
+MlOfBXi7uFVjpvp4BlMmbGzKSvdeUqozc9LSEqGNq2nt/6ZE0XFi8atY8sDCc84i
+n+I2fuUztTjVx5SPU7cqsP8KDgtD9sAO9jjx7K12MwtkOkvcnCii1+ziLxYhvRGc
+jGDycKWGRjYrb/7i+h+35eIOUAOBhQACgYEAmYO4Qss7pYq3vAEj5qj35A1gsOeC
+rGxvu8w9Dj8jACuz0IHOTq+vFbnB6p30rdloMU7Ci4NHPWqOVmWCFcXxkFgJMJld
+sBbkdbGnhPI80sWJGouUYofHFG4pK0QoMeDZKgg7OgwH8EnmQM+W9KDZYJRSuU4+
+6F6hQkmkwAKAXkU=
+-----END PUBLIC KEY-----
diff --git a/tests/ec.priv.pem b/tests/ec.priv.pem
new file mode 100644 (file)
index 0000000..2147d67
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MG0CAQEEHXXhxMbflWHSfCjfxsqHTsIR+BVbREI6JFYGaUs0oAcGBSuBBAAaoUAD
+PgAEAdJXSN/xnRiDqc4wSiYbWB7LGabs71Y9zzIE1ZbzAcvb7uxtoyUxrmRQC8xD
+EO2qZX16mtpmgoNz3EeT
+-----END EC PRIVATE KEY-----
diff --git a/tests/ec.pub.pem b/tests/ec.pub.pem
new file mode 100644 (file)
index 0000000..4a08b59
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFIwEAYHKoZIzj0CAQYFK4EEABoDPgAEAdJXSN/xnRiDqc4wSiYbWB7LGabs71Y9
+zzIE1ZbzAcvb7uxtoyUxrmRQC8xDEO2qZX16mtpmgoNz3EeT
+-----END PUBLIC KEY-----
diff --git a/tests/fips.py b/tests/fips.py
new file mode 100644 (file)
index 0000000..32eb75b
--- /dev/null
@@ -0,0 +1,8 @@
+try:
+    f = open('/proc/sys/crypto/fips_enabled')
+    try:
+        fips_mode = int(f.read())
+    finally:
+        f.close()
+except Exception, e:
+    fips_mode = 0
diff --git a/tests/long_serial_cert.pem b/tests/long_serial_cert.pem
new file mode 100644 (file)
index 0000000..20ccac4
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIJAPR7mEmILgX6MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMDkwNjE5MTkzNjIyWhcNMDkwNzE5MTkzNjIyWjBF
+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDRb/jlXLidgXQGsOLoFbh4JAvC+BgufR7jn93KPybI0oo8VXFUqr2eFuLDcPiE
+gpWIMrLwq9f0US/M/yXQdsH08L2xe+aaiNl+j+o4VsPhXfnvFvAtFRs+JqCR3VfI
+vVePwov31+/28PmF1kOxr9SmSzvSPnN3SqSC0GDAmhWNYwIDAQABo4GnMIGkMB0G
+A1UdDgQWBBR3SQBG5X/vH18obsb2aaBxhU/+HjB1BgNVHSMEbjBsgBR3SQBG5X/v
+H18obsb2aaBxhU/+HqFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt
+U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPR7mEmI
+LgX6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAoyvUPgjkwPAgiqiU
+4cD1tZdcxIzkk22U02cTjJtAPnIYwqwwDpFltApvRbp8MNiPqXbBt0tchj/Ovu4D
+JiDTMaizVOZ+XmiC68uNTZ4nvEwHvVdKmudNVTZDdFr/a6BErAeTknlMCihN3v6M
+POx8a1iz8Y/wJ8YA74vMPORKlKc=
+-----END CERTIFICATE-----
diff --git a/tests/pubring.pgp b/tests/pubring.pgp
new file mode 100644 (file)
index 0000000..fbe4863
Binary files /dev/null and b/tests/pubring.pgp differ
diff --git a/tests/recipient.pem b/tests/recipient.pem
new file mode 100644 (file)
index 0000000..d766266
--- /dev/null
@@ -0,0 +1,61 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            d1:b6:bf:af:06:17:8c:c1
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Validity
+            Not Before: Jul 28 04:39:19 2009 GMT
+            Not After : Jul 26 04:39:19 2019 GMT
+        Subject: C=US, ST=California, O=M2Crypto, CN=Recipient/emailAddress=recipient@example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:c2:21:a3:4f:64:59:9c:21:39:21:d2:3c:e7:0a:
+                    60:72:c8:39:b3:c3:27:4a:6d:56:8f:a0:5d:1b:c6:
+                    e4:3e:26:61:09:a9:ae:04:83:69:3f:9d:2b:12:7e:
+                    d4:f7:8e:d0:6e:a9:8c:9b:d1:bf:17:0c:bd:d0:73:
+                    99:02:6e:7e:cb:7a:80:2d:cf:b1:29:c0:30:36:3f:
+                    68:12:3e:4e:bf:f9:8b:3d:1d:56:af:24:94:ae:d5:
+                    59:b4:00:50:0c:c0:2b:59:c3:99:b3:8a:19:f1:86:
+                    14:bd:ee:e9:c4:f1:d7:6a:0c:e9:67:8a:94:9a:2d:
+                    2d:60:25:22:c6:72:68:c2:0d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                11:CB:60:AC:55:85:52:84:C5:C8:20:5A:50:13:D0:89:C7:7A:B7:81
+            X509v3 Authority Key Identifier: 
+                keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+    Signature Algorithm: sha1WithRSAEncryption
+        87:56:17:6d:ba:3b:a6:c4:22:af:20:f1:a0:e5:9d:27:c4:50:
+        bd:79:eb:d2:84:e5:9a:00:5f:5d:5a:c3:34:58:77:f5:a9:00:
+        f9:76:e9:2d:89:b4:3f:9d:e3:cf:15:0c:64:1b:0a:03:db:e4:
+        6f:2b:ff:1c:82:89:1a:0f:7e:83:58:0f:e6:da:af:26:97:49:
+        4a:59:d7:61:3f:4b:ed:1d:5b:51:00:3b:83:96:c7:1e:3d:84:
+        f4:91:1f:70:69:12:b9:a7:2c:5b:1b:05:cd:74:90:2b:a0:ba:
+        e7:70:cd:6b:7d:ac:be:d7:92:50:e9:f5:c0:42:29:04:ef:8f:
+        a1:68
+-----BEGIN CERTIFICATE-----
+MIICtzCCAiCgAwIBAgIJANG2v68GF4zBMA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzkxOVoXDTE5MDcy
+NjA0MzkxOVowbzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlSZWNpcGllbnQxJDAiBgkqhkiG9w0B
+CQEWFXJlY2lwaWVudEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAwiGjT2RZnCE5IdI85wpgcsg5s8MnSm1Wj6BdG8bkPiZhCamuBINpP50r
+En7U947QbqmMm9G/Fwy90HOZAm5+y3qALc+xKcAwNj9oEj5Ov/mLPR1WrySUrtVZ
+tABQDMArWcOZs4oZ8YYUve7pxPHXagzpZ4qUmi0tYCUixnJowg0CAwEAAaN7MHkw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFBHLYKxVhVKExcggWlAT0InHereBMB8GA1UdIwQY
+MBaAFK1kRXSPg8cs1deghZEQQJqcls/uMA0GCSqGSIb3DQEBBQUAA4GBAIdWF226
+O6bEIq8g8aDlnSfEUL1569KE5ZoAX11awzRYd/WpAPl26S2JtD+d488VDGQbCgPb
+5G8r/xyCiRoPfoNYD+baryaXSUpZ12E/S+0dW1EAO4OWxx49hPSRH3BpErmnLFsb
+Bc10kCuguudwzWt9rL7XklDp9cBCKQTvj6Fo
+-----END CERTIFICATE-----
diff --git a/tests/recipient_key.pem b/tests/recipient_key.pem
new file mode 100644 (file)
index 0000000..5bf0f70
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDCIaNPZFmcITkh0jznCmByyDmzwydKbVaPoF0bxuQ+JmEJqa4E
+g2k/nSsSftT3jtBuqYyb0b8XDL3Qc5kCbn7LeoAtz7EpwDA2P2gSPk6/+Ys9HVav
+JJSu1Vm0AFAMwCtZw5mzihnxhhS97unE8ddqDOlnipSaLS1gJSLGcmjCDQIDAQAB
+AoGAZlrJ+kAUpyc1Mkng5ogoFhzPn6ITg0Bm1U9eCBkzmjkuDKQ0JhkLUwkQ/q10
+qBnad55ZjoZmVEbZhaCNWiTcIIy0nKAMWNKRcg3vTgrnbmbjco1HECDStfJKogZl
+7egoIImHnU1f/IeKQDUYUfs/INonmnnZ1d2jrU7QsdTz84ECQQDzhT0UwP8S1oma
+0IBgeUOt5ptZs7nFdZnbIKCd+ADra6NiQznokCHe5K0WZHqPKvN9asKx1u0h+97H
+Wmk6Fw7RAkEAzBR1+mTRSrlJT8/NTCsIDPtCK/+OhmGbNy1pfsOWq1lN58Za5HV7
+fmtaH2No+MP+DlfNigsg557GzAYl2ZumfQJAHQj33W+dehuGUKUniVksDqH+R9W8
+AqUg8RWU0QDu6yLsWhz13JrCzxao5JCaZFOUsJF4IUglAfZL+6z1+u0g4QJAH5aL
+LFaujoJfdpsTi9adSGUbuPO1e9dfzwqYaaaci6knBdkN+I62rrqvGGyqstajXFT6
+24MddLx+yNWqxiPxgQJBAKF8YiR4eLqLSnq4ftqCqVCC1XbA2H9b7G5RBWi00WFq
+3Nx+B/wjLzbqsMamTCIDUCEW+MzFx6otCxduDZRMKH8=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/rsa.priv.pem b/tests/rsa.priv.pem
new file mode 100644 (file)
index 0000000..0b8b163
--- /dev/null
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM1lIRXaaLVgzlvW
+F2S6OMFJsfG+coZLx9qzmNb2gK6qjyGa71HeaLvFmQFv60dPjpuaGPs2uhL88hcN
+JAChGiD8LxNpVW0EEw+RRH6/CBlDGuKjkSaPz8zzpEhSZq/yGb0F4zaau1HINnwo
+rYPyRXWyRUzfpEB/7mx8/FUD24knAgMBAAECgYAaInsSP8dBBP9c+iHh5DwihBEL
+VJNX+T6F2oJhH96B2xv5R7CZ9zXWZq8wWqBSY5IexH3XQUBt+BeJzVc+aUFcpKLM
+D1O3OZ8NwC9HGIY0sLeX+uawYdFAPJfF8BZ8x3LMxWA8jdJM+4/P3C3jh2EvyzLT
+HQ1rXBPrLkH45xJQSQJBAPPfSiObRvbeJvkgE0z5SqdbQjTGxeAX5qt1e2LtTwdU
+gAxpYnYPz9CFCcIpa0j9UejlGninXasQvhpdwytBLk0CQQDXm/2kKh9BuWzemIu/
+QcLSgc7VrGgZnGQ27fp3bXDSOV3Bev9VywLQ5VDBJcRSkMTC0V/+iHZbMl9EpwHN
+8ZdDAkBJHtAZ8PrMFjvVQnrG/5AUsdYeAONfl4sAKc9/D+w8JGfoUMjG4WLMALe2
+UbjrP5kJnXfcaUI6gmCdgzN7iqWZAkAvJbpKOrfJDH4lEuCEOyIaHC6ZhPDioNM9
+O77ofLMOFWNOGtJY9WKxQWPuSI7sqyGLpHNEWpzfBl3UylxXp3u3AkEAzCzGUMfM
+0qw/TFlVLzCHhrkP13SrJohdD693w9nuhYM2u27R32qJlF1OvN9NxEV7ZoOSGJxi
+CGTjWcXUGgYQgQ==
+-----END PRIVATE KEY-----
diff --git a/tests/rsa.priv2.pem b/tests/rsa.priv2.pem
new file mode 100644 (file)
index 0000000..013ab5e
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIq+j6kBSOkTkCAggA
+MBQGCCqGSIb3DQMHBAin1qbPaI3dAQSCAoAwb9BTWY6+o9GAZk9ZUJHAHL0Yb7C/
+Hkm8Kh+YBqIEHbTzzSzIO3pFFnLrLLSVbWuYX3bBJRDSUfmV9JaZu0YYJ/TzBtb5
+epgD+sZ83E11NM0L3rJTI9GOUm8b9U15N94X+gnQj0JSK8Ex0dJpJ3rwHPd1zAOe
+0SjXViOCCuHeu4Mnz3P9B42FR5C/53GLkqtSZCsznSBsbPGZ/mb6eEGjgYtxFm15
+17Px7ezDjjr5knBozYua3OehCfI6lN1W+yyTvHGF4lpWkm7Pj24uHHh6yagFQuvB
+RgE8eFLLPLBBa3kHWTn6hAPL4pfPIaPiDtX69IshSv2LVcbUPp6pTkji7mo3EFpN
+Jigd3msMCf6w5Wh4I2k8Hb6eSkfsModIru05xq0fuTYi1nTh2l/M3FEGeOuBmpbD
+AYzpT6J1+373rshkdqmv1C/REsnnrACGwbM7JN6K3sKnJZesI3iiHY5tnumypyv3
+f7wMaRcIq0QOi/WUIKzU0B4f9WxgjDuFwWyYlEBl2IYZ8wxD0P2s968puc7RRwrc
+11Tn0a993122gBAHaa24iAW2ig2hGktLtxY1EvY6Sfd/migu2iVA6bwdVz68kKBj
+tYfJQEoMGJhR+NqSDYvgJYgoNljOIf6Wq++L9/zqgtYkiL7xRLqSvths2NWaxGmc
+RvjWFeq2sTiVXFn36jzO9YfJ4BFqgt5UoBRSw8jYQwm+W5TUhgWGQxQTTrCUs/36
+5oQXOwpRol+ivO/VtMdDShg6sKHEjQ/FhHqNpPccVLg/g81HbJyfmEmeqYu6rtOd
+xBe9lVFW+86wObsYl1WCHYUQuBUlPv+uEDLqC92/6zLdCtDYYRYvdLF8
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/rsa.pub.pem b/tests/rsa.pub.pem
new file mode 100644 (file)
index 0000000..e4f13e0
--- /dev/null
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNZSEV2mi1YM5b1hdkujjBSbHx
+vnKGS8fas5jW9oCuqo8hmu9R3mi7xZkBb+tHT46bmhj7NroS/PIXDSQAoRog/C8T
+aVVtBBMPkUR+vwgZQxrio5Emj8/M86RIUmav8hm9BeM2mrtRyDZ8KK2D8kV1skVM
+36RAf+5sfPxVA9uJJwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/server.pem b/tests/server.pem
new file mode 100644 (file)
index 0000000..825abc6
--- /dev/null
@@ -0,0 +1,75 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            d1:b6:bf:af:06:17:8c:be
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Validity
+            Not Before: Jul 28 04:31:41 2009 GMT
+            Not After : Jul 26 04:31:41 2019 GMT
+        Subject: C=US, ST=California, O=M2Crypto, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:d4:99:6f:33:3f:e6:ac:0a:34:d8:0e:45:97:f3:
+                    2b:6a:50:2a:84:30:0a:52:9c:15:30:9f:05:29:3a:
+                    21:f4:c1:c3:01:9e:2f:55:56:4e:35:ac:f1:16:1e:
+                    26:8d:b5:26:b7:99:78:92:ea:1c:74:46:ab:41:12:
+                    ef:cc:53:62:cc:59:5c:9e:c4:86:df:d9:25:35:55:
+                    05:4b:16:ff:d9:90:e3:f4:51:b4:b4:fa:c5:98:4b:
+                    60:f0:60:7f:14:4e:1e:dd:61:9b:22:a2:9c:21:17:
+                    43:a3:cb:07:80:f5:75:59:9c:55:1c:fe:e0:66:d4:
+                    70:77:5e:13:06:0c:05:c7:1f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                04:05:3D:6A:A7:E8:D7:52:BD:2F:C4:52:30:7C:2C:BD:D3:81:46:C6
+            X509v3 Authority Key Identifier: 
+                keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+    Signature Algorithm: sha1WithRSAEncryption
+        ac:2b:ad:86:36:96:5c:fb:34:2c:02:ca:d9:5f:a7:8e:b6:58:
+        24:1d:27:b6:8e:81:aa:69:0e:60:26:64:2e:72:a1:ff:d8:ba:
+        bb:7e:5d:46:c7:07:2d:a8:c8:4c:df:1e:ba:c8:bc:21:5b:f2:
+        b3:01:4c:d6:3b:10:fd:49:70:e6:83:01:f3:24:e2:a9:97:d7:
+        c3:9c:5b:2d:d7:64:2b:e5:e2:0e:3e:d9:8c:e6:93:86:39:32:
+        50:43:5f:36:4a:3b:b0:05:e7:65:a3:b3:ef:50:56:7f:7e:dc:
+        f0:65:83:ac:42:7e:97:a0:c0:7e:63:c6:c8:c6:35:d3:60:d1:
+        4f:51
+-----BEGIN CERTIFICATE-----
+MIICkTCCAfqgAwIBAgIJANG2v68GF4y+MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzE0MVoXDTE5MDcy
+NjA0MzE0MVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANSZbzM/5qwKNNgORZfzK2pQKoQwClKcFTCfBSk6IfTB
+wwGeL1VWTjWs8RYeJo21JreZeJLqHHRGq0ES78xTYsxZXJ7Eht/ZJTVVBUsW/9mQ
+4/RRtLT6xZhLYPBgfxROHt1hmyKinCEXQ6PLB4D1dVmcVRz+4GbUcHdeEwYMBccf
+AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu
+ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQEBT1qp+jXUr0vxFIwfCy904FG
+xjAfBgNVHSMEGDAWgBStZEV0j4PHLNXXoIWREECanJbP7jANBgkqhkiG9w0BAQUF
+AAOBgQCsK62GNpZc+zQsAsrZX6eOtlgkHSe2joGqaQ5gJmQucqH/2Lq7fl1Gxwct
+qMhM3x66yLwhW/KzAUzWOxD9SXDmgwHzJOKpl9fDnFst12Qr5eIOPtmM5pOGOTJQ
+Q182SjuwBedlo7PvUFZ/ftzwZYOsQn6XoMB+Y8bIxjXTYNFPUQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDUmW8zP+asCjTYDkWX8ytqUCqEMApSnBUwnwUpOiH0wcMBni9V
+Vk41rPEWHiaNtSa3mXiS6hx0RqtBEu/MU2LMWVyexIbf2SU1VQVLFv/ZkOP0UbS0
++sWYS2DwYH8UTh7dYZsiopwhF0OjyweA9XVZnFUc/uBm1HB3XhMGDAXHHwIDAQAB
+AoGBALBHrSm8kYMTT2/anZ/5tIUJhcdnohePbg6LvJbLqf4tb4l25V6IGn9tL9Yc
+F/GmRD02VwDSd9d+BWAG2Kj+d0rfdCLfKY9O8PVVm0DF6grLZ7ugItYqUHRDYOdV
+MOVOQrx+mCIzHtoEtQ6HLqmqt2rIX731L1TA7OLNm3XHyISJAkEA/mgNNNg0e23G
+64z83yxxwPEnBrnKd1+xjH9QJ0Z9SJJuF4sNXRIFA4YUNvv2MNe3gMS4Hg9w78HL
+PwcEzLnO9QJBANXuWAZGV58CdkM2w7H9+ukxMbQeLSnmgjpdddo31qqbfgFAYZMK
+LppRqyosj+a2qQ6vua0ndstTImSi7KPmCUMCQQDbwr5Fu836ISYIK830aswIw0fX
+A37mB3+zwfZXNwjaO8NmCvQMRZiXJqcnqBdOsckOLuBs9yGzuk/7rfBzeL5RAkA2
+uBcly7o/vsZ3HLvjfB5ApUecVZehvwcSXLN3VI8A5nLNaSVMEe+nozoPuIQ6NAB7
+9DCe/JgjG6mRaibzKTS3AkEAjTl5MTKkYR78+2u3NRU/ypa1iKCicSvI/Ryw7p/z
+Q8XmVA0CmNRvltf9gA1gJ04ZijBPtl+s09uppaCw9L3vuA==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/signer.pem b/tests/signer.pem
new file mode 100644 (file)
index 0000000..816f5fa
--- /dev/null
@@ -0,0 +1,61 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            d1:b6:bf:af:06:17:8c:c0
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Validity
+            Not Before: Jul 28 04:37:25 2009 GMT
+            Not After : Jul 26 04:37:25 2019 GMT
+        Subject: C=US, ST=California, O=M2Crypto, CN=Signer/emailAddress=signer@example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:c3:9c:76:f3:21:aa:10:19:9f:77:e3:82:1d:9d:
+                    c3:4a:da:bc:c3:83:71:d1:89:78:8b:82:a4:b9:c5:
+                    70:bb:e3:00:bf:49:b8:99:96:67:0b:bf:fe:72:cb:
+                    d9:b6:63:85:f4:fb:86:55:32:22:1e:6e:ce:fd:88:
+                    5c:75:9d:77:3c:92:17:c5:b2:70:04:59:02:33:ef:
+                    be:33:26:f1:e4:72:41:45:72:f1:bf:c4:21:b1:fe:
+                    de:92:b9:f3:25:3e:1a:15:4b:26:47:29:cc:38:7f:
+                    58:3b:ae:b7:c5:69:e7:48:81:b6:55:61:45:c3:3f:
+                    b6:9d:06:e5:17:41:f6:f2:e9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                22:CA:29:B7:D7:39:B4:BF:35:F9:36:5E:EE:2B:E4:17:4E:F9:6E:EE
+            X509v3 Authority Key Identifier: 
+                keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+    Signature Algorithm: sha1WithRSAEncryption
+        5f:a0:da:6b:37:b4:bb:25:34:a7:ed:f3:f7:2e:f2:85:aa:91:
+        01:8f:c3:80:e5:44:87:df:9e:64:5e:5f:3e:5c:7f:c1:07:12:
+        2a:46:cc:bb:9f:a4:a5:c8:3f:84:9a:a4:9e:d5:26:33:af:b4:
+        5f:eb:8e:7d:81:65:f6:44:18:78:89:17:74:fb:07:dc:04:65:
+        fa:15:0c:b2:f3:e7:e7:af:1f:d9:02:c4:c4:44:b7:95:91:47:
+        fe:c0:2a:e1:7a:ae:dd:5f:f8:a9:fa:bb:dd:89:2d:0b:05:b6:
+        ce:ba:12:37:7f:97:4c:48:a9:fb:d4:b7:a5:d1:61:f6:85:ea:
+        30:8c
+-----BEGIN CERTIFICATE-----
+MIICsTCCAhqgAwIBAgIJANG2v68GF4zAMA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzcyNVoXDTE5MDcy
+NjA0MzcyNVowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMQ8wDQYDVQQDEwZTaWduZXIxITAfBgkqhkiG9w0BCQEW
+EnNpZ25lckBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+w5x28yGqEBmfd+OCHZ3DStq8w4Nx0Yl4i4KkucVwu+MAv0m4mZZnC7/+csvZtmOF
+9PuGVTIiHm7O/YhcdZ13PJIXxbJwBFkCM+++Mybx5HJBRXLxv8Qhsf7ekrnzJT4a
+FUsmRynMOH9YO663xWnnSIG2VWFFwz+2nQblF0H28ukCAwEAAaN7MHkwCQYDVR0T
+BAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh
+dGUwHQYDVR0OBBYEFCLKKbfXObS/Nfk2Xu4r5BdO+W7uMB8GA1UdIwQYMBaAFK1k
+RXSPg8cs1deghZEQQJqcls/uMA0GCSqGSIb3DQEBBQUAA4GBAF+g2ms3tLslNKft
+8/cu8oWqkQGPw4DlRIffnmReXz5cf8EHEipGzLufpKXIP4SapJ7VJjOvtF/rjn2B
+ZfZEGHiJF3T7B9wEZfoVDLLz5+evH9kCxMREt5WRR/7AKuF6rt1f+Kn6u92JLQsF
+ts66Ejd/l0xIqfvUt6XRYfaF6jCM
+-----END CERTIFICATE-----
diff --git a/tests/signer_key.pem b/tests/signer_key.pem
new file mode 100644 (file)
index 0000000..39abda1
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDDnHbzIaoQGZ9344IdncNK2rzDg3HRiXiLgqS5xXC74wC/SbiZ
+lmcLv/5yy9m2Y4X0+4ZVMiIebs79iFx1nXc8khfFsnAEWQIz774zJvHkckFFcvG/
+xCGx/t6SufMlPhoVSyZHKcw4f1g7rrfFaedIgbZVYUXDP7adBuUXQfby6QIDAQAB
+AoGAZL24JQ85XoFTt5Lb+BS/91Uf0jFn9Nov0um9nE8q+Bi40ctN3wuulkaS7Nw/
+i8dFvh2r2USwfavjvn7z3z7xoMG8V2c1ZFJCI2CKjocuWVkGwNnIsbO7/BOG03nu
+vir/i7TXN0YbN8zMhfuFC9APmR8bdmMa2KgHXzQcLuAmI4ECQQDhDIkC97l6rMKG
+QWbYrbc7GoMZNwCsPb/fasUknGmtPmq+s818i335u1yyhAk5pwKV7HF+WyZ76S2A
+P1bZf9+FAkEA3oN98qoklVmWSK0qV+CKHjZHSqtt32q2eu6+eAO5fVZOWHwXhS/B
+MkTtfKJbIDTLyUnwhKyht/hXOniVqHE5FQJAf99VgoArvc6oAQzsWTXrpQOddhhQ
+o426lkHenrzZNvz+PjmACsJf5CRXuX9Ylo+U4ockvb0hEssddX+H47HK2QJBAIYr
+aV1SJH79pvWpnLeiSAYRmok2tyiZMvELVkQNkuI1kUYfhRslAWxrTXvyddoEm8CC
+2glWAqlokEhMf4kyxEUCQCIQbV+XFoEqkECchik34PPmcPi2ends32dv/sW+AKjQ
+pxKpWbxVB4sEOPZzpmujP0LLxvCY4HOUJDlhENGQ8MM=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/test_asn1.py b/tests/test_asn1.py
new file mode 100644 (file)
index 0000000..30ef759
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.ASN1.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved."""
+
+import unittest, time, datetime
+from M2Crypto import ASN1, m2
+
+class ASN1TestCase(unittest.TestCase):
+
+    def test_Integer(self):
+        pass # XXX Dunno how to test
+
+    def test_BitSTring(self):
+        pass # XXX Dunno how to test
+
+    def test_String(self):
+        asn1ptr = m2.asn1_string_new()
+        text = 'hello there'
+        # In RFC2253 format:
+        # #040B68656C6C6F207468657265
+        #      h e l l o   t h e r e 
+        m2.asn1_string_set(asn1ptr, text)
+        a = ASN1.ASN1_String(asn1ptr, 1)
+        assert a.as_text() == 'hello there', a.as_text()
+        assert a.as_text(flags=m2.ASN1_STRFLGS_RFC2253) == '#040B68656C6C6F207468657265', a.as_text(flags=m2.ASN1_STRFLGS_RFC2253)
+        self.assertEqual(a.as_text(), str(a))
+
+    def test_Object(self):
+        pass # XXX Dunno how to test
+
+    def test_UTCTIME(self):
+        asn1 = ASN1.ASN1_UTCTIME()
+        assert str(asn1) == 'Bad time value'
+        
+        format = '%b %d %H:%M:%S %Y GMT'
+        utcformat = '%y%m%d%H%M%SZ'
+
+        s = '990807053011Z'
+        asn1.set_string(s)
+        #assert str(asn1) == 'Aug  7 05:30:11 1999 GMT'
+        t1 = time.strptime(str(asn1), format)
+        t2 = time.strptime(s, utcformat)
+        self.assertEqual(t1, t2)
+        
+        asn1.set_time(500)
+        #assert str(asn1) == 'Jan  1 00:08:20 1970 GMT'
+        t1 = time.strftime(format, time.strptime(str(asn1), format))
+        t2 = time.strftime(format, time.gmtime(500))
+        self.assertEqual(t1, t2)
+        
+        t = long(time.time()) + time.timezone
+        asn1.set_time(t)
+        t1 = time.strftime(format, time.strptime(str(asn1), format))
+        t2 = time.strftime(format, time.gmtime(t))
+        self.assertEqual(t1, t2)
+
+    def test_UTCTIME_datetime(self):
+        asn1 = ASN1.ASN1_UTCTIME()
+        # Test get_datetime and set_datetime
+        t = time.time()
+        dt = datetime.datetime.fromtimestamp(int(t))
+        udt = dt.replace(tzinfo=ASN1.LocalTimezone()).astimezone(ASN1.UTC)
+        asn1.set_time(int(t))
+        t1 = str(asn1)
+        asn1.set_datetime(dt)
+        t2 = str(asn1)
+        self.assertEqual(t1, t2)
+        self.assertEqual(str(udt), str(asn1.get_datetime()))
+
+        dt = dt.replace(tzinfo=ASN1.LocalTimezone())
+        asn1.set_datetime(dt)
+        t2 = str(asn1)
+        self.assertEqual(t1, t2)
+        self.assertEqual(str(udt), str(asn1.get_datetime()))
+        
+        dt = dt.astimezone(ASN1.UTC)
+        asn1.set_datetime(dt)
+        t2 = str(asn1)
+        self.assertEqual(t1, t2)
+        self.assertEqual(str(udt), str(asn1.get_datetime()))
+         
+
+def suite():
+    return unittest.makeSuite(ASN1TestCase)
+
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
diff --git a/tests/test_authcookie.py b/tests/test_authcookie.py
new file mode 100644 (file)
index 0000000..9ce0eb8
--- /dev/null
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.AuthCookie.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import Cookie, binascii, time, unittest, sys
+from M2Crypto.AuthCookie import AuthCookie, AuthCookieJar, mix, unmix, unmix3
+from M2Crypto import Rand, EVP
+
+class AuthCookieTestCase(unittest.TestCase):
+
+    _format = 'Set-Cookie: _M2AUTH_="exp=%s&data=%s&digest=%s"'
+    if sys.version_info < (2,5):
+        _format += ';'
+    _token = '_M2AUTH_'
+
+    def setUp(self):
+        self.data = 'cogitoergosum'
+        self.exp = time.time() + 3600
+        self.jar = AuthCookieJar()
+
+    def tearDown(self):
+        pass
+
+    def test_mix_unmix(self):
+        dough = mix(self.exp, self.data)
+        exp, data = unmix(dough)
+        self.failUnlessEqual(data, self.data)
+        self.failUnlessEqual(exp, self.exp)
+
+    def test_make_cookie(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        self.failUnless(isinstance(c, AuthCookie))
+        self.failUnlessEqual(c.expiry(), self.exp)
+        self.failUnlessEqual(c.data(), self.data)
+        # Peek inside the cookie jar...
+        key = self.jar._key
+        mac = binascii.b2a_base64(EVP.hmac(key, mix(self.exp, self.data), 'sha1'))[:-1]
+        self.failUnlessEqual(c.mac(), mac)
+        # Ok, stop peeking now.
+        cookie_str = self._format % (repr(self.exp), self.data, mac)
+        self.failUnlessEqual(c.output(), cookie_str)
+
+    def test_expired(self):
+        t = self.exp - 7200
+        c = self.jar.makeCookie(t, self.data)
+        self.failUnless(c.isExpired())
+
+    def test_not_expired(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        self.failIf(c.isExpired())
+
+    def test_is_valid(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        self.failUnless(self.jar.isGoodCookie(c))
+        
+    def test_is_invalid_expired(self):
+        t = self.exp - 7200
+        c = self.jar.makeCookie(t, self.data)
+        self.failIf(self.jar.isGoodCookie(c))
+
+    def test_is_invalid_changed_exp(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        c._expiry = 'this is bad'
+        self.failIf(self.jar.isGoodCookie(c))
+
+    def test_is_invalid_changed_data(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        c._data = 'this is bad'
+        self.failIf(self.jar.isGoodCookie(c))
+
+    def test_is_invalid_changed_mac(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        c._mac = 'this is bad'
+        self.failIf(self.jar.isGoodCookie(c))
+
+    def test_mix_unmix3(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        s = Cookie.SmartCookie()
+        s.load(c.output())
+        exp, data, digest = unmix3(s[self._token].value)
+        self.failUnlessEqual(data, self.data)
+        self.failUnlessEqual(float(exp), self.exp)
+        key = self.jar._key     # Peeking...
+        mac = binascii.b2a_base64(EVP.hmac(key, mix(self.exp, self.data), 'sha1'))[:-1]
+        self.failUnlessEqual(digest, mac)
+
+    def test_cookie_str(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        self.failUnless(self.jar.isGoodCookieString(c.output()))
+
+    def test_cookie_str2(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        s = Cookie.SmartCookie()
+        s.load(c.output())
+        self.failUnless(self.jar.isGoodCookieString(s.output()))
+
+    def test_cookie_str_expired(self):
+        t = self.exp - 7200
+        c = self.jar.makeCookie(t, self.data)
+        s = Cookie.SmartCookie()
+        s.load(c.output())
+        self.failIf(self.jar.isGoodCookieString(s.output()))
+
+    def test_cookie_str_arbitrary_change(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        cout = c.output()
+        str = cout[:32] + 'this is bad' + cout[32:]
+        s = Cookie.SmartCookie()
+        s.load(str)
+        self.failIf(self.jar.isGoodCookieString(s.output()))
+
+    def test_cookie_str_changed_exp(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        cout = c.output()
+        str = cout[:26] + '2' + cout[27:]
+        s = Cookie.SmartCookie()
+        s.load(str)
+        self.failIf(self.jar.isGoodCookieString(s.output()))
+
+    def test_cookie_str_changed_data(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        cout = c.output()
+        str = cout[:36] + 'X' + cout[37:]
+        s = Cookie.SmartCookie()
+        s.load(str)
+        self.failIf(self.jar.isGoodCookieString(s.output()))
+
+    def test_cookie_str_changed_mac(self):
+        c = self.jar.makeCookie(self.exp, self.data)
+        cout = c.output()
+        str = cout[:76] + 'X' + cout[77:]
+        s = Cookie.SmartCookie()
+        s.load(str)
+        self.failIf(self.jar.isGoodCookieString(s.output()))
+
+
+def suite():
+    return unittest.makeSuite(AuthCookieTestCase)
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_bio.py b/tests/test_bio.py
new file mode 100644 (file)
index 0000000..1d7b0c3
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.BIO.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Copyright (c) 2006 Open Source Applications Foundation
+Author: Heikki Toivonen
+"""
+
+import unittest
+from M2Crypto import BIO, Rand
+
+from fips import fips_mode
+
+class CipherStreamTestCase(unittest.TestCase):
+    def try_algo(self, algo):
+        enc = 1
+        dec = 0
+        data = '123456789012345678901234'
+        # Encrypt.
+        mem = BIO.MemoryBuffer()
+        cf = BIO.CipherStream(mem)
+        cf.set_cipher(algo, 'key', 'iv', 1)
+        cf.write(data)
+        cf.flush()
+        cf.write_close()
+        cf.close()
+        xxx = mem.read()
+    
+        # Decrypt.
+        mem = BIO.MemoryBuffer(xxx)
+        cf = BIO.CipherStream(mem)
+        cf.set_cipher(algo, 'key', 'iv', 0)
+        cf.write_close()
+        data2 = cf.read()
+        cf.close()
+        assert not cf.readable()
+        
+        self.assertRaises(IOError, cf.read)
+        self.assertRaises(IOError, cf.readline)
+        self.assertRaises(IOError, cf.readlines)
+    
+        assert data == data2, '%s algorithm cipher test failed' % algo
+        
+    def test_ciphers(self):
+        ciphers=[
+            'des_ede_ecb', 'des_ede_cbc', 'des_ede_cfb', 'des_ede_ofb',
+            'des_ede3_ecb', 'des_ede3_cbc', 'des_ede3_cfb', 'des_ede3_ofb',
+            'aes_128_ecb', 'aes_128_cbc', 'aes_128_cfb', 'aes_128_ofb',
+            'aes_192_ecb', 'aes_192_cbc', 'aes_192_cfb', 'aes_192_ofb',
+            'aes_256_ecb', 'aes_256_cbc', 'aes_256_cfb', 'aes_256_ofb']
+        nonfips_ciphers=['bf_ecb', 'bf_cbc', 'bf_cfb', 'bf_ofb', 
+                         #'idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb',
+                         'cast5_ecb', 'cast5_cbc', 'cast5_cfb', 'cast5_ofb',
+                         #'rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb',
+                         'des_ecb', 'des_cbc', 'des_cfb', 'des_ofb',
+                         'rc4', 'rc2_40_cbc']
+        if not fips_mode: # Forbidden ciphers
+            ciphers += nonfips_ciphers
+        for i in ciphers:
+            self.try_algo(i)
+
+        self.assertRaises(ValueError, self.try_algo, 'nosuchalgo4567')
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(CipherStreamTestCase))
+    return suite    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_bio_file.py b/tests/test_bio_file.py
new file mode 100644 (file)
index 0000000..e118386
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.File.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import File, openfile
+import os, sys
+
+class FileTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.data = 'abcdef' * 64
+        if sys.platform != 'win32':
+            self.fname = os.tmpnam()
+        else:
+            import tempfile
+            self.fname = tempfile.mktemp()
+
+    def tearDown(self):
+        try:
+            os.unlink(self.fname)
+        except OSError:
+            pass
+
+    def test_openfile_rb(self):
+        # First create the file using Python's open().
+        f = open(self.fname, 'wb')
+        f.write(self.data)
+        f.close()
+        # Now open the file using M2Crypto.BIO.openfile().
+        f = openfile(self.fname, 'rb')
+        data = f.read(len(self.data))
+        assert data == self.data
+
+    def test_openfile_wb(self):
+        # First create the file using M2Crypto.BIO.openfile().
+        f = openfile(self.fname, 'wb')
+        f.write(self.data)
+        f.close()
+        # Now open the file using Python's open().
+        f = open(self.fname, 'rb')
+        data = f.read(len(self.data))
+        assert data == self.data
+
+    def test_closed(self):
+        f = openfile(self.fname, 'wb')
+        f.write(self.data)
+        f.close()
+        self.assertRaises(IOError, f.write, self.data)
+
+    def test_use_pyfile(self):
+        # First create the file.
+        f = open(self.fname, 'wb')
+        f2 = File(f)
+        f2.write(self.data)
+        f2.close()
+        # Now read the file.
+        f = open(self.fname, 'rb')
+        data = f.read(len(self.data))
+        assert data == self.data
+
+
+def suite():
+    # Python 2.2 warns that os.tmpnam() is unsafe.
+    try:
+        import warnings
+        warnings.filterwarnings('ignore')
+    except ImportError:
+        pass
+    return unittest.makeSuite(FileTestCase)
+    
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_iobuf.py b/tests/test_bio_iobuf.py
new file mode 100644 (file)
index 0000000..358e5df
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.IOBuffer.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from cStringIO import StringIO
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import IOBuffer, MemoryBuffer
+
+class IOBufferTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self._data = 'abcdef\n'
+        self.data = self._data * 1024
+
+    def tearDown(self):
+        pass
+
+    def test_init_empty(self):
+        mb = MemoryBuffer()
+        io = IOBuffer(mb)
+        out = io.read()
+        assert out == ''
+
+    def test_init_something(self):
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        out = io.read(len(self.data))
+        assert out == self.data
+
+    def test_read_less_than(self):
+        chunk = len(self.data) - 7
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        out = io.read(chunk)
+        assert out == self.data[:chunk]
+        
+    def test_read_more_than(self):
+        chunk = len(self.data) + 8
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        out = io.read(chunk)
+        assert out == self.data
+
+    def test_readline(self):
+        buf = StringIO()
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        while 1:
+            out = io.readline()
+            if not out:
+                break
+            buf.write(out)
+            assert out == self._data
+        assert buf.getvalue() == self.data
+
+    def test_readlines(self):
+        buf = StringIO()
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        lines = io.readlines()
+        for line in lines:
+            assert line == self._data
+            buf.write(line)
+        assert buf.getvalue() == self.data
+
+    def test_closed(self):
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb)
+        io.close()
+        self.assertRaises(IOError, io.write, self.data)
+        assert not io.readable() and not io.writeable()
+
+    def test_read_only(self):
+        mb = MemoryBuffer(self.data)
+        io = IOBuffer(mb, mode='r')
+        self.assertRaises(IOError, io.write, self.data)
+        assert not io.writeable()
+
+
+def suite():
+    return unittest.makeSuite(IOBufferTestCase)
+    
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_membuf.py b/tests/test_bio_membuf.py
new file mode 100644 (file)
index 0000000..7d719fd
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.MemoryBuffer.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import MemoryBuffer
+
+class MemoryBufferTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.data = 'abcdef' * 64
+
+    def tearDown(self):
+        pass
+
+    def test_init_empty(self):
+        mb = MemoryBuffer()
+        assert len(mb) == 0
+        out = mb.read()
+        assert out is None
+
+    def test_init_something(self):
+        mb = MemoryBuffer(self.data)
+        assert len(mb) == len(self.data)
+        out = mb.read()
+        assert out == self.data
+
+    def test_read_less_than(self):
+        chunk = len(self.data) - 7
+        mb = MemoryBuffer(self.data)
+        out = mb.read(chunk)
+        assert out == self.data[:chunk] and len(mb) == (len(self.data) - chunk)
+        
+    def test_read_more_than(self):
+        chunk = len(self.data) + 8
+        mb = MemoryBuffer(self.data)
+        out = mb.read(chunk)
+        assert out == self.data and len(mb) == 0
+
+    def test_write_close(self):
+        mb = MemoryBuffer(self.data)
+        assert mb.writeable()
+        mb.write_close()
+        assert mb.readable()
+        self.assertRaises(IOError, mb.write, self.data)
+        assert not mb.writeable()
+
+    def test_closed(self):
+        mb = MemoryBuffer(self.data)
+        mb.close()
+        self.assertRaises(IOError, mb.write, self.data)
+        assert mb.readable() and not mb.writeable()
+
+
+def suite():
+    return unittest.makeSuite(MemoryBufferTestCase)
+    
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_ssl.py b/tests/test_bio_ssl.py
new file mode 100644 (file)
index 0000000..0dee9da
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+"""Unit tests for M2Crypto.BIO.File.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import unittest, threading, sys, socket
+
+from M2Crypto import BIO 
+from M2Crypto import SSL 
+from M2Crypto import Err
+from M2Crypto import Rand
+from M2Crypto import threading as m2threading
+
+from test_ssl import srv_host, srv_port
+
+class HandshakeClient(threading.Thread):
+    
+    def __init__(self, host, port):
+        threading.Thread.__init__(self)
+        self.host = host
+        self.port = port
+        
+    def run(self):
+        ctx = SSL.Context()
+        ctx.load_cert_chain("tests/server.pem") 
+        conn = SSL.Connection(ctx)
+        cipher_list = conn.get_cipher_list()
+        sslbio = BIO.SSLBio()
+        readbio = BIO.MemoryBuffer()
+        writebio = BIO.MemoryBuffer()
+        sslbio.set_ssl(conn)
+        conn.set_bio(readbio, writebio)
+        conn.set_connect_state()
+        sock = socket.socket()
+        sock.connect((self.host, self.port))
+        
+        handshake_complete = False
+        while not handshake_complete:
+            ret = sslbio.do_handshake()
+            if ret <= 0: 
+                if not sslbio.should_retry() or not sslbio.should_read():
+                    err_string = Err.get_error() 
+                    print err_string
+                    sys.exit("unrecoverable error in handshake - client")
+                else:
+                     output_token  = writebio.read()
+                     if output_token is not None:
+                         sock.sendall(output_token)
+                     else: 
+                         input_token = sock.recv(1024)
+                         readbio.write(input_token) 
+            else:
+                handshake_complete = True 
+       
+        sock.close()
+
+
+class SSLTestCase(unittest.TestCase):
+    
+    def setUp(self):
+        self.sslbio = BIO.SSLBio()
+    
+    def test_pass(self): # XXX leaks 64/24 bytes
+        pass
+
+    def test_set_ssl(self): # XXX leaks 64/1312 bytes
+        ctx = SSL.Context()
+        conn = SSL.Connection(ctx)
+        self.sslbio.set_ssl(conn)
+
+    def test_do_handshake_fail(self): # XXX leaks 64/42066 bytes
+        ctx = SSL.Context()
+        conn = SSL.Connection(ctx)
+        conn.set_connect_state()
+        self.sslbio.set_ssl(conn)
+        ret = self.sslbio.do_handshake() 
+        assert ret == 0 
+
+    def test_should_retry_fail(self): # XXX leaks 64/1312 bytes
+        ctx = SSL.Context()
+        conn = SSL.Connection(ctx) 
+        self.sslbio.set_ssl(conn)
+        ret = self.sslbio.do_handshake() 
+        assert ret == -1 
+        ret = self.sslbio.should_retry() 
+        assert ret == 0 
+    
+    def test_should_write_fail(self): # XXX leaks 64/1312 bytes
+        ctx = SSL.Context()
+        conn = SSL.Connection(ctx) 
+        self.sslbio.set_ssl(conn)
+        ret = self.sslbio.do_handshake() 
+        assert ret == -1 
+        ret = self.sslbio.should_write() 
+        assert ret == 0 
+    
+    def test_should_read_fail(self): # XXX leaks 64/1312 bytes
+        ctx = SSL.Context()
+        conn = SSL.Connection(ctx)
+        self.sslbio.set_ssl(conn)
+        ret = self.sslbio.do_handshake() 
+        assert ret == -1 
+        ret = self.sslbio.should_read() 
+        assert ret == 0 
+   
+    def test_do_handshake_succeed(self): # XXX leaks 196/26586 bytes
+        ctx = SSL.Context() 
+        ctx.load_cert_chain("tests/server.pem")
+        conn = SSL.Connection(ctx) 
+        self.sslbio.set_ssl(conn)
+        readbio = BIO.MemoryBuffer()
+        writebio = BIO.MemoryBuffer()
+        conn.set_bio(readbio, writebio)
+        conn.set_accept_state()
+        handshake_complete = False
+        sock = socket.socket()
+        sock.bind((srv_host, srv_port))
+        sock.listen(5)
+        handshake_client = HandshakeClient(srv_host, srv_port)
+        handshake_client.start() 
+        new_sock, addr = sock.accept()
+        while not handshake_complete:
+            input_token = new_sock.recv(1024)
+            readbio.write(input_token)
+
+            ret = self.sslbio.do_handshake()
+            if ret <= 0:
+                if not self.sslbio.should_retry() or not self.sslbio.should_read():
+                    sys.exit("unrecoverable error in handshake - server")
+            else:
+                handshake_complete = True
+
+            output_token  = writebio.read()
+            if output_token is not None:
+                new_sock.sendall(output_token)
+      
+        handshake_client.join() 
+        sock.close() 
+        new_sock.close() 
+
+def suite(): 
+    return unittest.makeSuite(SSLTestCase)
+    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    m2threading.init()
+    unittest.TextTestRunner().run(suite())
+    m2threading.cleanup()
+    Rand.save_file('randpool.dat')
diff --git a/tests/test_bn.py b/tests/test_bn.py
new file mode 100755 (executable)
index 0000000..62c83fe
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.BN.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import unittest, re
+from M2Crypto import BN, Rand
+
+loops = 16
+
+class BNTestCase(unittest.TestCase):
+
+    def test_rand(self):
+        # defaults
+        for x in range(loops):
+            r8 = BN.rand(8)
+        
+        # top
+        for x in range(loops):
+            r8 = BN.rand(8, top=0)
+            assert r8 & 128
+        for x in range(loops):
+            r8 = BN.rand(8, top=1)
+            assert r8 & 192
+        
+        # bottom
+        for x in range(loops):
+            r8 = BN.rand(8, bottom=1)
+            assert r8 % 2 == 1
+
+        # make sure we can get big numbers and work with them
+        for x in range(loops):
+            r8 = BN.rand(8, top=0)
+            r16 = BN.rand(16, top=0)
+            r32 = BN.rand(32, top=0)
+            r64 = BN.rand(64, top=0)
+            r128 = BN.rand(128, top=0)
+            r256 = BN.rand(256, top=0)
+            r512 = BN.rand(512, top=0)
+            assert r8 < r16 < r32 < r64 < r128 < r256 < r512 < (r512 + 1)
+        
+
+    def test_rand_range(self):
+        # small range
+        for x in range(loops):
+            r = BN.rand_range(1)
+            assert r == 0
+        
+        for x in range(loops):
+            r = BN.rand_range(4)
+            assert 0 <= r < 4
+        
+        # large range
+        r512 = BN.rand(512, top=0)
+        for x in range(loops):
+            r = BN.rand_range(r512)
+            assert 0 <= r < r512
+
+            
+    def test_randfname(self):
+        m = re.compile('^[a-zA-Z0-9]{8}$')
+        for x in range(loops):
+            r = BN.randfname(8)
+            assert m.match(r)
+        
+
+def suite():
+    return unittest.makeSuite(BNTestCase)
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_dh.py b/tests/test_dh.py
new file mode 100644 (file)
index 0000000..0e66c89
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.DH.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+from M2Crypto import DH, BIO, Rand, m2
+
+class DHTestCase(unittest.TestCase):
+
+    params = 'tests/dhparam.pem'
+
+    def genparam_callback(self, *args):
+        pass 
+
+    def genparam_callback2(self):
+        pass 
+
+    def test_init_junk(self):
+        self.assertRaises(TypeError, DH.DH, 'junk')
+
+    def test_gen_params(self):
+        a = DH.gen_params(1024, 2, self.genparam_callback)
+        assert a.check_params() == 0
+
+    def test_gen_params_bad_cb(self):
+        a = DH.gen_params(1024, 2, self.genparam_callback2)
+        assert a.check_params() == 0
+
+    def test_print_params(self):
+        a = DH.gen_params(1024, 2, self.genparam_callback)
+        bio = BIO.MemoryBuffer()
+        a.print_params(bio)
+        params = bio.read()
+        assert params.find('(1024 bit)')
+        assert params.find('generator: 2 (0x2)')
+
+    def test_load_params(self):
+        a = DH.load_params('tests/dhparams.pem')
+        assert a.check_params() == 0
+
+    def test_compute_key(self):
+        a = DH.load_params('tests/dhparams.pem')
+        b = DH.set_params(a.p, a.g)
+        a.gen_key()
+        b.gen_key()
+        ak = a.compute_key(b.pub)
+        bk = b.compute_key(a.pub)
+        assert ak == bk
+        self.assertEqual(len(a), 128)
+
+        self.assertRaises(DH.DHError, setattr, a, 'p', 1)
+        self.assertRaises(DH.DHError, setattr, a, 'priv', 1)
+
+
+def suite():
+    return unittest.makeSuite(DHTestCase)
+
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_dsa.py b/tests/test_dsa.py
new file mode 100644 (file)
index 0000000..7823f50
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.DSA.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import sha
+from M2Crypto import DSA, BIO, Rand, m2
+
+class DSATestCase(unittest.TestCase):
+
+    errkey  = 'tests/rsa.priv.pem'
+    privkey = 'tests/dsa.priv.pem'
+    pubkey  = 'tests/dsa.pub.pem'
+    param   = 'tests/dsa.param.pem'
+
+    data = sha.sha('Can you spell subliminal channel?').digest()
+    different_data = sha.sha('I can spell.').digest()
+
+    def callback(self, *args):
+        pass
+
+    def test_loadkey_junk(self):
+        self.assertRaises(DSA.DSAError, DSA.load_key, self.errkey)
+
+    def test_loadkey(self):
+        dsa = DSA.load_key(self.privkey)
+        assert len(dsa) == 1024
+        self.assertRaises(AttributeError, getattr, dsa, 'foobar')
+        for k in ('p', 'q', 'g', 'priv', 'pub'):
+            self.assertRaises(DSA.DSAError, setattr, dsa, k, 1)
+
+    def test_loadparam(self):
+        self.assertRaises(DSA.DSAError, DSA.load_key, self.param)
+        dsa = DSA.load_params(self.param)
+        assert not dsa.check_key()
+        assert len(dsa) == 1024
+
+    def test_sign(self):
+        dsa = DSA.load_key(self.privkey)
+        assert dsa.check_key()
+        r, s = dsa.sign(self.data)
+        assert dsa.verify(self.data, r, s)
+        assert not dsa.verify(self.data, s, r)
+
+    def test_sign_asn1(self):
+        dsa = DSA.load_key(self.privkey)
+        blob = dsa.sign_asn1(self.data)
+        assert dsa.verify_asn1(self.data, blob)
+
+    def test_sign_with_params_only(self):
+        dsa = DSA.load_params(self.param)
+        self.assertRaises(AssertionError, dsa.sign, self.data)
+        self.assertRaises(AssertionError, dsa.sign_asn1, self.data)
+
+    def test_pub_verify(self):
+        dsa = DSA.load_key(self.privkey)
+        r, s = dsa.sign(self.data)
+        dsapub = DSA.load_pub_key(self.pubkey)
+        assert dsapub.check_key()
+        assert dsapub.verify(self.data, r, s)
+        self.assertRaises(DSA.DSAError, dsapub.sign)
+
+    def test_verify_fail(self):
+        dsa = DSA.load_key(self.privkey)
+        r, s = dsa.sign(self.data)
+        assert not dsa.verify(self.different_data, r, s)
+
+    def test_verify_fail2(self):
+        dsa = DSA.load_key(self.privkey)
+        r,s = dsa.sign(self.data)
+        dsa2 = DSA.load_params(self.param)
+        assert not dsa2.check_key()
+        self.assertRaises(AssertionError, dsa2.verify, self.data, r, s)
+
+    def test_genparam_setparam_genkey(self):
+        dsa = DSA.gen_params(1024, self.callback)
+        assert len(dsa) == 1024
+        p = dsa.p
+        q = dsa.q
+        g = dsa.g
+        dsa2 = DSA.set_params(p,q,g)
+        assert not dsa2.check_key()
+        dsa2.gen_key()
+        assert dsa2.check_key()
+        r,s = dsa2.sign(self.data)
+        assert dsa2.verify(self.data, r, s)
+
+def suite():
+    return unittest.makeSuite(DSATestCase)
+    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ec_curves.py b/tests/test_ec_curves.py
new file mode 100644 (file)
index 0000000..d01f863
--- /dev/null
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# XXX memory leaks
+"""
+    Unit tests for M2Crypto.EC, the curves
+    
+    There are several ways one could unittest elliptical curves
+    but we are going to only validate that we are using the 
+    OpenSSL curve and that it works with ECDSA.  We will assume
+    OpenSSL has validated the curves themselves.  
+    
+    Also, some curves are shorter than a SHA-1 digest of 160 
+    bits.  To keep the testing simple, we will take advantage
+    of ECDSA's ability to sign any digest length and create a 
+    digset string of only 48 bits.  Remember we are testing our
+    ability to access the curve, not ECDSA itself.
+    
+    Copyright (c) 2006 Larry Bugbee. All rights reserved.
+    
+"""
+
+import unittest
+#import sha
+from M2Crypto import EC, Rand
+from test_ecdsa import ECDSATestCase as ECDSATest
+
+
+curves = [
+    ('secp112r1', 112),
+    ('secp112r2', 112),
+    ('secp128r1', 128),
+    ('secp128r2', 128),
+    ('secp160k1', 160),
+    ('secp160r1', 160),
+    ('secp160r2', 160),
+    ('secp192k1', 192),
+    ('secp224k1', 224),
+    ('secp224r1', 224),
+    ('secp256k1', 256),
+    ('secp384r1', 384),
+    ('secp521r1', 521),
+    
+    ('sect113r1', 113),
+    ('sect113r2', 113),
+    ('sect131r1', 131),
+    ('sect131r2', 131),
+    ('sect163k1', 163),
+    ('sect163r1', 163),
+    ('sect163r2', 163),
+    ('sect193r1', 193),
+    ('sect193r2', 193),
+    ('sect233k1', 233),
+    ('sect233r1', 233),
+    ('sect239k1', 239),
+    ('sect283k1', 283),
+    ('sect283r1', 283),
+    ('sect409k1', 409),
+    ('sect409r1', 409),
+    ('sect571k1', 571),
+    ('sect571r1', 571),
+    
+    ('X9_62_prime192v1', 192),
+    ('X9_62_prime192v2', 192),
+    ('X9_62_prime192v3', 192),
+    ('X9_62_prime239v1', 239),
+    ('X9_62_prime239v2', 239),
+    ('X9_62_prime239v3', 239),
+    ('X9_62_prime256v1', 256),
+    
+    ('X9_62_c2pnb163v1', 163),
+    ('X9_62_c2pnb163v2', 163),
+    ('X9_62_c2pnb163v3', 163),
+    ('X9_62_c2pnb176v1', 176),
+    ('X9_62_c2tnb191v1', 191),
+    ('X9_62_c2tnb191v2', 191),
+    ('X9_62_c2tnb191v3', 191),
+    ('X9_62_c2pnb208w1', 208),
+    ('X9_62_c2tnb239v1', 239),
+    ('X9_62_c2tnb239v2', 239),
+    ('X9_62_c2tnb239v3', 239),
+    ('X9_62_c2pnb272w1', 272),
+    ('X9_62_c2pnb304w1', 304),
+    ('X9_62_c2tnb359v1', 359),
+    ('X9_62_c2pnb368w1', 368),
+    ('X9_62_c2tnb431r1', 431),
+    
+    ('wap_wsg_idm_ecid_wtls1', 113),
+    ('wap_wsg_idm_ecid_wtls3', 163),
+    ('wap_wsg_idm_ecid_wtls4', 113),
+    ('wap_wsg_idm_ecid_wtls5', 163),
+    ('wap_wsg_idm_ecid_wtls6', 112),
+    ('wap_wsg_idm_ecid_wtls7', 160),
+    ('wap_wsg_idm_ecid_wtls8', 112),
+    ('wap_wsg_idm_ecid_wtls9', 160),
+    ('wap_wsg_idm_ecid_wtls10', 233),
+    ('wap_wsg_idm_ecid_wtls11', 233),
+    ('wap_wsg_idm_ecid_wtls12', 224),
+]
+
+# The following two curves, according to OpenSSL, have a 
+# "Questionable extension field!" and are not supported by 
+# the OpenSSL inverse function.  ECError: no inverse.
+# As such they cannot be used for signing.  They might, 
+# however, be usable for encryption but that has not 
+# been tested.  Until thir usefulness can be established,
+# they are not supported at this time.
+#curves2 = [
+#    ('ipsec3', 155),
+#    ('ipsec4', 185),
+#]
+
+class ECCurveTests(unittest.TestCase):
+    #data = sha.sha('Kilroy was here!').digest()     # 160 bits
+    data = "digest"     # keep short (48 bits) so lesser curves 
+                        # will work...  ECDSA requires curve be 
+                        # equal or longer than digest
+
+    def genkey(self, curveName, curveLen):
+        curve = getattr(EC, 'NID_'+curveName)
+        ec = EC.gen_params(curve)
+        assert len(ec) == curveLen
+        ec.gen_key()
+        assert  ec.check_key(), 'check_key() failure for "%s"' % curveName
+        return ec
+
+#    def check_ec_curves_genkey(self):        
+#        for curveName, curveLen in curves2:
+#            self.genkey(curveName, curveLen)
+#
+#        self.assertRaises(AttributeError, self.genkey, 
+#                                          'nosuchcurve', 1)
+
+    def sign_verify_ecdsa(self, curveName, curveLen):
+        ec = self.genkey(curveName, curveLen)
+        r, s = ec.sign_dsa(self.data)
+        assert ec.verify_dsa(self.data, r, s)
+        assert not ec.verify_dsa(self.data, s, r)            
+
+    def test_ec_curves_ECDSA(self):
+        for curveName, curveLen in curves:
+            self.sign_verify_ecdsa(curveName, curveLen)
+
+        self.assertRaises(AttributeError, self.sign_verify_ecdsa, 
+                                          'nosuchcurve', 1)
+
+#        for curveName, curveLen in curves2:
+#            self.assertRaises(EC.ECError, self.sign_verify_ecdsa, 
+#                              curveName, curveLen)
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ECCurveTests))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ecdh.py b/tests/test_ecdh.py
new file mode 100644 (file)
index 0000000..29581dc
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.EC, ECDH part.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+
+import unittest
+from M2Crypto import EC, BIO, Rand, m2
+import sys
+
+class ECDHTestCase(unittest.TestCase):
+
+    privkey = 'tests/ec.priv.pem'
+
+    def test_init_junk(self):
+        self.assertRaises(TypeError, EC.EC, 'junk')
+
+    def test_compute_key(self):
+        a = EC.load_key(self.privkey)
+        b = EC.gen_params(EC.NID_sect233k1)
+        b.gen_key()
+        ak = a.compute_dh_key(b.pub())
+        bk = b.compute_dh_key(a.pub())
+        assert ak == bk
+
+    def test_pubkey_from_der(self):
+        a = EC.gen_params(EC.NID_sect233k1)
+        a.gen_key()
+        b = EC.gen_params(EC.NID_sect233k1)
+        b.gen_key()
+        a_pub_der = a.pub().get_der()
+        a_pub = EC.pub_key_from_der(a_pub_der)
+        ak = a.compute_dh_key(b.pub())
+        bk = b.compute_dh_key(a_pub)
+        assert ak == bk
+
+
+def suite():
+    return unittest.makeSuite(ECDHTestCase)
+
+
+if __name__=='__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ecdsa.py b/tests/test_ecdsa.py
new file mode 100644 (file)
index 0000000..74d9a82
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.EC, ECDSA part.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+import unittest
+import sha
+from M2Crypto import EC, BIO, Rand, m2
+
+class ECDSATestCase(unittest.TestCase):
+
+    errkey = 'tests/rsa.priv.pem'
+    privkey = 'tests/ec.priv.pem'
+    pubkey = 'tests/ec.pub.pem'
+
+    data = sha.sha('Can you spell subliminal channel?').digest()
+
+    def callback(self, *args):
+        pass
+
+    def callback2(self):
+        pass
+
+    def test_loadkey_junk(self):
+        self.assertRaises(ValueError, EC.load_key, self.errkey)
+
+    def test_loadkey(self):
+        ec = EC.load_key(self.privkey)
+        assert len(ec) == 233
+
+    def test_loadpubkey(self):
+        # XXX more work needed
+        ec = EC.load_pub_key(self.pubkey)
+        assert len(ec) == 233
+        self.assertRaises(EC.ECError, EC.load_pub_key, self.errkey)
+
+    def _test_sign_dsa(self):
+        ec = EC.gen_params(EC.NID_sect233k1)
+        # ec.gen_key()
+        self.assertRaises(EC.ECError, ec.sign_dsa, self.data)
+        ec = EC.load_key(self.privkey)
+        r, s = ec.sign_dsa(self.data)
+        assert ec.verify_dsa(self.data, r, s)
+        assert not ec.verify_dsa(self.data, s, r)
+
+    def test_sign_dsa_asn1(self):
+        ec = EC.load_key(self.privkey)
+        blob = ec.sign_dsa_asn1(self.data)
+        assert ec.verify_dsa_asn1(self.data, blob)
+        self.assertRaises(EC.ECError, ec.verify_dsa_asn1, blob, self.data)
+
+    def test_verify_dsa(self):
+        ec = EC.load_key(self.privkey)
+        r, s = ec.sign_dsa(self.data)
+        ec2 = EC.load_pub_key(self.pubkey)
+        assert ec2.verify_dsa(self.data, r, s)
+        assert not ec2.verify_dsa(self.data, s, r)
+        
+    def test_genparam(self):
+        ec = EC.gen_params(EC.NID_sect233k1)
+        assert len(ec) == 233
+
+
+def suite():
+    return unittest.makeSuite(ECDSATestCase)
+    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_engine.py b/tests/test_engine.py
new file mode 100644 (file)
index 0000000..91c2aa8
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.Engine."""
+
+import unittest
+from M2Crypto import Engine, m2
+
+class EngineTestCase(unittest.TestCase):
+
+    privkey = 'tests/rsa.priv.pem'
+    bad_id = '1bea1edfeb97'
+
+    def tearDown(self):
+        Engine.cleanup()
+
+    def test_by_id_junk(self):
+        self.assertRaises(ValueError, Engine.Engine, self.bad_id)
+        self.assertRaises(ValueError, Engine.Engine)
+
+    def test_by_id_openssl(self):
+        Engine.load_openssl()
+        e = Engine.Engine('openssl')
+        self.assertEqual(e.get_name(), 'Software engine support')
+        self.assertEqual(e.get_id(), 'openssl')
+        
+    def test_by_id_dynamic(self):
+        Engine.load_dynamic()
+        Engine.Engine('dynamic')
+        
+    def test_load_private(self):
+        Engine.load_openssl()
+        e = Engine.Engine('openssl')
+        e.set_default()
+        e.load_private_key(self.privkey)
+
+    def test_load_certificate(self):
+        Engine.load_openssl()
+        e = Engine.Engine('openssl')
+        e.set_default()
+        self.assertRaises(Engine.EngineError, e.load_certificate, '/dev/null')
+
+def suite():
+    return unittest.makeSuite(EngineTestCase)
+    
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_evp.py b/tests/test_evp.py
new file mode 100644 (file)
index 0000000..ba09092
--- /dev/null
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.EVP.
+
+Copyright (c) 2004-2007 Open Source Applications Foundation
+Author: Heikki Toivonen
+"""
+
+import unittest
+import cStringIO, sha
+from binascii import hexlify, unhexlify
+from M2Crypto import EVP, RSA, util, Rand, m2, BIO
+from M2Crypto.util import h2b
+
+from fips import fips_mode
+
+class EVPTestCase(unittest.TestCase):
+    def _gen_callback(self, *args):
+        pass
+    
+    def _pass_callback(self, *args):
+        return 'foobar'
+    
+    def _assign_rsa(self):
+        rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa, capture=0) # capture=1 should cause crash
+        return rsa
+    
+    def test_assign(self):
+        rsa = self._assign_rsa()
+        rsa.check_key()
+        
+    def test_pem(self):
+        rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa)
+        assert pkey.as_pem(callback=self._pass_callback) != pkey.as_pem(cipher=None)
+        self.assertRaises(ValueError, pkey.as_pem, cipher='noXX$$%%suchcipher',
+                          callback=self._pass_callback)
+                          
+    def test_as_der(self):
+        """
+        Test DER encoding the PKey instance after assigning 
+        a RSA key to it.
+        """
+        rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa)
+        der_blob = pkey.as_der()        
+        #A quick but not thorough sanity check
+        assert len(der_blob) == 160
+          
+        
+    def test_MessageDigest(self):
+        self.assertRaises(ValueError, EVP.MessageDigest, 'sha513')
+        md = EVP.MessageDigest('sha1')
+        assert md.update('Hello') == 1
+        assert util.octx_to_num(md.final()) == 1415821221623963719413415453263690387336440359920
+
+    def test_as_der_capture_key(self):
+        """
+        Test DER encoding the PKey instance after assigning 
+        a RSA key to it. Have the PKey instance capture the RSA key.
+        """
+        rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa, 1)
+        der_blob = pkey.as_der()
+        #A quick but not thorough sanity check
+        assert len(der_blob) == 160
+
+    def test_size(self):
+        rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa)
+        size = pkey.size() 
+        assert size == 128
+        
+    def test_hmac(self):
+        assert util.octx_to_num(EVP.hmac('key', 'data')) == 92800611269186718152770431077867383126636491933, util.octx_to_num(EVP.hmac('key', 'data'))
+        if not fips_mode: # Disabled algorithms
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='md5')) == 209168838103121722341657216703105225176, util.octx_to_num(EVP.hmac('key', 'data', algo='md5'))
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='ripemd160')) == 1176807136224664126629105846386432860355826868536, util.octx_to_num(EVP.hmac('key', 'data', algo='ripemd160'))
+         
+        if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha224')) == 2660082265842109788381286338540662430962855478412025487066970872635, util.octx_to_num(EVP.hmac('key', 'data', algo='sha224'))
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha256')) == 36273358097036101702192658888336808701031275731906771612800928188662823394256, util.octx_to_num(EVP.hmac('key', 'data', algo='sha256'))
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha384')) == 30471069101236165765942696708481556386452105164815350204559050657318908408184002707969468421951222432574647369766282, util.octx_to_num(EVP.hmac('key', 'data', algo='sha384'))
+            assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha512')) == 3160730054100700080556942280820129108466291087966635156623014063982211353635774277148932854680195471287740489442390820077884317620321797003323909388868696, util.octx_to_num(EVP.hmac('key', 'data', algo='sha512'))
+        
+        self.assertRaises(ValueError, EVP.hmac, 'key', 'data', algo='sha513')
+
+
+    def test_get_rsa(self):
+        """
+        Testing retrieving the RSA key from the PKey instance.
+        """
+        rsa = RSA.gen_key(512, 3, callback=self._gen_callback)
+        assert isinstance(rsa, RSA.RSA)
+        pkey = EVP.PKey()
+        pkey.assign_rsa(rsa) 
+        rsa2 = pkey.get_rsa()
+        assert isinstance(rsa2, RSA.RSA_pub)
+        assert rsa.e == rsa2.e
+        assert rsa.n == rsa2.n
+        pem = rsa.as_pem(callback=self._pass_callback)
+        pem2 = rsa2.as_pem()
+        assert pem
+        assert pem2
+        assert pem != pem2
+        
+        message = "This is the message string"
+        digest = sha.sha(message).digest()
+        assert rsa.sign(digest) == rsa2.sign(digest)
+        
+        rsa3 = RSA.gen_key(1024, 3, callback=self._gen_callback)
+        assert rsa.sign(digest) != rsa3.sign(digest)
+    
+    def test_get_rsa_fail(self):
+        """
+        Testing trying to retrieve the RSA key from the PKey instance
+        when it is not holding a RSA Key. Should raise a ValueError.
+        """
+        pkey = EVP.PKey()
+        self.assertRaises(ValueError, pkey.get_rsa)
+
+    def test_get_modulus(self):
+        pkey = EVP.PKey()
+        self.assertRaises(ValueError, pkey.get_modulus)
+
+        rsa = RSA.gen_key(512, 3, callback=self._gen_callback)
+        pkey.assign_rsa(rsa)
+        mod = pkey.get_modulus()
+        assert len(mod) > 0, mod
+        assert len(mod.strip('0123456789ABCDEF')) == 0
+        
+    def test_verify_final(self):
+        from M2Crypto import X509
+        pkey = EVP.load_key('tests/signer_key.pem')
+        pkey.sign_init()
+        pkey.sign_update('test  message')
+        sig = pkey.sign_final()
+        
+        # OK
+        x509 = X509.load_cert('tests/signer.pem')
+        pubkey = x509.get_pubkey()
+        pubkey.verify_init()
+        pubkey.verify_update('test  message')
+        assert pubkey.verify_final(sig) == 1
+        
+        # wrong cert
+        x509 = X509.load_cert('tests/x509.pem')
+        pubkey = x509.get_pubkey()
+        pubkey.verify_init()
+        pubkey.verify_update('test  message')
+        assert pubkey.verify_final(sig) == 0
+        
+        # wrong message
+        x509 = X509.load_cert('tests/signer.pem')
+        pubkey = x509.get_pubkey()
+        pubkey.verify_init()
+        pubkey.verify_update('test  message not')
+        assert pubkey.verify_final(sig) == 0
+
+    def test_load_bad(self):
+        self.assertRaises(BIO.BIOError, EVP.load_key,
+                          'thisdoesnotexist-dfgh56789')
+        self.assertRaises(EVP.EVPError, EVP.load_key,
+                          'tests/signer.pem') # not a key
+        self.assertRaises(EVP.EVPError, EVP.load_key_bio,
+                          BIO.MemoryBuffer('no a key'))
+
+    def test_pad(self):
+        self.assertEqual(util.pkcs5_pad('Hello World'),
+                         'Hello World\x05\x05\x05\x05\x05')
+        self.assertEqual(util.pkcs7_pad('Hello World', 15),
+                         'Hello World\x04\x04\x04\x04')
+        self.assertRaises(ValueError, util.pkcs7_pad, 'Hello', 256)
+
+
+class CipherTestCase(unittest.TestCase):
+    def cipher_filter(self, cipher, inf, outf):
+        while 1:
+            buf=inf.read()
+            if not buf:
+                break
+            outf.write(cipher.update(buf))
+        outf.write(cipher.final())
+        return outf.getvalue()
+
+    def try_algo(self, algo):
+        enc = 1
+        dec = 0
+        otxt='against stupidity the gods themselves contend in vain'
+    
+        k=EVP.Cipher(algo, 'goethe','12345678', enc, 1, 'sha1', 'saltsalt', 5)
+        pbuf=cStringIO.StringIO(otxt)
+        cbuf=cStringIO.StringIO()
+        ctxt=self.cipher_filter(k, pbuf, cbuf)
+        pbuf.close()
+        cbuf.close()
+    
+        j=EVP.Cipher(algo, 'goethe','12345678', dec, 1, 'sha1', 'saltsalt', 5)
+        pbuf=cStringIO.StringIO()
+        cbuf=cStringIO.StringIO(ctxt)
+        ptxt=self.cipher_filter(j, cbuf, pbuf)
+        pbuf.close()
+        cbuf.close()
+    
+        assert otxt == ptxt, '%s algorithm cipher test failed' % algo
+        
+    def test_ciphers(self):
+        ciphers=[
+            'des_ede_ecb', 'des_ede_cbc', 'des_ede_cfb', 'des_ede_ofb',
+            'des_ede3_ecb', 'des_ede3_cbc', 'des_ede3_cfb', 'des_ede3_ofb',
+            'aes_128_ecb', 'aes_128_cbc', 'aes_128_cfb', 'aes_128_ofb',
+            'aes_192_ecb', 'aes_192_cbc', 'aes_192_cfb', 'aes_192_ofb',
+            'aes_256_ecb', 'aes_256_cbc', 'aes_256_cfb', 'aes_256_ofb']
+        nonfips_ciphers=['bf_ecb', 'bf_cbc', 'bf_cfb', 'bf_ofb',
+                         #'idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb',
+                         'cast5_ecb', 'cast5_cbc', 'cast5_cfb', 'cast5_ofb',
+                         #'rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb',
+                         'des_ecb', 'des_cbc', 'des_cfb', 'des_ofb',
+                         'rc4', 'rc2_40_cbc']
+        if not fips_mode: # Disabled algorithms
+            ciphers += nonfips_ciphers
+        for i in ciphers:
+            self.try_algo(i)
+
+        # idea might not be compiled in
+        ciphers=['idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb']
+        try:
+            for i in ciphers:
+                self.try_algo(i)
+        except ValueError, e:
+            if str(e) != "('unknown cipher', 'idea_ecb')":
+                raise 
+
+        # rc5 might not be compiled in
+        ciphers=['rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb']
+        try:
+            for i in ciphers:
+                self.try_algo(i)
+        except ValueError, e:
+            if str(e) != "('unknown cipher', 'rc5_ecb')":
+                raise 
+
+        self.assertRaises(ValueError, self.try_algo, 'nosuchalgo4567')
+       
+    def test_AES(self):
+        enc = 1
+        dec = 0
+        tests = [
+            # test vectors from rfc 3602
+            #Case #1: Encrypting 16 bytes (1 block) using AES-CBC with 128-bit key
+            {
+            'KEY': '06a9214036b8a15b512e03d534120006',
+            'IV':  '3dafba429d9eb430b422da802c9fac41',
+            'PT':  'Single block msg',
+            'CT':  'e353779c1079aeb82708942dbe77181a',
+            },
+            
+            #Case #2: Encrypting 32 bytes (2 blocks) using AES-CBC with 128-bit key
+            {
+            'KEY': 'c286696d887c9aa0611bbb3e2025a45a',
+            'IV':  '562e17996d093d28ddb3ba695a2e6f58',
+            'PT':  unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'),
+            'CT':  'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+            },
+            
+            #Case #3: Encrypting 48 bytes (3 blocks) using AES-CBC with 128-bit key
+            {
+            'KEY': '6c3ea0477630ce21a2ce334aa746c2cd',
+            'IV':  'c782dc4c098c66cbd9cd27d825682c81',
+            'PT':  'This is a 48-byte message (exactly 3 AES blocks)',
+            'CT':  'd0a02b3836451753d493665d33f0e8862dea54cdb293abc7506939276772f8d5021c19216bad525c8579695d83ba2684',
+            },
+        ]
+       
+        # Test with padding
+        for test in tests:
+            # encrypt
+            k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=enc)
+            pbuf=cStringIO.StringIO(test['PT'])
+            cbuf=cStringIO.StringIO()
+            ciphertext = hexlify(self.cipher_filter(k, pbuf, cbuf))
+            cipherpadding = ciphertext[len(test['PT']) * 2:]
+            ciphertext = ciphertext[:len(test['PT']) * 2] # Remove the padding from the end
+            pbuf.close()
+            cbuf.close()
+            self.assertEqual(ciphertext, test['CT'])
+
+            # decrypt
+            j=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=dec)
+            pbuf=cStringIO.StringIO()
+            cbuf=cStringIO.StringIO(unhexlify(test['CT'] + cipherpadding))
+            plaintext=self.cipher_filter(j, cbuf, pbuf)
+            pbuf.close()
+            cbuf.close()
+            self.assertEqual(plaintext, test['PT'])
+
+        # Test without padding
+        for test in tests:
+            # encrypt
+            k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=enc, padding=False)
+            pbuf=cStringIO.StringIO(test['PT'])
+            cbuf=cStringIO.StringIO()
+            ciphertext = hexlify(self.cipher_filter(k, pbuf, cbuf))
+            pbuf.close()
+            cbuf.close()
+            self.assertEqual(ciphertext, test['CT'])
+
+            # decrypt
+            j=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=dec, padding=False)
+            pbuf=cStringIO.StringIO()
+            cbuf=cStringIO.StringIO(unhexlify(test['CT']))
+            plaintext=self.cipher_filter(j, cbuf, pbuf)
+            pbuf.close()
+            cbuf.close()
+            self.assertEqual(plaintext, test['PT'])
+
+
+    def test_raises(self):
+        def _cipherFilter(cipher, inf, outf):
+            while 1:
+                buf = inf.read()
+                if not buf:
+                    break
+                outf.write(cipher.update(buf))
+            outf.write(cipher.final())
+            return outf.getvalue()
+
+        def decrypt(ciphertext, key, iv, alg='aes_256_cbc'):
+            cipher = EVP.Cipher(alg=alg, key=key, iv=iv, op=0)
+            pbuf = cStringIO.StringIO()
+            cbuf = cStringIO.StringIO(ciphertext)
+            plaintext = _cipherFilter(cipher, cbuf, pbuf)
+            pbuf.close()
+            cbuf.close()
+            return plaintext
+        
+        self.assertRaises(EVP.EVPError, decrypt,
+                          unhexlify('941d3647a642fab26d9f99a195098b91252c652d07235b9db35758c401627711724637648e45cad0f1121751a1240a4134998cfdf3c4a95c72de2a2444de3f9e40d881d7f205630b0d8ce142fdaebd8d7fbab2aea3dc47f5f29a0e9b55aae59222671d8e2877e1fb5cd8ef1c427027e0'),
+                          unhexlify('5f2cc54067f779f74d3cf1f78c735aec404c8c3a4aaaa02eb1946f595ea4cddb'),
+                          unhexlify('0001efa4bd154ee415b9413a421cedf04359fff945a30e7c115465b1c780a85b65c0e45c'))
+
+        self.assertRaises(EVP.EVPError, decrypt,
+                          unhexlify('a78a510416c1a6f1b48077cc9eeb4287dcf8c5d3179ef80136c18876d774570d'),
+                          unhexlify('5cd148eeaf680d4ff933aed83009cad4110162f53ef89fd44fad09611b0524d4'),
+                          unhexlify(''))
+
+
+class PBKDF2TestCase(unittest.TestCase):
+    def test_rfc3211_test_vectors(self):
+        from binascii import hexlify, unhexlify
+        
+        password = 'password'
+        salt = unhexlify('12 34 56 78 78 56 34 12'.replace(' ', ''))
+        iter = 5
+        keylen = 8
+        ret = EVP.pbkdf2(password, salt, iter, keylen)
+        self.assertEqual(hexlify(ret), 'D1 DA A7 86 15 F2 87 E6'.replace(' ', '').lower())
+        
+        password = 'All n-entities must communicate with other n-entities via n-1 entiteeheehees'
+        salt = unhexlify('12 34 56 78 78 56 34 12'.replace(' ', ''))
+        iter = 500
+        keylen = 16
+        ret = EVP.pbkdf2(password, salt, iter, keylen)
+        self.assertEqual(hexlify(ret), '6A 89 70 BF 68 C9 2C AE A8 4A 8D F2 85 10 85 86'.replace(' ', '').lower())
+        
+
+class HMACTestCase(unittest.TestCase):
+    data1=['', 'More text test vectors to stuff up EBCDIC machines :-)', \
+           h2b("e9139d1e6ee064ef8cf514fc7dc83e86")]
+
+    data2=[h2b('0b'*16), "Hi There", \
+           h2b("9294727a3638bb1c13f48ef8158bfc9d")]
+
+    data3=['Jefe', "what do ya want for nothing?", \
+           h2b("750c783e6ab0b503eaa86e310a5db738")]
+
+    data4=[h2b('aa'*16), h2b('dd'*50), \
+           h2b("0x56be34521d144c88dbb8c733f0e8b3f6")]
+
+    data=[data1, data2, data3, data4]
+
+    def test_simple(self):
+        algo = 'md5'
+        for d in self.data:
+            h = EVP.HMAC(d[0], algo)
+            h.update(d[1])
+            ret = h.final()
+            self.assertEqual(ret, d[2])
+        self.assertRaises(ValueError, EVP.HMAC, d[0], algo='nosuchalgo')
+
+    def make_chain_HMAC(self, key, start, input, algo='sha1'):
+        chain = []
+        hmac = EVP.HMAC(key, algo)
+        hmac.update(`start`)
+        digest = hmac.final()
+        chain.append((digest, start))
+        for i in input:
+            hmac.reset(digest)
+            hmac.update(`i`)
+            digest = hmac.final()
+            chain.append((digest, i))
+        return chain
+    
+    def make_chain_hmac(self, key, start, input, algo='sha1'):
+        from M2Crypto.EVP import hmac
+        chain = []
+        digest = hmac(key, `start`, algo)
+        chain.append((digest, start))
+        for i in input:
+            digest = hmac(digest, `i`, algo)
+            chain.append((digest, i))
+        return chain
+    
+    def verify_chain_hmac(self, key, start, chain, algo='sha1'):
+        from M2Crypto.EVP import hmac
+        digest = hmac(key, `start`, algo)
+        c = chain[0]
+        if c[0] != digest or c[1] != start:
+            return 0
+        for d, v in chain[1:]:
+            digest = hmac(digest, `v`, algo)
+            if digest != d:
+                return 0
+        return 1
+    
+    def verify_chain_HMAC(self, key, start, chain, algo='sha1'):
+        hmac = EVP.HMAC(key, algo)
+        hmac.update(`start`)
+        digest = hmac.final()
+        c = chain[0]
+        if c[0] != digest or c[1] != start:
+            return 0
+        for d, v in chain[1:]:
+            hmac.reset(digest)
+            hmac.update(`v`)
+            digest = hmac.final()
+            if digest != d:
+                return 0
+        return 1
+    
+    def test_complicated(self):
+        make_chain = self.make_chain_hmac
+        verify_chain = self.verify_chain_hmac
+        key = 'numero uno'
+        start = 'zeroth item'
+        input = ['first item', 'go go go', 'fly fly fly']
+        chain = make_chain(key, start, input)
+        self.assertEquals(verify_chain('some key', start, chain), 0)
+        self.assertEquals(verify_chain(key, start, chain), 1)
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(EVPTestCase))
+    suite.addTest(unittest.makeSuite(CipherTestCase))
+    suite.addTest(unittest.makeSuite(PBKDF2TestCase))
+    suite.addTest(unittest.makeSuite(HMACTestCase))
+    return suite    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_obj.py b/tests/test_obj.py
new file mode 100644 (file)
index 0000000..9144135
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.m2 obj_* functions.
+"""
+
+import unittest
+from M2Crypto import X509, ASN1, BIO, Rand, m2
+
+"""
+These functions must be cleaned up and moved to some python module
+Taken from CA managment code
+"""
+
+def x509_name2list(name):
+    for i in range(0, name.entry_count()):
+        yield X509.X509_Name_Entry(m2.x509_name_get_entry(name._ptr(), i), _pyfree = 0)
+
+def x509_name_entry2tuple(entry):
+    bio = BIO.MemoryBuffer()
+    m2.asn1_string_print(bio._ptr(), m2.x509_name_entry_get_data(entry._ptr()))
+    return (m2.obj_obj2txt(m2.x509_name_entry_get_object(entry._ptr()), 0), bio.getvalue())
+
+def tuple2x509_name_entry(tup):
+    obj, data = tup
+    _x509_ne = m2.x509_name_entry_create_by_txt(None, obj, ASN1.MBSTRING_ASC, data, len(data))
+    if not _x509_ne:
+        raise ValueError("Invalid object indentifier: %s" % obj)
+    return X509.X509_Name_Entry(_x509_ne, _pyfree = 1) # Prevent memory leaks
+
+class ObjectsTestCase(unittest.TestCase):
+
+    def callback(self, *args):
+        pass
+
+    def test_obj2txt(self):
+        assert m2.obj_obj2txt(m2.obj_txt2obj("commonName", 0), 1) == "2.5.4.3", "2.5.4.3"
+        assert m2.obj_obj2txt(m2.obj_txt2obj("commonName", 0), 0) == "commonName", "commonName"
+
+    def test_nid(self):
+        assert m2.obj_ln2nid("commonName") == m2.obj_txt2nid("2.5.4.3"), "ln2nid and txt2nid mismatch"
+        assert m2.obj_ln2nid("CN") == 0, "ln2nid on sn"
+        assert m2.obj_sn2nid("CN") == m2.obj_ln2nid("commonName"), "ln2nid and sn2nid mismatch"
+        assert m2.obj_sn2nid("CN") == m2.obj_obj2nid(m2.obj_txt2obj("CN", 0)), "obj2nid"
+        assert m2.obj_txt2nid("__unknown") == 0, "__unknown"
+                              
+    def test_tuple2tuple(self):
+        tup = ("CN", "someCommonName")
+        tup1 = x509_name_entry2tuple(tuple2x509_name_entry(tup))
+        assert tup1[1] == tup[1], tup1 # tup1[0] is 'commonName', not 'CN'
+        assert x509_name_entry2tuple(tuple2x509_name_entry(tup1)) == tup1, tup1
+
+    def test_unknown(self):
+        self.assertRaises(ValueError, tuple2x509_name_entry, ("__unknown", "_"))
+        
+    def test_x509_name(self):
+        n = X509.X509_Name()
+        n.C = 'US' # It seems this actually needs to be a real 2 letter country code
+        n.SP = 'State or Province'
+        n.L = 'locality name'
+        n.O = 'orhanization name'
+        n.OU = 'org unit'
+        n.CN = 'common name'
+        n.Email = 'bob@example.com'
+        n.serialNumber = '1234'
+        n.SN = 'surname'
+        n.GN = 'given name'
+        
+        n.givenName = 'name given'
+        assert len(n) == 11, len(n)
+
+        tl = map(x509_name_entry2tuple, x509_name2list(n))
+
+        assert len(tl) == len(n), len(tl)
+
+        x509_n = m2.x509_name_new()
+        for o in map(tuple2x509_name_entry, tl):
+            m2.x509_name_add_entry(x509_n, o._ptr(), -1, 0)
+            o._pyfree = 0 # Take care of underlying object
+        n1 = X509.X509_Name(x509_n)
+
+        assert n.as_text() == n1.as_text(), n1.as_text()
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ObjectsTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
diff --git a/tests/test_pgp.py b/tests/test_pgp.py
new file mode 100644 (file)
index 0000000..c86d153
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+"""PGP test program.
+
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+from M2Crypto import EVP, PGP
+from cStringIO import StringIO
+
+
+class PGPTestCase(unittest.TestCase):
+
+    def test_simple(self):
+        pkr = PGP.load_pubring('tests/pubring.pgp')
+        daft = pkr['daft']
+        daft_pkt = daft._pubkey_pkt.pack()
+        s1 = EVP.MessageDigest('sha1')
+        s1.update(daft_pkt)
+        s1f = `s1.final()`
+    
+        buf = StringIO(daft_pkt)
+        ps = PGP.packet_stream(buf)
+        dift_pkt = ps.read()
+        s2 = EVP.MessageDigest('sha1')
+        s2.update(dift_pkt.pack())
+        s2f = `s2.final()`
+
+        self.assertEqual(s1f, s2f)
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(PGPTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
diff --git a/tests/test_rand.py b/tests/test_rand.py
new file mode 100644 (file)
index 0000000..3e4d65d
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.Rand.
+
+Copyright (C) 2006 Open Source Applications Foundation (OSAF). All Rights Reserved.
+"""
+
+import unittest
+import os, sys
+from M2Crypto import Rand
+
+class RandTestCase(unittest.TestCase):
+    def test_bytes(self):
+        self.assertRaises(MemoryError, Rand.rand_bytes, -1)
+        assert Rand.rand_bytes(0) == ''
+        assert len(Rand.rand_bytes(1)) == 1
+        
+    def test_pseudo_bytes(self):
+        self.assertRaises(MemoryError, Rand.rand_pseudo_bytes, -1)
+        assert Rand.rand_pseudo_bytes(0) == ('', 1)
+        a, b = Rand.rand_pseudo_bytes(1)
+        assert len(a) == 1
+        assert b == 1
+        
+    def test_load_save(self):
+        try:
+            os.remove('tests/randpool.dat')
+        except OSError:
+            pass
+        assert Rand.load_file('tests/randpool.dat', -1) == 0
+        assert Rand.save_file('tests/randpool.dat') == 1024
+        assert Rand.load_file('tests/randpool.dat', -1) == 1024
+        
+    def test_seed_add(self):
+        if sys.version_info >= (2, 4):
+            assert Rand.rand_seed(os.urandom(1024)) is None
+            
+            # XXX Should there be limits on the entropy parameter?
+            assert Rand.rand_add(os.urandom(2), 0.5) is None
+            Rand.rand_add(os.urandom(2), -0.5)
+            Rand.rand_add(os.urandom(2), 5000.0)
+
+        
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(RandTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
diff --git a/tests/test_rc4.py b/tests/test_rc4.py
new file mode 100644 (file)
index 0000000..5a2d239
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.RC4.
+
+Copyright (c) 2009 Heikki Toivonen. All rights reserved."""
+
+import unittest
+from binascii import hexlify
+from M2Crypto import RC4
+
+class RC4TestCase(unittest.TestCase):
+
+    def test_vectors(self):
+        """
+        Test with test vectors from Wikipedia: http://en.wikipedia.org/wiki/Rc4
+        """
+        vectors = (('Key', 'Plaintext', 'BBF316E8D940AF0AD3'),
+                   ('Wiki', 'pedia', '1021BF0420'),
+                   ('Secret', 'Attack at dawn', '45A01F645FC35B383552544B9BF5'))
+        
+        rc4 = RC4.RC4()
+        for key, plaintext, ciphertext in vectors:
+            rc4.set_key(key)
+            self.assertEqual(hexlify(rc4.update(plaintext)).upper(), ciphertext)
+
+        self.assertEqual(rc4.final(), '')
+    
+    def test_bad(self):
+        rc4 = RC4.RC4('foo')
+        self.assertNotEqual(hexlify(rc4.update('bar')).upper(), '45678')
+        
+        
+def suite():
+    return unittest.makeSuite(RC4TestCase)
+    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_rsa.py b/tests/test_rsa.py
new file mode 100644 (file)
index 0000000..0bb986b
--- /dev/null
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.RSA.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import sha, md5, os, sys
+from M2Crypto import RSA, BIO, Rand, m2, EVP, X509
+
+class RSATestCase(unittest.TestCase):
+
+    errkey = 'tests/dsa.priv.pem'
+    privkey = 'tests/rsa.priv.pem'
+    privkey2 = 'tests/rsa.priv2.pem'
+    pubkey = 'tests/rsa.pub.pem'
+
+    data = sha.sha('The magic words are squeamish ossifrage.').digest()
+
+    e_padding_ok = ('pkcs1_padding', 'pkcs1_oaep_padding')
+
+    s_padding_ok = ('pkcs1_padding',)
+    s_padding_nok = ('no_padding', 'sslv23_padding', 'pkcs1_oaep_padding')
+
+    def gen_callback(self, *args):
+        pass
+
+    def gen2_callback(self):
+        pass
+
+    def pp_callback(self, *args):
+        # The passphrase for rsa.priv2.pem is 'qwerty'.
+        return 'qwerty'
+
+    def pp2_callback(self, *args):
+        # Misbehaving passphrase callback.
+        pass
+
+    def test_loadkey_junk(self):
+        self.assertRaises(RSA.RSAError, RSA.load_key, self.errkey)
+
+    def test_loadkey_pp(self):
+        rsa = RSA.load_key(self.privkey2, self.pp_callback)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        assert rsa.check_key() == 1
+
+    def test_loadkey_pp_bad_cb(self):
+        self.assertRaises(RSA.RSAError, RSA.load_key, self.privkey2, self.pp2_callback)
+
+    def test_loadkey(self):
+        rsa = RSA.load_key(self.privkey)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        self.assertEqual(rsa.n, "\x00\x00\x00\x81\x00\xcde!\x15\xdah\xb5`\xce[\xd6\x17d\xba8\xc1I\xb1\xf1\xber\x86K\xc7\xda\xb3\x98\xd6\xf6\x80\xae\xaa\x8f!\x9a\xefQ\xdeh\xbb\xc5\x99\x01o\xebGO\x8e\x9b\x9a\x18\xfb6\xba\x12\xfc\xf2\x17\r$\x00\xa1\x1a \xfc/\x13iUm\x04\x13\x0f\x91D~\xbf\x08\x19C\x1a\xe2\xa3\x91&\x8f\xcf\xcc\xf3\xa4HRf\xaf\xf2\x19\xbd\x05\xe36\x9a\xbbQ\xc86|(\xad\x83\xf2Eu\xb2EL\xdf\xa4@\x7f\xeel|\xfcU\x03\xdb\x89'")
+        self.assertRaises(AttributeError, getattr, rsa, 'nosuchprop')
+        assert rsa.check_key() == 1
+
+    def test_loadkey_bio(self):
+        keybio = BIO.MemoryBuffer(open(self.privkey).read()) 
+        rsa = RSA.load_key_bio(keybio)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        assert rsa.check_key() == 1
+
+    def test_keygen(self):
+        rsa = RSA.gen_key(1024, 65537, self.gen_callback)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        assert rsa.check_key() == 1
+
+    def test_keygen_bad_cb(self):
+        rsa = RSA.gen_key(1024, 65537, self.gen2_callback)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        assert rsa.check_key() == 1
+
+    def test_private_encrypt(self):
+        priv = RSA.load_key(self.privkey)
+        # pkcs1_padding
+        for padding in self.s_padding_ok:
+            p = getattr(RSA, padding)
+            ctxt = priv.private_encrypt(self.data, p)
+            ptxt = priv.public_decrypt(ctxt, p)
+            assert ptxt == self.data
+        # The other paddings.
+        for padding in self.s_padding_nok:
+            p = getattr(RSA, padding)
+            self.assertRaises(RSA.RSAError, priv.private_encrypt, self.data, p)
+        # Type-check the data to be encrypted.
+        self.assertRaises(TypeError, priv.private_encrypt, self.gen_callback, RSA.pkcs1_padding)
+
+    def test_public_encrypt(self):
+        priv = RSA.load_key(self.privkey)
+        # pkcs1_padding, pkcs1_oaep_padding
+        for padding in self.e_padding_ok:
+            p = getattr(RSA, padding)
+            ctxt = priv.public_encrypt(self.data, p)
+            ptxt = priv.private_decrypt(ctxt, p)
+            assert ptxt == self.data
+        # sslv23_padding
+        ctxt = priv.public_encrypt(self.data, RSA.sslv23_padding)
+        self.assertRaises(RSA.RSAError, priv.private_decrypt, ctxt, RSA.sslv23_padding)
+        # no_padding
+        self.assertRaises(RSA.RSAError, priv.public_encrypt, self.data, RSA.no_padding)
+        # Type-check the data to be encrypted.
+        self.assertRaises(TypeError, priv.public_encrypt, self.gen_callback, RSA.pkcs1_padding)
+
+    def test_x509_public_encrypt(self):
+        x509 = X509.load_cert("tests/recipient.pem")
+        rsa = x509.get_pubkey().get_rsa()
+        rsa.public_encrypt("data", RSA.pkcs1_padding)
+        
+    def test_loadpub(self):
+        rsa = RSA.load_pub_key(self.pubkey)
+        assert len(rsa) == 1024
+        assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        self.assertRaises(RSA.RSAError, setattr, rsa, 'e', '\000\000\000\003\001\000\001')
+        self.assertRaises(RSA.RSAError, rsa.private_encrypt, 1)
+        self.assertRaises(RSA.RSAError, rsa.private_decrypt, 1)
+        assert rsa.check_key()
+
+    def test_loadpub_bad(self):
+        self.assertRaises(RSA.RSAError, RSA.load_pub_key, self.errkey)
+
+    def test_savepub(self):
+        rsa = RSA.load_pub_key(self.pubkey)
+        assert rsa.as_pem() # calls save_key_bio
+        f = 'tests/rsa_test.pub'
+        try:
+            self.assertEquals(rsa.save_key(f), 1)
+        finally:
+            try:
+                os.remove(f)
+            except IOError:
+                pass
+
+    def test_set_bn(self):
+        rsa = RSA.load_pub_key(self.pubkey)
+        assert m2.rsa_set_e(rsa.rsa, '\000\000\000\003\001\000\001') is None
+        self.assertRaises(RSA.RSAError, m2.rsa_set_e, rsa.rsa, '\000\000\000\003\001')
+
+    def test_newpub(self):
+        old = RSA.load_pub_key(self.pubkey)
+        new = RSA.new_pub_key(old.pub())
+        assert new.check_key()
+        assert len(new) == 1024
+        assert new.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+        
+    def test_sign_and_verify(self):
+        """
+        Testing signing and verifying digests
+        """
+        algos = {'sha1':'', 
+                 'ripemd160':'',
+                 'md5':''}
+
+        if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+            algos['sha224'] = ''
+            algos['sha256'] = ''
+            algos['sha384'] = '' 
+            algos['sha512'] = '' 
+
+        message = "This is the message string"
+        digest = sha.sha(message).digest()
+        rsa = RSA.load_key(self.privkey)
+        rsa2 = RSA.load_pub_key(self.pubkey)
+        for algo in algos.keys():
+            signature = rsa.sign(digest, algo)
+            #assert signature == algos[algo], 'mismatched signature with algorithm %s: signature=%s' % (algo, signature)
+            verify = rsa2.verify(digest, signature, algo) 
+            assert verify == 1, 'verification failed with algorithm %s' % algo
+    
+    if m2.OPENSSL_VERSION_NUMBER >= 0x90708F:
+        def test_sign_and_verify_rsassa_pss(self):
+            """
+            Testing signing and verifying using rsassa_pss
+    
+            The maximum size of the salt has to decrease as the
+            size of the digest increases because of the size of 
+            our test key limits it.
+            """
+            message = "This is the message string"
+            if sys.version_info < (2, 5):
+                algos = {'sha1': (43, sha.sha(message).digest()), 
+                         'md5': (47, md5.md5(message).digest())}
+        
+            else:
+                import hashlib
+                algos = {'sha1': 43, 
+                         'ripemd160': 43,
+                         'md5': 47}
+        
+                if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+                    algos['sha224'] = 35
+                    algos['sha256'] = 31
+                    algos['sha384'] = 15
+                    algos['sha512'] = 0 
+    
+                for algo, salt_max in algos.iteritems():
+                    h = hashlib.new(algo)
+                    h.update(message)
+                    digest = h.digest()
+                    algos[algo] = (salt_max, digest) 
+    
+            rsa = RSA.load_key(self.privkey)
+            rsa2 = RSA.load_pub_key(self.pubkey)
+            for algo, (salt_max, digest) in algos.iteritems():
+                for salt_length in range(0, salt_max):
+                    signature = rsa.sign_rsassa_pss(digest, algo, salt_length)
+                    verify = rsa2.verify_rsassa_pss(digest, signature, algo, salt_length)
+                    assert verify == 1, 'verification failed with algorithm %s salt length %d' % (algo, salt_length)
+
+    def test_sign_bad_method(self):
+        """
+        Testing calling sign with an unsupported message digest algorithm
+        """
+        rsa = RSA.load_key(self.privkey)
+        message = "This is the message string"
+        digest = md5.md5(message).digest() 
+        self.assertRaises(ValueError, rsa.sign, 
+                          digest, 'bad_digest_method') 
+    
+    def test_verify_bad_method(self):
+        """
+        Testing calling verify with an unsupported message digest algorithm
+        """
+        rsa = RSA.load_key(self.privkey)
+        message = "This is the message string"
+        digest = md5.md5(message).digest() 
+        signature = rsa.sign(digest, 'sha1')
+        self.assertRaises(ValueError, rsa.verify,
+                          digest, signature, 'bad_digest_method') 
+
+    def test_verify_mismatched_algo(self):
+        """
+        Testing verify to make sure it fails when we use a different
+        message digest algorithm
+        """
+        rsa = RSA.load_key(self.privkey)
+        message = "This is the message string"
+        digest = sha.sha(message).digest() 
+        signature = rsa.sign(digest, 'sha1')
+        rsa2 = RSA.load_pub_key(self.pubkey)
+        self.assertRaises(RSA.RSAError, rsa.verify, 
+                          digest, signature, 'md5')
+    
+    def test_sign_fail(self):
+        """
+        Testing sign to make sure it fails when I give it
+        a bogus digest. Looking at the RSA sign method
+        I discovered that with the digest methods we use
+        it has to be longer than a certain length.
+        """
+        rsa = RSA.load_key(self.privkey)
+        digest = """This string should be long enough to warrant an error in
+        RSA_sign""" * 2
+         
+        self.assertRaises(RSA.RSAError, rsa.sign, digest)
+    
+    def test_verify_bad_signature(self):
+        """
+        Testing verify to make sure it fails when we use a bad signature
+        """
+        rsa = RSA.load_key(self.privkey)
+        message = "This is the message string"
+        digest = sha.sha(message).digest() 
+
+        otherMessage = "Abracadabra"
+        otherDigest = sha.sha(otherMessage).digest() 
+        otherSignature = rsa.sign(otherDigest)
+
+        self.assertRaises(RSA.RSAError, rsa.verify, 
+                          digest, otherSignature)
+    
+        
+def suite():
+    return unittest.makeSuite(RSATestCase)
+    
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1) 
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_smime.py b/tests/test_smime.py
new file mode 100644 (file)
index 0000000..f18c9db
--- /dev/null
@@ -0,0 +1,262 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SMIME.
+
+Copyright (C) 2006 Open Source Applications Foundation. All Rights Reserved.
+"""
+
+import unittest
+from M2Crypto import SMIME, BIO, Rand, X509, EVP
+
+class SMIMETestCase(unittest.TestCase):
+    cleartext = 'some text to manipulate'
+
+    def setUp(self):
+        # XXX Ugly, but not sure what would be better
+        self.signed = self.test_sign()
+        self.encrypted = self.test_encrypt()
+    
+    def test_load_bad(self):
+        s = SMIME.SMIME()
+        self.assertRaises(EVP.EVPError, s.load_key,
+                          'tests/signer.pem',
+                          'tests/signer.pem')
+
+        self.assertRaises(BIO.BIOError, SMIME.load_pkcs7, 'nosuchfile-dfg456')
+        self.assertRaises(SMIME.PKCS7_Error, SMIME.load_pkcs7, 'tests/signer.pem')
+        self.assertRaises(SMIME.PKCS7_Error, SMIME.load_pkcs7_bio, BIO.MemoryBuffer('no pkcs7'))
+
+        self.assertRaises(SMIME.SMIME_Error, SMIME.smime_load_pkcs7, 'tests/signer.pem')
+        self.assertRaises(SMIME.SMIME_Error, SMIME.smime_load_pkcs7_bio, BIO.MemoryBuffer('no pkcs7'))
+
+    def test_crlf(self):
+        self.assertEqual(SMIME.text_crlf('foobar'), 'Content-Type: text/plain\r\n\r\nfoobar')
+        self.assertEqual(SMIME.text_crlf_bio(BIO.MemoryBuffer('foobar')).read(), 'Content-Type: text/plain\r\n\r\nfoobar')
+        
+    def test_sign(self):
+        buf = BIO.MemoryBuffer(self.cleartext)
+        s = SMIME.SMIME()
+        s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+        p7 = s.sign(buf, SMIME.PKCS7_DETACHED)
+        assert len(buf) == 0
+        assert p7.type() == SMIME.PKCS7_SIGNED, p7.type()
+        assert isinstance(p7, SMIME.PKCS7), p7
+        out = BIO.MemoryBuffer()
+        p7.write(out)
+        
+        buf = out.read()
+        
+        assert buf[:len('-----BEGIN PKCS7-----')] == '-----BEGIN PKCS7-----'
+        buf = buf.strip()
+        assert buf[-len('-----END PKCS7-----'):] == '-----END PKCS7-----', buf[-len('-----END PKCS7-----'):]
+        assert len(buf) > len('-----END PKCS7-----') + len('-----BEGIN PKCS7-----')
+        
+        s.write(out, p7, BIO.MemoryBuffer(self.cleartext))
+        return out
+
+    def test_store_load_info(self):        
+        st = X509.X509_Store()
+        self.assertRaises(X509.X509Error, st.load_info, 'tests/ca.pem-typoname')
+        self.assertEqual(st.load_info('tests/ca.pem'), 1) 
+
+    def test_verify(self):
+        s = SMIME.SMIME()
+        
+        x509 = X509.load_cert('tests/signer.pem')
+        sk = X509.X509_Stack()
+        sk.push(x509)
+        s.set_x509_stack(sk)
+        
+        st = X509.X509_Store()
+        st.load_info('tests/ca.pem')
+        s.set_x509_store(st)
+        
+        p7, data = SMIME.smime_load_pkcs7_bio(self.signed)
+        
+        assert isinstance(p7, SMIME.PKCS7), p7
+        v = s.verify(p7, data)
+        assert v == self.cleartext
+    
+        t = p7.get0_signers(sk)
+        assert len(t) == 1
+        assert t[0].as_pem() == x509.as_pem(), t[0].as_text()
+
+    def test_verifyBad(self):
+        s = SMIME.SMIME()
+        
+        x509 = X509.load_cert('tests/recipient.pem')
+        sk = X509.X509_Stack()
+        sk.push(x509)
+        s.set_x509_stack(sk)
+        
+        st = X509.X509_Store()
+        st.load_info('tests/recipient.pem')
+        s.set_x509_store(st)
+        
+        p7, data = SMIME.smime_load_pkcs7_bio(self.signed)
+        assert isinstance(p7, SMIME.PKCS7), p7
+        self.assertRaises(SMIME.PKCS7_Error, s.verify, p7) # Bad signer
+
+    def test_encrypt(self):
+        buf = BIO.MemoryBuffer(self.cleartext)
+        s = SMIME.SMIME()
+
+        x509 = X509.load_cert('tests/recipient.pem')
+        sk = X509.X509_Stack()
+        sk.push(x509)
+        s.set_x509_stack(sk)
+
+        self.assertRaises(ValueError, SMIME.Cipher, 'nosuchcipher')
+
+        s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+        p7 = s.encrypt(buf)
+        
+        assert len(buf) == 0
+        assert p7.type() == SMIME.PKCS7_ENVELOPED, p7.type()
+        assert isinstance(p7, SMIME.PKCS7), p7
+        out = BIO.MemoryBuffer()
+        p7.write(out)
+    
+        buf = out.read()
+        
+        assert buf[:len('-----BEGIN PKCS7-----')] == '-----BEGIN PKCS7-----'
+        buf = buf.strip()
+        assert buf[-len('-----END PKCS7-----'):] == '-----END PKCS7-----'
+        assert len(buf) > len('-----END PKCS7-----') + len('-----BEGIN PKCS7-----')
+        
+        s.write(out, p7)
+        return out
+    
+    def test_decrypt(self):
+        s = SMIME.SMIME()
+
+        s.load_key('tests/recipient_key.pem', 'tests/recipient.pem')
+        
+        p7, data = SMIME.smime_load_pkcs7_bio(self.encrypted)
+        assert isinstance(p7, SMIME.PKCS7), p7
+        self.assertRaises(SMIME.SMIME_Error, s.verify, p7) # No signer
+        
+        out = s.decrypt(p7)
+        assert out == self.cleartext
+
+    def test_decryptBad(self):
+        s = SMIME.SMIME()
+
+        s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+        
+        p7, data = SMIME.smime_load_pkcs7_bio(self.encrypted)
+        assert isinstance(p7, SMIME.PKCS7), p7
+        self.assertRaises(SMIME.SMIME_Error, s.verify, p7) # No signer
+
+        # Cannot decrypt: no recipient matches certificate
+        self.assertRaises(SMIME.PKCS7_Error, s.decrypt, p7)
+
+    def test_signEncryptDecryptVerify(self):
+        # sign
+        buf = BIO.MemoryBuffer(self.cleartext)
+        s = SMIME.SMIME()    
+        s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+        p7 = s.sign(buf)
+        
+        # encrypt
+        x509 = X509.load_cert('tests/recipient.pem')
+        sk = X509.X509_Stack()
+        sk.push(x509)
+        s.set_x509_stack(sk)
+    
+        s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+        
+        tmp = BIO.MemoryBuffer()
+        s.write(tmp, p7)
+
+        p7 = s.encrypt(tmp)
+        
+        signedEncrypted = BIO.MemoryBuffer()
+        s.write(signedEncrypted, p7)
+
+        # decrypt
+        s = SMIME.SMIME()
+    
+        s.load_key('tests/recipient_key.pem', 'tests/recipient.pem')
+        
+        p7, data = SMIME.smime_load_pkcs7_bio(signedEncrypted)
+        
+        out = s.decrypt(p7)
+        
+        # verify
+        x509 = X509.load_cert('tests/signer.pem')
+        sk = X509.X509_Stack()
+        sk.push(x509)
+        s.set_x509_stack(sk)
+        
+        st = X509.X509_Store()
+        st.load_info('tests/ca.pem')
+        s.set_x509_store(st)
+        
+        p7_bio = BIO.MemoryBuffer(out)
+        p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+        v = s.verify(p7)
+        assert v == self.cleartext
+
+
+class WriteLoadTestCase(unittest.TestCase):
+    def setUp(self):
+        s = SMIME.SMIME()
+        s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+        p7 = s.sign(BIO.MemoryBuffer('some text'))
+        self.filename = 'tests/sig.p7'
+        f = BIO.openfile(self.filename, 'wb')
+        assert p7.write(f) == 1
+        f.close()
+
+        p7 = s.sign(BIO.MemoryBuffer('some text'), SMIME.PKCS7_DETACHED)
+        self.filenameSmime = 'tests/sig.p7s'
+        f = BIO.openfile(self.filenameSmime, 'wb')
+        assert s.write(f, p7, BIO.MemoryBuffer('some text')) == 1
+        f.close()
+        
+    def test_write_pkcs7_der(self):
+        buf = BIO.MemoryBuffer()
+        assert SMIME.load_pkcs7(self.filename).write_der(buf) == 1
+        s = buf.read()
+        assert len(s) in (1204, 1243), len(s)
+        
+    def test_load_pkcs7(self):
+        assert SMIME.load_pkcs7(self.filename).type() == SMIME.PKCS7_SIGNED
+    
+    def test_load_pkcs7_bio(self):
+        f = open(self.filename, 'rb')
+        buf = BIO.MemoryBuffer(f.read())
+        f.close()
+        
+        assert SMIME.load_pkcs7_bio(buf).type() == SMIME.PKCS7_SIGNED
+
+    def test_load_smime(self):
+        a, b = SMIME.smime_load_pkcs7(self.filenameSmime)
+        assert isinstance(a, SMIME.PKCS7), a
+        assert isinstance(b, BIO.BIO), b
+        assert a.type() == SMIME.PKCS7_SIGNED
+        
+    def test_load_smime_bio(self):
+        f = open(self.filenameSmime, 'rb')
+        buf = BIO.MemoryBuffer(f.read())
+        f.close()
+
+        a, b = SMIME.smime_load_pkcs7_bio(buf)
+        assert isinstance(a, SMIME.PKCS7), a
+        assert isinstance(b, BIO.BIO), b
+        assert a.type() == SMIME.PKCS7_SIGNED
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(SMIMETestCase))
+    suite.addTest(unittest.makeSuite(WriteLoadTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
new file mode 100644 (file)
index 0000000..701484d
--- /dev/null
@@ -0,0 +1,1108 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SSL.
+
+Copyright (c) 2000-2004 Ng Pheng Siong. All rights reserved.
+
+Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+"""
+
+"""
+TODO
+
+Server tests:
+- ???
+
+Others:
+- ssl_dispatcher
+- SSLServer
+- ForkingSSLServer
+- ThreadingSSLServer
+"""
+
+import os, socket, string, sys, tempfile, thread, time, unittest
+from M2Crypto import Rand, SSL, m2, Err
+
+from fips import fips_mode
+
+srv_host = 'localhost'
+srv_port = 64000
+
+def verify_cb_new_function(ok, store):
+    try:
+        assert not ok
+        err = store.get_error()
+        assert err in [m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+                       m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+                       m2.X509_V_ERR_CERT_UNTRUSTED,
+                       m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE]
+        assert store.get_error_depth() == 0 
+        app_data = m2.x509_store_ctx_get_app_data(store.ctx)
+        assert app_data
+        x509 = store.get_current_cert()
+        assert x509
+        stack = store.get1_chain()
+        assert len(stack) == 1
+        assert stack[0].as_pem() == x509.as_pem()
+    except AssertionError, e:
+        # If we let exceptions propagate from here the
+        # caller may see strange errors. This is cleaner.
+        return 0   
+    return 1
+
+class VerifyCB:
+    def __call__(self, ok, store):
+        return verify_cb_new_function(ok, store)
+
+sleepTime = float(os.getenv('M2CRYPTO_TEST_SSL_SLEEP', 0.5))
+
+def find_openssl():
+    if os.name == 'nt' or sys.platform == 'cygwin':
+        openssl = 'openssl.exe'
+    else:
+        openssl = 'openssl'
+
+    plist = os.environ['PATH'].split(os.pathsep)
+    for p in plist:
+        try:
+            dir = os.listdir(p)
+            if openssl in dir:
+                return True
+        except:
+            pass
+    return False
+
+class BaseSSLClientTestCase(unittest.TestCase):
+
+    openssl_in_path = find_openssl()
+
+    def start_server(self, args):
+        if not self.openssl_in_path:
+            raise Exception('openssl command not in PATH')
+        
+        pid = os.fork()
+        if pid == 0:
+            # openssl must be started in the tests directory for it
+            # to find the .pem files
+            os.chdir('tests')
+            try:
+                os.execvp('openssl', args)
+            finally:
+                os.chdir('..')
+                
+        else:
+            time.sleep(sleepTime)
+            return pid
+
+    def stop_server(self, pid):
+        os.kill(pid, 1)
+        os.waitpid(pid, 0)
+
+    def http_get(self, s):
+        s.send('GET / HTTP/1.0\n\n') 
+        resp = ''
+        while 1:
+            try:
+                r = s.recv(4096)
+                if not r:
+                    break
+            except SSL.SSLError: # s_server throws an 'unexpected eof'...
+                break
+            resp = resp + r 
+        return resp
+
+    def setUp(self):
+        self.srv_host = srv_host
+        self.srv_port = srv_port
+        self.srv_addr = (srv_host, srv_port)
+        self.srv_url = 'https://%s:%s/' % (srv_host, srv_port)
+        self.args = ['s_server', '-quiet', '-www',
+                     #'-cert', 'server.pem', Implicitly using this
+                     '-accept', str(self.srv_port)]
+
+    def tearDown(self):
+        global srv_port
+        srv_port = srv_port - 1
+
+
+class PassSSLClientTestCase(BaseSSLClientTestCase):
+        
+    def test_pass(self):
+        pass
+
+class HttpslibSSLClientTestCase(BaseSSLClientTestCase):
+
+    def test_HTTPSConnection(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            c = httpslib.HTTPSConnection(srv_host, srv_port)
+            c.request('GET', '/')
+            data = c.getresponse().read()
+            c.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_HTTPSConnection_resume_session(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            ctx = SSL.Context()
+            ctx.load_verify_locations(cafile='tests/ca.pem')
+            ctx.load_cert('tests/x509.pem')
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 1)
+            ctx.set_session_cache_mode(m2.SSL_SESS_CACHE_CLIENT)
+            c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+            c.request('GET', '/')
+            ses = c.get_session()
+            t = ses.as_text()
+            data = c.getresponse().read()
+            # Appearently closing connection here screws session; Ali Polatel?
+            # c.close()
+            
+            ctx2 = SSL.Context()
+            ctx2.load_verify_locations(cafile='tests/ca.pem')
+            ctx2.load_cert('tests/x509.pem')
+            ctx2.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 1)
+            ctx2.set_session_cache_mode(m2.SSL_SESS_CACHE_CLIENT)
+            c2 = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx2)
+            c2.set_session(ses)
+            c2.request('GET', '/')
+            ses2 = c2.get_session()
+            t2 = ses2.as_text()
+            data = c2.getresponse().read()
+            c.close()
+            c2.close()
+            assert t == t2, "Sessions did not match"
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_HTTPSConnection_secure_context(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+            c.request('GET', '/')
+            data = c.getresponse().read()
+            c.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_HTTPSConnection_secure_context_fail(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/server.pem')
+            c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+            self.assertRaises(SSL.SSLError, c.request, 'GET', '/')
+            c.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_HTTPS(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            c = httpslib.HTTPS(srv_host, srv_port)
+            c.putrequest('GET', '/')
+            c.putheader('Accept', 'text/html')
+            c.putheader('Accept', 'text/plain')
+            c.endheaders()
+            err, msg, headers = c.getreply()
+            assert err == 200, err
+            f = c.getfile()
+            data = f.read()
+            c.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_HTTPS_secure_context(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            c = httpslib.HTTPS(srv_host, srv_port, ssl_context=ctx)
+            c.putrequest('GET', '/')
+            c.putheader('Accept', 'text/html')
+            c.putheader('Accept', 'text/plain')
+            c.endheaders()
+            err, msg, headers = c.getreply()
+            assert err == 200, err
+            f = c.getfile()
+            data = f.read()
+            c.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_HTTPS_secure_context_fail(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import httpslib
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/server.pem')
+            c = httpslib.HTTPS(srv_host, srv_port, ssl_context=ctx)
+            c.putrequest('GET', '/')
+            c.putheader('Accept', 'text/html')
+            c.putheader('Accept', 'text/plain')
+            self.assertRaises(SSL.SSLError, c.endheaders)
+            c.close()
+        finally:
+            self.stop_server(pid)
+            
+    def test_HTTPSConnection_illegalkeywordarg(self):
+        from M2Crypto import httpslib
+        self.assertRaises(ValueError, httpslib.HTTPSConnection, 'example.org',
+                          badKeyword=True)
+
+
+class MiscSSLClientTestCase(BaseSSLClientTestCase):
+
+    def test_no_connection(self):
+        ctx = SSL.Context()
+        s = SSL.Connection(ctx)
+        
+    def test_server_simple(self):
+        pid = self.start_server(self.args)
+        try:
+            self.assertRaises(ValueError, SSL.Context, 'tlsv5')
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            self.assertRaises(ValueError, s.read, 0)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_server_simple_secure_context(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_server_simple_secure_context_fail(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/server.pem')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_server_simple_timeouts(self):
+        pid = self.start_server(self.args)
+        try:
+            self.assertRaises(ValueError, SSL.Context, 'tlsv5')
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            
+            r = s.get_socket_read_timeout()
+            w = s.get_socket_write_timeout()
+            assert r.sec == 0, r.sec
+            assert r.microsec == 0, r.microsec
+            assert w.sec == 0, w.sec
+            assert w.microsec == 0, w.microsec
+
+            s.set_socket_read_timeout(SSL.timeout())
+            s.set_socket_write_timeout(SSL.timeout(909,9))
+            r = s.get_socket_read_timeout()
+            w = s.get_socket_write_timeout()
+            assert r.sec == 600, r.sec
+            assert r.microsec == 0, r.microsec
+            assert w.sec == 909, w.sec
+            #assert w.microsec == 9, w.microsec XXX 4000
+            
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_tls1_nok(self):
+        if fips_mode: # TLS is required in FIPS mode
+            return
+        self.args.append('-no_tls1')
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context('tlsv1')
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                self.failUnlessEqual(e[0], 'wrong version number')
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_tls1_ok(self):
+        self.args.append('-tls1')
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context('tlsv1')
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_sslv23_no_v2(self):
+        if fips_mode: # TLS is required in FIPS mode
+            return
+        self.args.append('-no_tls1')
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context('sslv23')
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            self.failUnlessEqual(s.get_version(), 'SSLv3')
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_sslv23_no_v2_no_service(self):
+        if fips_mode: # TLS is required in FIPS mode
+            return
+        self.args = self.args + ['-no_tls1', '-no_ssl3']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context('sslv23')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_sslv23_weak_crypto(self):
+        if fips_mode: # TLS is required in FIPS mode
+            return
+        self.args = self.args + ['-no_tls1', '-no_ssl3']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context('sslv23', weak_crypto=1)
+            s = SSL.Connection(ctx)
+            if m2.OPENSSL_VERSION_NUMBER < 0x10000000: # SSLv2 ciphers disabled by default in newer OpenSSL
+                s.connect(self.srv_addr)
+                self.failUnlessEqual(s.get_version(), 'SSLv2')
+            else:
+                self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_cipher_mismatch(self):
+        self.args = self.args + ['-cipher', 'AES256-SHA']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.set_cipher_list('AES128-SHA')
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                self.failUnlessEqual(e[0], 'sslv3 alert handshake failure')
+            s.close()
+        finally:
+            self.stop_server(pid)
+        
+    def test_no_such_cipher(self):
+        self.args = self.args + ['-cipher', 'AES128-SHA']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.set_cipher_list('EXP-RC2-MD5')
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                self.failUnlessEqual(e[0], 'no ciphers available')
+            s.close()
+        finally:
+            self.stop_server(pid)
+        
+    def test_no_weak_cipher(self):
+        if fips_mode: # Weak ciphers are prohibited
+            return
+        self.args = self.args + ['-cipher', 'EXP']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                self.failUnlessEqual(e[0], 'sslv3 alert handshake failure')
+            s.close()
+        finally:
+            self.stop_server(pid)
+        
+    def test_use_weak_cipher(self):
+        if fips_mode: # Weak ciphers are prohibited
+            return
+        self.args = self.args + ['-cipher', 'EXP']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context(weak_crypto=1)
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+        
+    def test_cipher_ok(self):
+        self.args = self.args + ['-cipher', 'AES128-SHA']
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.set_cipher_list('AES128-SHA')
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            
+            assert s.get_cipher().name() == 'AES128-SHA', s.get_cipher().name()
+            
+            cipher_stack = s.get_ciphers()
+            assert cipher_stack[0].name() == 'AES128-SHA', cipher_stack[0].name()
+            self.assertRaises(IndexError, cipher_stack.__getitem__, 2)
+            # For some reason there are 2 entries in the stack
+            #assert len(cipher_stack) == 1, len(cipher_stack)
+            assert s.get_cipher_list() == 'AES128-SHA', s.get_cipher_list()
+            
+            # Test Cipher_Stack iterator
+            i = 0
+            for cipher in cipher_stack:
+                i += 1
+                assert cipher.name() == 'AES128-SHA', '"%s"' % cipher.name()
+                self.assertEqual('AES128-SHA-128', str(cipher))
+            # For some reason there are 2 entries in the stack
+            #assert i == 1, i
+            self.assertEqual(i, len(cipher_stack))
+            
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+        
+    def verify_cb_new(self, ok, store):
+        return verify_cb_new_function(ok, store)
+
+    def test_verify_cb_new(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           self.verify_cb_new)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cb_new_class(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           VerifyCB())
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cb_new_function(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           verify_cb_new_function)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cb_lambda(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           lambda ok, store: 1)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def verify_cb_exception(self, ok, store):
+        raise Exception, 'We should fail verification'
+
+    def test_verify_cb_exception(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           self.verify_cb_exception)
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_verify_cb_not_callable(self):
+        ctx = SSL.Context()
+        self.assertRaises(TypeError,
+                          ctx.set_verify,
+                          SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
+                          9,
+                          1)
+
+    def test_verify_cb_wrong_callable(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           lambda _: '')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def verify_cb_old(self, ctx_ptr, x509_ptr, err, depth, ok):
+        try:
+            from M2Crypto import X509
+            assert not ok
+            assert err == m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT or \
+                   err == m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY or \
+                   err == m2.X509_V_ERR_CERT_UNTRUSTED or \
+                   err == m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
+            assert m2.ssl_ctx_get_cert_store(ctx_ptr)
+            assert X509.X509(x509_ptr).as_pem()
+        except AssertionError:
+            # If we let exceptions propagate from here the
+            # caller may see strange errors. This is cleaner.
+            return 0
+        return 1
+
+    def test_verify_cb_old(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           self.verify_cb_old)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_allow_unknown_old(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           SSL.cb.ssl_verify_callback)
+            ctx.set_allow_unknown_ca(1)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_allow_unknown_new(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+                           SSL.cb.ssl_verify_callback_allow_unknown_ca)
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cert(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cert_fail(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/server.pem')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_verify_cert_mutual_auth(self):
+        self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])        
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            ctx.load_cert('tests/x509.pem')
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cert_mutual_auth_servernbio(self):
+        self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem', '-nbio'])
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            ctx.load_cert('tests/x509.pem')
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_verify_cert_mutual_auth_fail(self):
+        self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])        
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_verify_nocert_fail(self):
+        self.args.extend(['-nocert'])        
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+            ctx.load_verify_locations('tests/ca.pem')
+            s = SSL.Connection(ctx)
+            self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_blocking0(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.setblocking(0)
+            self.assertRaises(Exception, s.connect, self.srv_addr)
+            s.close()
+        finally:
+            self.stop_server(pid)
+
+    def test_blocking1(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            s.setblocking(1)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_makefile(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            bio = s.makefile('rw')
+            #s.close()  # XXX bug 6628?
+            bio.write('GET / HTTP/1.0\n\n')
+            bio.flush()
+            data = bio.read()
+            bio.close()
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_makefile_err(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            s = SSL.Connection(ctx)
+            try:
+                s.connect(self.srv_addr)
+            except SSL.SSLError, e:
+                assert 0, e
+            f = s.makefile()
+            data = self.http_get(s)
+            s.close()
+            del f
+            del s
+            err_code = Err.peek_error_code()
+            assert not err_code, 'Unexpected error: %s' % err_code
+            err = Err.get_error()
+            assert not err, 'Unexpected error: %s' % err
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    def test_info_callback(self):
+        pid = self.start_server(self.args)
+        try:
+            ctx = SSL.Context()
+            ctx.set_info_callback()
+            s = SSL.Connection(ctx)
+            s.connect(self.srv_addr)
+            data = self.http_get(s)
+            s.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+
+class UrllibSSLClientTestCase(BaseSSLClientTestCase):
+
+    def test_urllib(self):
+        pid = self.start_server(self.args)
+        try:
+            from M2Crypto import m2urllib
+            url = m2urllib.FancyURLopener()
+            url.addheader('Connection', 'close')
+            u = url.open('https://%s:%s/' % (srv_host, srv_port))
+            data = u.read()
+            u.close()
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+    # XXX Don't actually know how to use m2urllib safely!
+    #def test_urllib_secure_context(self):
+    #def test_urllib_secure_context_fail(self):
+
+    # XXX Don't actually know how to use m2urllib safely!
+    #def test_urllib_safe_context(self):
+    #def test_urllib_safe_context_fail(self):
+
+
+class Urllib2SSLClientTestCase(BaseSSLClientTestCase):
+
+    if sys.version_info >= (2,4):
+        def test_urllib2(self):
+            pid = self.start_server(self.args)
+            try:
+                from M2Crypto import m2urllib2
+                opener = m2urllib2.build_opener()
+                opener.addheaders = [('Connection', 'close')]
+                u = opener.open('https://%s:%s/' % (srv_host, srv_port))
+                data = u.read()
+                u.close()
+            finally:
+                self.stop_server(pid)
+            self.failIf(string.find(data, 's_server -quiet -www') == -1)
+    
+        def test_urllib2_secure_context(self):
+            pid = self.start_server(self.args)
+            try:
+                ctx = SSL.Context()
+                ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+                ctx.load_verify_locations('tests/ca.pem')
+                
+                from M2Crypto import m2urllib2
+                opener = m2urllib2.build_opener(ctx)
+                opener.addheaders = [('Connection', 'close')]           
+                u = opener.open('https://%s:%s/' % (srv_host, srv_port))
+                data = u.read()
+                u.close()
+            finally:
+                self.stop_server(pid)
+            self.failIf(string.find(data, 's_server -quiet -www') == -1)
+    
+        def test_urllib2_secure_context_fail(self):
+            pid = self.start_server(self.args)
+            try:
+                ctx = SSL.Context()
+                ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+                ctx.load_verify_locations('tests/server.pem')
+                
+                from M2Crypto import m2urllib2
+                opener = m2urllib2.build_opener(ctx)
+                opener.addheaders = [('Connection', 'close')]
+                self.assertRaises(SSL.SSLError, opener.open, 'https://%s:%s/' % (srv_host, srv_port))
+            finally:
+                self.stop_server(pid)
+
+        def test_z_urllib2_opener(self):
+            pid = self.start_server(self.args)
+            try:
+                ctx = SSL.Context()
+
+                from M2Crypto import m2urllib2
+                opener = m2urllib2.build_opener(ctx, m2urllib2.HTTPBasicAuthHandler())
+                m2urllib2.install_opener(opener)
+                req = m2urllib2.Request('https://%s:%s/' % (srv_host, srv_port))
+                u = m2urllib2.urlopen(req)
+                data = u.read()
+                u.close()
+            finally:
+                self.stop_server(pid)
+            self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+        def test_urllib2_opener_handlers(self):
+            ctx = SSL.Context()
+
+            from M2Crypto import m2urllib2
+            opener = m2urllib2.build_opener(ctx,
+                                            m2urllib2.HTTPBasicAuthHandler())
+
+        def test_urllib2_leak(self):
+            pid = self.start_server(self.args)
+            try:
+                import gc
+                from M2Crypto import m2urllib2
+                o = m2urllib2.build_opener()
+                r = o.open('https://%s:%s/' % (srv_host, srv_port))
+                s = [r.fp._sock.fp]
+                r.close()
+                self.assertEqual(len(gc.get_referrers(s[0])), 1)
+            finally:
+                self.stop_server(pid)
+
+
+class TwistedSSLClientTestCase(BaseSSLClientTestCase):
+
+    def test_twisted_wrapper(self):
+        # Test only when twisted and ZopeInterfaces are present
+        try:
+            from twisted.internet.protocol import ClientFactory
+            from twisted.protocols.basic import LineReceiver
+            from twisted.internet import reactor
+            import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+        except ImportError:
+            import warnings
+            warnings.warn('Skipping twisted wrapper test because twisted not found')
+            return
+        
+        class EchoClient(LineReceiver):
+            def connectionMade(self):
+                self.sendLine('GET / HTTP/1.0\n\n')
+
+            def lineReceived(self, line):
+                global twisted_data
+                twisted_data += line
+
+        class EchoClientFactory(ClientFactory):
+            protocol = EchoClient
+        
+            def clientConnectionFailed(self, connector, reason):
+                reactor.stop()
+                assert 0, reason
+        
+            def clientConnectionLost(self, connector, reason):
+                reactor.stop()
+                
+        pid = self.start_server(self.args)
+
+        class ContextFactory:
+            def getContext(self):
+                return SSL.Context()
+
+        try:
+            global twisted_data
+            twisted_data = ''
+            
+            contextFactory = ContextFactory()
+            factory = EchoClientFactory()
+            wrapper.connectSSL(srv_host, srv_port, factory, contextFactory)
+            reactor.run() # This will block until reactor.stop() is called
+        finally:
+            self.stop_server(pid)
+        self.failIf(string.find(twisted_data, 's_server -quiet -www') == -1)
+
+
+twisted_data = ''
+
+
+class XmlRpcLibTestCase(unittest.TestCase):
+    def test_lib(self):
+        from M2Crypto import m2xmlrpclib
+        m2xmlrpclib.SSL_Transport()
+        # XXX need server to test against
+
+
+class FtpsLibTestCase(unittest.TestCase):
+    def test_lib(self):
+        from M2Crypto import ftpslib
+        ftpslib.FTP_TLS()
+        # XXX need server to test against
+
+
+class SessionTestCase(unittest.TestCase):
+    def test_session_load_bad(self):
+        self.assertRaises(SSL.SSLError, SSL.Session.load_session,
+                          'tests/signer.pem')
+
+class FtpslibTestCase(unittest.TestCase):
+    def test_26_compat(self):
+        from M2Crypto import ftpslib
+        f = ftpslib.FTP_TLS()
+        # 2.6 used to raise AttributeError:
+        self.assertRaises(socket.gaierror, f.connect, 'no-such-host-dfgHJK56789', 990)
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(SessionTestCase))
+    suite.addTest(unittest.makeSuite(XmlRpcLibTestCase))
+    suite.addTest(unittest.makeSuite(FtpsLibTestCase))
+    suite.addTest(unittest.makeSuite(PassSSLClientTestCase))
+    suite.addTest(unittest.makeSuite(HttpslibSSLClientTestCase))
+    suite.addTest(unittest.makeSuite(UrllibSSLClientTestCase))
+    suite.addTest(unittest.makeSuite(Urllib2SSLClientTestCase))
+    suite.addTest(unittest.makeSuite(MiscSSLClientTestCase))
+    suite.addTest(unittest.makeSuite(FtpslibTestCase))
+    try:
+        import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+        suite.addTest(unittest.makeSuite(TwistedSSLClientTestCase))
+    except ImportError:
+        pass
+    return suite    
+    
+
+def zap_servers():
+    s = 's_server'
+    fn = tempfile.mktemp() 
+    cmd = 'ps | egrep %s > %s' % (s, fn)
+    os.system(cmd)
+    f = open(fn)
+    while 1:
+        ps = f.readline()
+        if not ps:
+            break
+        chunk = string.split(ps)
+        pid, cmd = chunk[0], chunk[4]
+        if cmd == s:
+            os.kill(int(pid), 1)
+    f.close()
+    os.unlink(fn)
+
+
+if __name__ == '__main__':
+    report_leaks = 0
+    
+    if report_leaks:
+        import gc
+        gc.enable()
+        gc.set_debug(gc.DEBUG_LEAK & ~gc.DEBUG_SAVEALL)
+    
+    try:
+        Rand.load_file('randpool.dat', -1) 
+        unittest.TextTestRunner().run(suite())
+        Rand.save_file('randpool.dat')
+    finally:
+        zap_servers()
+
+    if report_leaks:
+        import alltests
+        alltests.dump_garbage()
diff --git a/tests/test_ssl_offline.py b/tests/test_ssl_offline.py
new file mode 100644 (file)
index 0000000..ba77434
--- /dev/null
@@ -0,0 +1,60 @@
+"""Unit tests for M2Crypto.SSL offline parts
+
+Copyright (C) 2006 Open Source Applications Foundation. All Rights Reserved.
+
+Copyright (C) 2009-2010 Heikki Toivonen. All Rights Reserved.
+"""
+
+import unittest, doctest
+from M2Crypto.SSL import Checker
+from M2Crypto import X509
+from M2Crypto import SSL
+from test_ssl import srv_host
+
+
+class CheckerTestCase(unittest.TestCase):
+    def test_checker(self):
+
+        check = Checker.Checker(host=srv_host,
+                                peerCertHash='7B754EFA41A264AAD370D43460BC8229F9354ECE')
+        x509 = X509.load_cert('tests/server.pem')
+        assert check(x509, srv_host)
+        self.assertRaises(Checker.WrongHost, check, x509, 'example.com')
+        
+        doctest.testmod(Checker)
+
+    
+class ContextTestCase(unittest.TestCase):
+    def test_ctx_load_verify_locations(self):
+        ctx = SSL.Context()
+        self.assertRaises(ValueError, ctx.load_verify_locations, None, None)
+        
+    def test_map(self):
+        from M2Crypto.SSL.Context import map, _ctxmap
+        assert isinstance(map(), _ctxmap)
+        ctx = SSL.Context()
+        assert map()
+        ctx.close()
+        assert map() is _ctxmap.singleton
+
+    def test_certstore(self):
+        ctx = SSL.Context()
+        ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+        ctx.load_verify_locations('tests/ca.pem')
+        ctx.load_cert('tests/x509.pem')
+
+        store = ctx.get_cert_store()
+        assert isinstance(store, X509.X509_Store)
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(CheckerTestCase))
+    suite.addTest(unittest.makeSuite(ContextTestCase))
+    return suite    
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
diff --git a/tests/test_ssl_win.py b/tests/test_ssl_win.py
new file mode 100644 (file)
index 0000000..0d13bd0
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SSL. 
+
+Win32 version - requires Mark Hammond's Win32 extensions and openssl.exe 
+on your PATH.
+
+Copyright (c) 2000-2001 Ng Pheng Siong. All rights reserved."""
+
+import os, os.path, string, time, unittest
+try:
+    import win32process
+except ImportError:
+    win32process = None
+
+if win32process:
+    from M2Crypto import Rand, SSL
+    import test_ssl
+    
+    def find_openssl():
+        plist = os.environ['PATH'].split(';')
+        for p in plist:
+            try:
+                dir = os.listdir(p)
+                if 'openssl.exe' in dir:
+                    return os.path.join(p, 'openssl.exe')
+            except WindowsError:
+                pass
+        return None
+    
+    
+    srv_host = 'localhost'
+    srv_port = 64000
+    
+    class SSLWinClientTestCase(test_ssl.SSLClientTestCase):
+    
+        startupinfo = win32process.STARTUPINFO()
+        openssl = find_openssl()
+    
+        def start_server(self, args):
+            # openssl must be started in the tests directory for it
+            # to find the .pem files
+            os.chdir('tests')        
+            try:
+                hproc, hthread, pid, tid = win32process.CreateProcess(self.openssl,
+                    string.join(args), None, None, 0, win32process.DETACHED_PROCESS, 
+                    None, None, self.startupinfo)
+            finally:
+                os.chdir('..')            
+            time.sleep(0.3)
+            return hproc
+    
+        def stop_server(self, hproc):
+            win32process.TerminateProcess(hproc, 0)
+    
+    
+    def suite():
+        return unittest.makeSuite(SSLWinClientTestCase)
+    
+    def zap_servers():
+        pass
+    
+    
+    if __name__ == '__main__':
+        try:
+            if find_openssl() is not None:
+                Rand.load_file('randpool.dat', -1) 
+                unittest.TextTestRunner().run(suite())
+                Rand.save_file('randpool.dat')
+        finally:
+            zap_servers()
diff --git a/tests/test_threading.py b/tests/test_threading.py
new file mode 100644 (file)
index 0000000..7912a61
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.threading.
+
+Copyright (C) 2007 Open Source Applications Foundation. All Rights Reserved.
+"""
+
+import unittest
+from M2Crypto import threading as m2threading, Rand
+
+
+class ThreadingTestCase(unittest.TestCase):
+
+    def setUp(self):
+        m2threading.init()
+
+    def tearDown(self):
+        m2threading.cleanup()
+
+    def test_pass(self):
+        pass
+
+    def test_multiInitCleanup(self):
+        m2threading.init()
+        m2threading.init()
+        m2threading.cleanup()
+        m2threading.cleanup()
+
+        m2threading.init()
+        m2threading.cleanup()
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ThreadingTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
diff --git a/tests/test_x509.py b/tests/test_x509.py
new file mode 100644 (file)
index 0000000..7ea86df
--- /dev/null
@@ -0,0 +1,559 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.X509.
+
+Contributed by Toby Allsopp <toby@MI6.GEN.NZ> under M2Crypto's license.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+Author: Heikki Toivonen
+"""
+
+import unittest
+import os, time, base64, sys
+from M2Crypto import X509, EVP, RSA, Rand, ASN1, m2, util, BIO
+
+class X509TestCase(unittest.TestCase):
+
+    def callback(self, *args):
+        pass
+
+    def mkreq(self, bits, ca=0):
+        pk = EVP.PKey()
+        x = X509.Request()
+        rsa = RSA.gen_key(bits, 65537, self.callback)
+        pk.assign_rsa(rsa)
+        rsa = None # should not be freed here
+        x.set_pubkey(pk)
+        name = x.get_subject()
+        name.C = "UK"
+        name.CN = "OpenSSL Group"
+        if not ca:
+            ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+            ext2 = X509.new_extension('nsComment', 'Hello there')
+            extstack = X509.X509_Extension_Stack()
+            extstack.push(ext1)
+            extstack.push(ext2)
+            x.add_extensions(extstack)
+        self.assertRaises(ValueError, x.sign, pk, 'sha513')
+        x.sign(pk,'sha1')
+        assert x.verify(pk)
+        pk2 = x.get_pubkey()
+        assert x.verify(pk2)
+        return x, pk
+
+    def test_ext(self):
+        self.assertRaises(ValueError, X509.new_extension,
+                          'subjectKeyIdentifier', 'hash')
+        ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+        assert ext.get_value() == 'DNS:foobar.example.com'
+        assert ext.get_value(indent=2) == '  DNS:foobar.example.com'
+        assert ext.get_value(flag=m2.X509V3_EXT_PARSE_UNKNOWN) == 'DNS:foobar.example.com'
+
+    def test_extstack(self):
+        # new
+        ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+        ext2 = X509.new_extension('nsComment', 'Hello there')
+        extstack = X509.X509_Extension_Stack()
+        
+        # push
+        extstack.push(ext1)
+        extstack.push(ext2)
+        assert(extstack[1].get_name() == 'nsComment')
+        assert len(extstack) == 2
+        
+        # iterator
+        i = 0
+        for e in extstack:
+            i += 1
+            assert len(e.get_name()) > 0
+        assert i == 2
+        
+        # pop
+        ext3 = extstack.pop()
+        assert len(extstack) == 1
+        assert(extstack[0].get_name() == 'subjectAltName')
+        extstack.push(ext3)
+        assert len(extstack) == 2
+        assert(extstack[1].get_name() == 'nsComment')
+        
+        assert extstack.pop() is not None
+        assert extstack.pop() is not None
+        assert extstack.pop() is None
+
+    def test_x509_name(self):
+        n = X509.X509_Name()
+        n.C = 'US' # It seems this actually needs to be a real 2 letter country code
+        assert n.C == 'US'
+        n.SP = 'State or Province'
+        assert n.SP == 'State or Province'
+        n.L = 'locality name'
+        assert n.L == 'locality name'
+        n.O = 'orhanization name'
+        assert n.O == 'orhanization name'
+        n.OU = 'org unit'
+        assert n.OU == 'org unit'
+        n.CN = 'common name'
+        assert n.CN == 'common name'
+        n.Email = 'bob@example.com'
+        assert n.Email == 'bob@example.com'
+        n.serialNumber = '1234'
+        assert n.serialNumber == '1234'
+        n.SN = 'surname'
+        assert n.SN == 'surname'
+        n.GN = 'given name'
+        assert n.GN == 'given name'
+        assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name', '"%s"' % n.as_text()
+        assert len(n) == 10, len(n)
+        n.givenName = 'name given'
+        assert n.GN == 'given name' # Just gets the first
+        assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name, GN=name given', '"%s"' % n.as_text()
+        assert len(n) == 11, len(n)
+        n.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
+                           entry="Proxy", len=-1, loc=-1, set=0)
+        assert len(n) == 12, len(n)
+        assert n.entry_count() == 12, n.entry_count()
+        assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name, GN=name given, CN=Proxy', '"%s"' % n.as_text()
+
+        self.assertRaises(AttributeError, n.__getattr__, 'foobar')
+        n.foobar = 1
+        assert n.foobar == 1, n.foobar
+        
+        # X509_Name_Entry tests
+        l = 0
+        for entry in n:
+            assert isinstance(entry, X509.X509_Name_Entry), entry
+            assert isinstance(entry.get_object(), ASN1.ASN1_Object), entry
+            assert isinstance(entry.get_data(), ASN1.ASN1_String), entry
+            l += 1
+        assert l == 12, l
+        
+        l = 0
+        for cn in n.get_entries_by_nid(m2.NID_commonName):
+            assert isinstance(cn, X509.X509_Name_Entry), cn
+            assert isinstance(cn.get_object(), ASN1.ASN1_Object), cn
+            data = cn.get_data()
+            assert isinstance(data, ASN1.ASN1_String), data
+            t = data.as_text()
+            assert t == "common name" or t == "Proxy", t
+            l += 1
+        assert l == 2, l
+
+        cn.set_data("Hello There!")
+        assert cn.get_data().as_text() == "Hello There!", cn.get_data().as_text()
+
+        assert n.as_hash() == 1697185131
+        
+        self.assertRaises(IndexError, lambda: n[100])
+        self.assert_(n[10])
+
+    def test_mkreq(self):
+        (req, _) = self.mkreq(1024)
+        req.save_pem('tests/tmp_request.pem')
+        req2 = X509.load_request('tests/tmp_request.pem')
+        os.remove('tests/tmp_request.pem')
+        req.save('tests/tmp_request.pem')
+        req3 = X509.load_request('tests/tmp_request.pem')
+        os.remove('tests/tmp_request.pem')
+        req.save('tests/tmp_request.der', format=X509.FORMAT_DER)
+        req4 = X509.load_request('tests/tmp_request.der',
+                format=X509.FORMAT_DER)
+        os.remove('tests/tmp_request.der')
+        assert req.as_pem() == req2.as_pem()
+        assert req.as_text() == req2.as_text()
+        assert req.as_der() == req2.as_der()
+        assert req.as_pem() == req3.as_pem()
+        assert req.as_text() == req3.as_text()
+        assert req.as_der() == req3.as_der()
+        assert req.as_pem() == req4.as_pem()
+        assert req.as_text() == req4.as_text()
+        assert req.as_der() == req4.as_der()
+        self.assertEqual(req.get_version(), 0)
+        req.set_version(1)
+        self.assertEqual(req.get_version(), 1)
+        req.set_version(0)
+        self.assertEqual(req.get_version(), 0)
+
+
+    def test_mkcert(self):
+        req, pk = self.mkreq(1024)
+        pkey = req.get_pubkey()
+        assert(req.verify(pkey))
+        sub = req.get_subject()
+        assert len(sub) == 2, len(sub)
+        cert = X509.X509()
+        cert.set_serial_number(1)
+        cert.set_version(2)
+        cert.set_subject(sub)
+        t = long(time.time()) + time.timezone
+        now = ASN1.ASN1_UTCTIME()
+        now.set_time(t)
+        nowPlusYear = ASN1.ASN1_UTCTIME()
+        nowPlusYear.set_time(t + 60 * 60 * 24 * 365)
+        cert.set_not_before(now)
+        cert.set_not_after(nowPlusYear)
+        assert str(cert.get_not_before()) == str(now)
+        assert str(cert.get_not_after()) == str(nowPlusYear)
+        issuer = X509.X509_Name()
+        issuer.CN = 'The Issuer Monkey'
+        issuer.O = 'The Organization Otherwise Known as My CA, Inc.'
+        cert.set_issuer(issuer)
+        cert.set_pubkey(pkey)
+        cert.set_pubkey(cert.get_pubkey()) # Make sure get/set work
+        ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+        ext.set_critical(0)
+        assert ext.get_critical() == 0
+        cert.add_ext(ext)
+        cert.sign(pk, 'sha1')
+        self.assertRaises(ValueError, cert.sign, pk, 'nosuchalgo')
+        assert(cert.get_ext('subjectAltName').get_name() == 'subjectAltName')
+        assert(cert.get_ext_at(0).get_name() == 'subjectAltName')
+        assert(cert.get_ext_at(0).get_value() == 'DNS:foobar.example.com')
+        assert cert.get_ext_count() == 1, cert.get_ext_count()
+        self.assertRaises(IndexError, cert.get_ext_at, 1)
+        assert cert.verify()
+        assert cert.verify(pkey)
+        assert cert.verify(cert.get_pubkey())
+        assert cert.get_version() == 2
+        assert cert.get_serial_number() == 1
+        assert cert.get_issuer().CN == 'The Issuer Monkey' 
+        
+        if m2.OPENSSL_VERSION_NUMBER >= 0x90800f:
+            assert not cert.check_ca()
+            assert not cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 1)
+            assert not cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 1)
+            assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 0)
+            assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 0)
+            assert cert.check_purpose(m2.X509_PURPOSE_ANY, 0)            
+        else:
+            self.assertRaises(AttributeError, cert.check_ca)
+
+    def mkcacert(self):
+        req, pk = self.mkreq(1024, ca=1)
+        pkey = req.get_pubkey()
+        sub = req.get_subject()
+        cert = X509.X509()
+        cert.set_serial_number(1)
+        cert.set_version(2)
+        cert.set_subject(sub)
+        t = long(time.time()) + time.timezone
+        now = ASN1.ASN1_UTCTIME()
+        now.set_time(t)
+        nowPlusYear = ASN1.ASN1_UTCTIME()
+        nowPlusYear.set_time(t + 60 * 60 * 24 * 365)
+        cert.set_not_before(now)
+        cert.set_not_after(nowPlusYear)
+        issuer = X509.X509_Name()
+        issuer.C = "UK"
+        issuer.CN = "OpenSSL Group"
+        cert.set_issuer(issuer)
+        cert.set_pubkey(pkey) 
+        ext = X509.new_extension('basicConstraints', 'CA:TRUE')
+        cert.add_ext(ext)
+        cert.sign(pk, 'sha1')
+
+        if m2.OPENSSL_VERSION_NUMBER >= 0x0090800fL:
+            assert cert.check_ca()
+            assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 1)
+            assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 1)
+            assert cert.check_purpose(m2.X509_PURPOSE_ANY, 1)
+            assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 0)
+            assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 0)
+            assert cert.check_purpose(m2.X509_PURPOSE_ANY, 0)
+        else:
+            self.assertRaises(AttributeError, cert.check_ca)
+        
+        return cert, pk, pkey
+
+    def test_mkcacert(self): 
+        cacert, pk, pkey = self.mkcacert()
+        assert cacert.verify(pkey)
+        
+
+    def test_mkproxycert(self): 
+        cacert, pk1, pkey = self.mkcacert()
+        end_entity_cert_req, pk2 = self.mkreq(1024)
+        end_entity_cert = self.make_eecert(cacert)
+        end_entity_cert.set_subject(end_entity_cert_req.get_subject())
+        end_entity_cert.set_pubkey(end_entity_cert_req.get_pubkey())
+        end_entity_cert.sign(pk1, 'sha1')
+        proxycert = self.make_proxycert(end_entity_cert)
+        proxycert.sign(pk2, 'sha1')
+        assert proxycert.verify(pk2)
+        assert proxycert.get_ext_at(0).get_name() == 'proxyCertInfo', proxycert.get_ext_at(0).get_name()
+        assert proxycert.get_ext_at(0).get_value() == 'Path Length Constraint: infinite\nPolicy Language: Inherit all\n', '"%s"' % proxycert.get_ext_at(0).get_value()
+        assert proxycert.get_ext_count() == 1, proxycert.get_ext_count()
+        assert proxycert.get_subject().as_text() == 'C=UK, CN=OpenSSL Group, CN=Proxy', proxycert.get_subject().as_text()
+        assert proxycert.get_subject().as_text(indent=2, flags=m2.XN_FLAG_RFC2253) == '  CN=Proxy,CN=OpenSSL Group,C=UK', '"%s"' %  proxycert.get_subject().as_text(indent=2, flags=m2.XN_FLAG_RFC2253)
+
+    def make_eecert(self, cacert):
+        eecert = X509.X509()
+        eecert.set_serial_number(2)
+        eecert.set_version(2)
+        t = long(time.time()) + time.timezone
+        now = ASN1.ASN1_UTCTIME()
+        now.set_time(t)
+        now_plus_year = ASN1.ASN1_UTCTIME()
+        now_plus_year.set_time(t + 60 * 60 * 24 * 365)
+        eecert.set_not_before(now)
+        eecert.set_not_after(now_plus_year)
+        eecert.set_issuer(cacert.get_subject())
+        return eecert
+    
+    def make_proxycert(self, eecert):
+        proxycert = X509.X509()
+        pk2 = EVP.PKey()
+        proxykey =  RSA.gen_key(1024, 65537, self.callback)
+        pk2.assign_rsa(proxykey)
+        proxycert.set_pubkey(pk2)
+        proxycert.set_version(2)
+        not_before = ASN1.ASN1_UTCTIME()
+        not_after = ASN1.ASN1_UTCTIME()
+        not_before.set_time(int(time.time()))
+        offset = 12 * 3600
+        not_after.set_time(int(time.time()) + offset )
+        proxycert.set_not_before(not_before)
+        proxycert.set_not_after(not_after)
+        proxycert.set_issuer_name(eecert.get_subject())
+        proxycert.set_serial_number(12345678)
+        proxy_subject_name = X509.X509_Name()
+        issuer_name_string = eecert.get_subject().as_text()
+        seq = issuer_name_string.split(",")
+
+        subject_name = X509.X509_Name()
+        for entry in seq:
+            l = entry.split("=")
+            subject_name.add_entry_by_txt(field=l[0].strip(),
+                                          type=ASN1.MBSTRING_ASC,
+                                          entry=l[1], len=-1, loc=-1, set=0)
+
+        subject_name.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
+                                      entry="Proxy", len=-1, loc=-1, set=0)
+
+
+        proxycert.set_subject_name(subject_name)
+        pci_ext = X509.new_extension("proxyCertInfo", 
+                                     "critical,language:Inherit all", 1) # XXX leaks 8 bytes 
+        proxycert.add_ext(pci_ext)
+        return proxycert
+    
+    def test_fingerprint(self):
+        x509 = X509.load_cert('tests/x509.pem')
+        fp = x509.get_fingerprint('sha1')
+        expected = '8D2EB9E203B5FFDC7F4FA7DC4103E852A55B808D'
+        assert fp == expected, '%s != %s' % (fp, expected)
+
+    def test_load_der_string(self):
+        f = open('tests/x509.der', 'rb')
+        x509 = X509.load_cert_der_string(''.join(f.readlines()))
+        fp = x509.get_fingerprint('sha1')
+        expected = '8D2EB9E203B5FFDC7F4FA7DC4103E852A55B808D'
+        assert fp == expected, '%s != %s' % (fp, expected)
+
+    def test_save_der_string(self):
+        x509 = X509.load_cert('tests/x509.pem')
+        s = x509.as_der()
+        f = open('tests/x509.der', 'rb')
+        s2 = f.read()
+        f.close()
+        assert s == s2
+
+    def test_load(self):
+        x509 = X509.load_cert('tests/x509.pem')
+        x5092 = X509.load_cert('tests/x509.der', format=X509.FORMAT_DER)
+        assert x509.as_text() == x5092.as_text()
+        assert x509.as_pem() == x5092.as_pem()
+        assert x509.as_der() == x5092.as_der()
+        return
+    
+    def test_load_bio(self):
+        bio = BIO.openfile('tests/x509.pem')
+        bio2 = BIO.openfile('tests/x509.der')
+        x509 = X509.load_cert_bio(bio)
+        x5092 = X509.load_cert_bio(bio2, format=X509.FORMAT_DER)
+        
+        self.assertRaises(ValueError, X509.load_cert_bio, bio2, format=45678)
+
+        assert x509.as_text() == x5092.as_text()
+        assert x509.as_pem() == x5092.as_pem()
+        assert x509.as_der() == x5092.as_der()
+        return
+
+    def test_load_string(self):
+        f = open('tests/x509.pem')
+        s = f.read()
+        f.close()
+        f2 = open('tests/x509.der', 'rb')
+        s2 = f2.read()
+        f2.close()
+        x509 = X509.load_cert_string(s)
+        x5092 = X509.load_cert_string(s2, X509.FORMAT_DER)
+        assert x509.as_text() == x5092.as_text()
+        assert x509.as_pem() == x5092.as_pem()
+        assert x509.as_der() == x5092.as_der()
+        return
+    
+    def test_load_request_bio(self):
+        (req, _) = self.mkreq(512)
+
+        r1 = X509.load_request_der_string(req.as_der())
+        r2 = X509.load_request_string(req.as_der(), X509.FORMAT_DER)
+        r3 = X509.load_request_string(req.as_pem(), X509.FORMAT_PEM)
+
+        r4 = X509.load_request_bio(BIO.MemoryBuffer(req.as_der()), X509.FORMAT_DER)
+        r5 = X509.load_request_bio(BIO.MemoryBuffer(req.as_pem()), X509.FORMAT_PEM)
+
+        for r in [r1, r2, r3, r4, r5]:
+            assert req.as_der() == r.as_der()
+
+        self.assertRaises(ValueError, X509.load_request_bio, BIO.MemoryBuffer(req.as_pem()), 345678)
+
+    def test_save(self):
+        x509 = X509.load_cert('tests/x509.pem')
+        f = open('tests/x509.pem', 'r')
+        lTmp = f.readlines()
+        x509_pem = ''.join(lTmp[44:60]) # -----BEGIN CERTIFICATE----- : -----END CERTIFICATE-----
+        f.close()
+        f = open('tests/x509.der', 'rb')
+        x509_der = f.read()
+        f.close()
+        x509.save('tests/tmpcert.pem')
+        f = open('tests/tmpcert.pem')
+        s = f.read()
+        f.close()
+        self.assertEquals(s, x509_pem)
+        os.remove('tests/tmpcert.pem')
+        x509.save('tests/tmpcert.der', format=X509.FORMAT_DER)
+        f = open('tests/tmpcert.der', 'rb')
+        s = f.read()
+        f.close()
+        self.assertEquals(s, x509_der)
+        os.remove('tests/tmpcert.der')
+
+    def test_malformed_data(self):
+        self.assertRaises(X509.X509Error, X509.load_cert_string, 'Hello')
+        self.assertRaises(X509.X509Error, X509.load_cert_der_string, 'Hello')
+        self.assertRaises(X509.X509Error, X509.new_stack_from_der, 'Hello')
+        self.assertRaises(X509.X509Error, X509.load_cert, 'tests/alltests.py')
+        self.assertRaises(X509.X509Error, X509.load_request, 'tests/alltests.py')
+        self.assertRaises(X509.X509Error, X509.load_request_string, 'Hello')
+        self.assertRaises(X509.X509Error, X509.load_request_der_string, 'Hello')
+        self.assertRaises(X509.X509Error, X509.load_crl, 'tests/alltests.py')
+        
+    def test_long_serial(self):
+        from M2Crypto import X509
+        cert = X509.load_cert('tests/long_serial_cert.pem')
+        self.assertEquals(cert.get_serial_number(), 17616841808974579194)
+
+        cert = X509.load_cert('tests/thawte.pem')
+        self.assertEquals(cert.get_serial_number(), 127614157056681299805556476275995414779)
+
+
+class X509_StackTestCase(unittest.TestCase):
+    
+    def test_make_stack_from_der(self):
+        f = open("tests/der_encoded_seq.b64")
+        b64 = f.read(1304)
+        seq = base64.decodestring(b64)
+        stack = X509.new_stack_from_der(seq)
+        cert = stack.pop()
+        assert stack.pop() is None
+        
+        cert.foobar = 1
+        assert cert.foobar == 1
+        
+        subject = cert.get_subject() 
+        assert str(subject) == "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov"
+
+    def test_make_stack_check_num(self):
+        f = open("tests/der_encoded_seq.b64")
+        b64 = f.read(1304)
+        seq = base64.decodestring(b64)
+        stack = X509.new_stack_from_der(seq)
+        num = len(stack)
+        assert num == 1 
+        cert = stack.pop() 
+        num = len(stack)
+        assert num == 0 
+        subject = cert.get_subject() 
+        assert str(subject) == "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov"
+
+    def test_make_stack(self):
+        stack = X509.X509_Stack()
+        cert = X509.load_cert("tests/x509.pem")
+        issuer = X509.load_cert("tests/ca.pem")
+        cert_subject1 = cert.get_subject()
+        issuer_subject1 = issuer.get_subject()
+        stack.push(cert)
+        stack.push(issuer)
+        
+        # Test stack iterator
+        i = 0
+        for c in stack:
+            i += 1
+            assert len(c.get_subject().CN) > 0
+        assert i == 2
+        
+        issuer_pop = stack.pop() 
+        cert_pop = stack.pop() 
+        cert_subject2 = cert_pop.get_subject() 
+        issuer_subject2 = issuer.get_subject()
+        assert str(cert_subject1) == str(cert_subject2)
+        assert str(issuer_subject1) == str(issuer_subject2)
+    
+    def test_as_der(self):
+        stack = X509.X509_Stack()
+        cert = X509.load_cert("tests/x509.pem")
+        issuer = X509.load_cert("tests/ca.pem")
+        cert_subject1 = cert.get_subject()
+        issuer_subject1 = issuer.get_subject()
+        stack.push(cert)
+        stack.push(issuer)
+        der_seq = stack.as_der() 
+        stack2 = X509.new_stack_from_der(der_seq)
+        issuer_pop = stack2.pop() 
+        cert_pop = stack2.pop() 
+        cert_subject2 = cert_pop.get_subject() 
+        issuer_subject2 = issuer.get_subject()
+        assert str(cert_subject1) == str(cert_subject2)
+        assert str(issuer_subject1) == str(issuer_subject2)
+        
+
+class X509_ExtTestCase(unittest.TestCase):
+    
+    def test_ext(self):
+        if 0: # XXX
+            # With this leaks 8 bytes:
+            name = "proxyCertInfo"
+            value = "critical,language:Inherit all"
+        else:
+            # With this there are no leaks:
+            name = "nsComment"
+            value = "Hello"
+        
+        lhash = m2.x509v3_lhash()
+        ctx = m2.x509v3_set_conf_lhash(lhash)
+        x509_ext_ptr = m2.x509v3_ext_conf(lhash, ctx, name, value)
+        x509_ext = X509.X509_Extension(x509_ext_ptr, 1)
+
+
+class CRLTestCase(unittest.TestCase):
+    def test_new(self):
+        crl = X509.CRL()
+        self.assertEqual(crl.as_text()[:34],
+                         'Certificate Revocation List (CRL):')
+    
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(X509TestCase))
+    suite.addTest(unittest.makeSuite(X509_StackTestCase))
+    suite.addTest(unittest.makeSuite(X509_ExtTestCase))
+    suite.addTest(unittest.makeSuite(CRLTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    Rand.load_file('randpool.dat', -1)
+    unittest.TextTestRunner().run(suite())
+    Rand.save_file('randpool.dat')
diff --git a/tests/thawte.pem b/tests/thawte.pem
new file mode 100644 (file)
index 0000000..34af29e
--- /dev/null
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----\r
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB\r
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf\r
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw\r
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV\r
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa\r
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl\r
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u\r
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl\r
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz\r
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm\r
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8\r
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf\r
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9\r
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S\r
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk\r
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV\r
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA\r
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW\r
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu\r
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c\r
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM\r
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu\r
+MdRAGmI0Nj81Aa6sY6A=\r
+-----END CERTIFICATE-----\r
diff --git a/tests/x509.der b/tests/x509.der
new file mode 100644 (file)
index 0000000..fbc9e81
Binary files /dev/null and b/tests/x509.der differ
diff --git a/tests/x509.pem b/tests/x509.pem
new file mode 100644 (file)
index 0000000..4a1f1ee
--- /dev/null
@@ -0,0 +1,75 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            d1:b6:bf:af:06:17:8c:bf
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+        Validity
+            Not Before: Jul 28 04:34:34 2009 GMT
+            Not After : Jul 26 04:34:34 2019 GMT
+        Subject: C=US, ST=California, O=M2Crypto, CN=X509
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:d3:62:55:12:30:b8:dc:84:7c:63:bd:80:1d:19:
+                    1a:72:f2:28:f8:59:0b:2a:6b:f2:2a:23:9d:bb:0f:
+                    7f:92:5e:dd:27:74:bc:78:0a:27:ab:1c:2e:23:1c:
+                    26:77:48:b6:8f:03:ef:57:1c:a0:54:ae:1a:e8:f5:
+                    24:a1:46:a1:27:48:55:33:98:fc:db:6a:83:2e:89:
+                    3f:e0:f3:91:9d:da:4f:db:74:90:9d:a6:8d:4a:46:
+                    cb:9f:ba:b8:60:df:ae:ee:22:4b:3f:80:55:f7:1d:
+                    89:3c:2b:28:df:46:19:d5:18:ac:e9:07:4e:40:81:
+                    75:bc:da:5b:d5:e1:c2:04:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                B1:C4:6F:98:6F:E8:3B:8C:A1:26:11:81:97:9A:12:50:4A:1A:6C:88
+            X509v3 Authority Key Identifier: 
+                keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+    Signature Algorithm: sha1WithRSAEncryption
+        3f:0b:44:bc:d2:da:5f:a9:39:be:08:53:e6:fd:10:ff:d6:f0:
+        a3:51:f6:be:03:20:cc:b3:52:cf:0f:7c:3f:56:42:6f:9d:72:
+        9b:09:a5:64:3f:43:29:24:2b:d6:79:94:54:2f:99:e8:ce:fe:
+        fd:de:bb:ca:43:28:16:ff:32:ac:3d:c5:56:db:87:23:3c:d4:
+        69:f7:4e:1b:c4:be:c9:d8:27:99:2a:64:be:3a:6b:7e:51:85:
+        db:75:35:40:a5:6c:ae:53:c3:09:e7:00:35:17:64:1a:17:71:
+        c5:d5:59:e5:8f:fc:96:4a:f9:81:33:23:4c:c1:60:71:93:18:
+        0a:c4
+-----BEGIN CERTIFICATE-----
+MIICjDCCAfWgAwIBAgIJANG2v68GF4y/MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzQzNFoXDTE5MDcy
+NjA0MzQzNFowRDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMQ0wCwYDVQQDEwRYNTA5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDTYlUSMLjchHxjvYAdGRpy8ij4WQsqa/IqI527D3+SXt0ndLx4
+CierHC4jHCZ3SLaPA+9XHKBUrhro9SShRqEnSFUzmPzbaoMuiT/g85Gd2k/bdJCd
+po1KRsufurhg367uIks/gFX3HYk8KyjfRhnVGKzpB05AgXW82lvV4cIEFQIDAQAB
+o3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRl
+ZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUscRvmG/oO4yhJhGBl5oSUEoabIgwHwYD
+VR0jBBgwFoAUrWRFdI+DxyzV16CFkRBAmpyWz+4wDQYJKoZIhvcNAQEFBQADgYEA
+PwtEvNLaX6k5vghT5v0Q/9bwo1H2vgMgzLNSzw98P1ZCb51ymwmlZD9DKSQr1nmU
+VC+Z6M7+/d67ykMoFv8yrD3FVtuHIzzUafdOG8S+ydgnmSpkvjprflGF23U1QKVs
+rlPDCecANRdkGhdxxdVZ5Y/8lkr5gTMjTMFgcZMYCsQ=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDTYlUSMLjchHxjvYAdGRpy8ij4WQsqa/IqI527D3+SXt0ndLx4
+CierHC4jHCZ3SLaPA+9XHKBUrhro9SShRqEnSFUzmPzbaoMuiT/g85Gd2k/bdJCd
+po1KRsufurhg367uIks/gFX3HYk8KyjfRhnVGKzpB05AgXW82lvV4cIEFQIDAQAB
+AoGATPipcY48QlAb21XNqMrTTrfPI1+JKVFVRPLjJJJoKaxRa2SenDdWaoBAbJh7
+iUP49erA5D+QQkWDlwBs7i0B0NqSkZAUVTfzRjGackTNJUQ+smfeqRLMH+Oru6DS
+VFbb818nJOJKqMMhMz8SrPrrbg+qiHlJ3JUQnNzTYohOMAECQQDvTJBSSit34ZBO
+ABj4vWYucCnOygcpICQnIsG97sZmF8tuF55tA5e+0v9R7BPuyAjrQnKJqDj3r/AY
+AxhgngGVAkEA4iMGoHzoSQvh+gT0A2rPCtVo+URNswIEZhQmMuA0VjrFCphWkZE+
+3jgDsJTNQUJs4mczQMcBzL34Nh1cJThYgQJARMMrdXn6o6gdX0yH4HIMOqvgV5uW
+Eys5OEW0hm9mc0/DFQ+UZp7xq9PVqiS8VZEFfxTI9OVx+TqFM2EwUBMXQQJBAIge
+n0mRhl0Z6v+NZbh83X3e8h5BUCf1ieJMNKYhMT/KhnsXMdzTui0XOJldKKQksNgj
+WMWgROQSYctpJuM8pIECQQCNN27XVHs4YAQ6GvBkrHsK5w6LZkm6UaJgbCqDqyeS
+eqfPp9VRurZ/FhK1mPbgNN67U4Ik1nwjR0o8wD4mreIj
+-----END RSA PRIVATE KEY-----