[svn:qpsmtpd] rev 488 - trunk/plugins
[email protected] 8 Jul 2005 16:43:38 -0000
| Newsgroups | perl.cvs.qpsmtpd |
|---|---|
| Message-ID | <[email protected]> |
Author: msergeant
Date: Fri Jul 8 09:43:37 2005
New Revision: 488
Added:
trunk/plugins/tls
Log:
TLS plugin
Added: trunk/plugins/tls
==============================================================================
--- (empty file)
+++ trunk/plugins/tls Fri Jul 8 09:43:37 2005
@@ -0,0 +1,135 @@
+#!perl -w
+
+=head1 NAME
+
+tls - plugin to support STARTTLS
+
+=head1 SYNOPSIS
+
+# in config/plugins
+
+ tls ssl/cert.pem ssl/privkey.pem
+
+=head1 DESCRIPTION
+
+This plugin implements basic TLS support.
+
+If TLS is successfully negotiated then the C<tls_enabled> field in the
+Connection notes is set. If you wish to make TLS mandatory you should check
+that field and take appropriate action. Note that you can only do that from
+MAIL FROM onwards.
+
+=cut
+
+use IO::Socket::SSL qw(debug1 debug2 debug3 debug4);
+
+sub init {
+ my ($self, $qp, $cert, $key) = @_;
+ $cert ||= 'ssl/cert.pem';
+ $key ||= 'ssl/privkey.pem';
+ $self->tls_cert($cert);
+ $self->tls_key($key);
+
+ local $^W; # this bit is very noisy...
+ my $ssl_ctx = IO::Socket::SSL::SSL_Context->new(
+ SSL_use_cert => 1,
+ SSL_cert_file => $self->tls_cert,
+ SSL_key_file => $self->tls_key,
+ SSL_cipher_list => 'HIGH',
+ SSL_server => 1
+ ) or die "Could not create SSL context: $!";
+ # now extract the password...
+
+ $self->ssl_context($ssl_ctx);
+}
+
+sub hook_ehlo {
+ my ($self, $transaction) = @_;
+ return DECLINED unless $self->can_do_tls;
+ return DECLINED if $self->connection->notes('tls_enabled');
+ return DENY, "Command refused due to lack of security" if $transaction->notes('ssl_failed');
+ my $cap = $transaction->notes('capabilities');
+ $cap ||= [];
+ push @$cap, 'STARTTLS';
+ $transaction->notes('tls_enabled', 1);
+ $transaction->notes('capabilities', $cap);
+ return DECLINED;
+}
+
+sub hook_unrecognized_command {
+ my ($self, $transaction, $cmd, @args) = @_;
+ return DECLINED unless $cmd eq 'starttls';
+ return DECLINED unless $transaction->notes('tls_enabled');
+ return DENY, "Syntax error (no parameters allowed)" if @args;
+
+ # OK, now we setup TLS
+ $self->qp->respond (220, "Go ahead with TLS");
+
+ eval {
+ my $tlssocket = IO::Socket::SSL->new_from_fd(
+ fileno(STDIN), '+>',
+ SSL_use_cert => 1,
+ SSL_cert_file => $self->tls_cert,
+ SSL_key_file => $self->tls_key,
+ SSL_cipher_list => 'HIGH',
+ SSL_server => 1,
+ SSL_reuse_ctx => $self->ssl_context,
+ ) or die "Could not create SSL socket: $!";
+
+ my $conn = $self->connection;
+ # Create a new connection object with subset of information collected thus far
+ $self->qp->connection(Qpsmtpd::Connection->new(
+ map { $_ => $conn->$_ }
+ qw(
+ local_ip
+ local_port
+ remote_ip
+ remote_port
+ remote_host
+ remote_info
+ ),
+ ));
+ $self->qp->reset_transaction;
+ *STDIN = *STDOUT = $self->connection->notes('tls_socket', $tlssocket);
+ $self->connection->notes('tls_enabled', 1);
+ };
+ if ($@) {
+ # SSL setup failed. Now we must respond to every command with 5XX
+ warn("TLS failed: $@\n");
+ $transaction->notes('ssl_failed', 1);
+ return DENY, "TLS Negotiation Failed";
+ }
+
+ warn("TLS setup returning\n");
+ return DONE;
+}
+
+sub can_do_tls {
+ my ($self) = @_;
+ $self->tls_cert && -r $self->tls_cert;
+}
+
+sub tls_cert {
+ my $self = shift;
+ @_ and $self->{_tls_cert} = shift;
+ $self->{_tls_cert};
+}
+
+sub tls_key {
+ my $self = shift;
+ @_ and $self->{_tls_key} = shift;
+ $self->{_tls_key};
+}
+
+sub ssl_context {
+ my $self = shift;
+ @_ and $self->{_ssl_ctx} = shift;
+ $self->{_ssl_ctx};
+}
+
+# Fulfill RFC 2487 secn 5.1
+sub bad_ssl_hook {
+ my ($self, $transaction) = @_;
+ return DENY, "Command refused due to lack of security" if $transaction->notes('ssl_failed');
+}
+*hook_helo = *hook_data = *hook_rcpt = *hook_mail = *hook_auth = \&bad_ssl_hook;