File Coverage

blib/lib/Auth/Kokolores/Protocol/DovecotAuth.pm
Criterion Covered Total %
statement 9 93 9.6
branch 0 18 0.0
condition 0 6 0.0
subroutine 3 13 23.0
pod 0 9 0.0
total 12 139 8.6


line stmt bran cond sub pod time code
1             package Auth::Kokolores::Protocol::DovecotAuth;
2              
3             # read dovecot wiki for protocol specs:
4             # http://wiki2.dovecot.org/Authentication%20Protocol
5             # http://wiki2.dovecot.org/Authentication/Mechanisms
6              
7 1     1   3 use Moose;
  1         1  
  1         10  
8             extends 'Auth::Kokolores::Protocol';
9              
10             # ABSTRACT: dovecot auth protocol implementation for kokolores
11             our $VERSION = '1.01'; # VERSION
12              
13 1     1   4324 use Auth::Kokolores::Request;
  1         2  
  1         17  
14              
15 1     1   469 use MIME::Base64;
  1         542  
  1         980  
16              
17             has 'major_version' => ( is => 'ro', isa => 'Int', default => 1 );
18             has 'minor_version' => ( is => 'ro', isa => 'Int', default => 1 );
19              
20             has 'client_major_version' => ( is => 'rw', isa => 'Maybe[Str]' );
21             has 'client_minor_version' => ( is => 'rw', isa => 'Maybe[Str]' );
22             has 'client_pid' => ( is => 'rw', isa => 'Maybe[Str]' );
23              
24             sub read_command {
25 0     0 0   my ( $self, $expected ) = @_;
26 0           my $line = $self->handle->getline;
27 0           $line =~ s/[\r\n]*$//;
28 0           my @fields = split("\t", $line);
29 0           $self->log(4, 'recv cmd: '.join(', ', @fields));
30 0 0         if( ! defined $fields[0] ) {
31 0           die('protocol error: no command specified on line');
32             }
33 0 0 0       if( defined $expected && $fields[0] ne $expected ) {
34 0           die('protocol error: expected command '.$expected.' got '.$fields[0]);
35             }
36 0           return @fields;
37             }
38              
39             sub send_command {
40 0     0 0   my ( $self, @cmd ) = @_;
41 0           $self->log(4, 'send cmd: '.join(', ', @cmd));
42 0           $self->handle->print( join("\t", @cmd)."\n" );
43 0           return;
44             }
45              
46             sub init_connection {
47 0     0 0   my ( $self ) = @_;
48 0           my ( $cmaj, $cmin, $cpid );
49 0           ( undef, $cmaj, $cmin ) = $self->read_command('VERSION');
50 0           ( undef, $cpid ) = $self->read_command('CPID');
51 0 0         if( $cmaj ne $self->major_version ) {
52 0           die('wrong major protocol version');
53             }
54 0           $self->client_major_version( $cmaj );
55 0           $self->client_minor_version( $cmin );
56 0           $self->client_pid( $cpid );
57 0           $self->send_command('VERSION', $self->major_version, $self->minor_version);
58 0           $self->send_command('SPID', $$);
59 0           foreach my $mech ( keys %{$self->mechanisms} ) {
  0            
60             $self->send_command('MECH', $mech,
61 0           @{$self->mechanisms->{$mech}->{'parameters'}} );
  0            
62             }
63 0           $self->send_command('DONE');
64 0           return;
65             }
66              
67             sub shutdown_connection {
68 0     0 0   my ( $self ) = @_;
69 0           $self->last_auth_id(0);
70             return:
71             }
72              
73             has 'mechanisms' => (
74             is => 'ro', isa => 'HashRef', lazy => 1,
75             default => sub { {
76             'LOGIN' => {
77             parameters => [ 'plaintext '],
78             handler => \&handle_login,
79             },
80             'PLAIN' => {
81             parameters => [ 'plaintext '],
82             handler => \&handle_plain,
83             },
84             } },
85             );
86              
87             has 'last_auth_id' => ( is => 'rw', isa => 'Int', default => 0 );
88              
89             sub read_auth_command {
90 0     0 0   my $self = shift;
91 0           my $cmd = {};
92 0           my ( undef, $id, $mech, @params ) = $self->read_command('AUTH');
93              
94 0           while( my $p = shift @params ) {
95 0 0         if( $p =~ /^resp=/ ) { # everything next is resp
    0          
96 0           my $resp = join("\t", $p, @params);
97 0           $resp = substr($resp, 5);
98 0           $cmd->{'resp'} = $resp;
99 0           last;
100             } elsif( $p =~ /=/ ) {
101 0           my ( $key, $value ) = split('=', $p, 2);
102 0           $cmd->{$key} = $value;
103             } else {
104 0           $cmd->{$p} = 1;
105             }
106             }
107 0           $cmd->{'mech'} = $mech;
108 0           $cmd->{'id'} = $id;
109 0           $self->{'last_auth_id'} = $id;
110              
111 0           return( $cmd );
112             }
113              
114             sub _check_auth_id {
115 0     0     my ( $self, $id ) = @_;
116 0 0 0       if( defined $self->last_auth_id
117             && $self->last_auth_id ne $id ) {
118 0           die('protocol error: missmatch of AUTH ID');
119             }
120 0           return;
121             }
122              
123             sub handle_login {
124 0     0 0   my ( $self, $cmd ) = @_;
125 0           my ( $id, $username, $password );
126              
127 0           $self->send_command('CONT', $self->last_auth_id,
128             encode_base64('Username:'));
129 0           ( undef, $id, $username ) = $self->read_command('CONT');
130 0           $self->_check_auth_id( $id );
131 0           $username = decode_base64( $username );
132              
133 0           $self->send_command('CONT', $self->last_auth_id,
134             encode_base64('Password:'));
135 0           ( undef, $id, $password ) = $self->read_command('CONT');
136 0           $self->_check_auth_id( $id );
137 0           $password = decode_base64( $password );
138              
139 0           return($username, $password, $cmd);
140             }
141              
142             sub handle_plain {
143 0     0 0   my ( $self, $cmd ) = @_;
144 0 0         if( ! defined $cmd->{'resp'} ) {
145 0           die('protocol error: AUTH PLAIN request without resp= parameter');
146             }
147 0           $cmd->{'resp'} = decode_base64( $cmd->{'resp'} );
148 0           my ( $authzid, $authcid, $passwd ) = split("\0", $cmd->{'resp'});
149 0           $cmd->{'authzid'} = $authzid;
150 0           return( $authcid, $passwd, $cmd );
151             }
152              
153             sub read_request {
154 0     0 0   my $self = shift;
155              
156 0           my $cmd = $self->read_auth_command;
157 0           my $mech = $self->mechanisms->{ $cmd->{'mech'} };
158 0 0         if( ! defined $mech ) {
159 0           die('mechanism is not supported');
160             }
161             my ( $username, $password, $params )
162 0           = $mech->{'handler'}->( $self, $cmd );
163              
164 0           return Auth::Kokolores::Request->new(
165             username => $username,
166             password => $password,
167             parameters => $params,
168             server => $self->server,
169             );
170             }
171              
172             sub write_response {
173 0     0 0   my ( $self, $response ) = @_;
174 0           my $cmd = 'FAIL';
175 0 0         if( $response->success ) {
176 0           $cmd = 'OK';
177             }
178              
179 0           $self->send_command($cmd, $self->last_auth_id);
180 0           return;
181             }
182              
183             1;
184              
185             __END__
186              
187             =pod
188              
189             =encoding UTF-8
190              
191             =head1 NAME
192              
193             Auth::Kokolores::Protocol::DovecotAuth - dovecot auth protocol implementation for kokolores
194              
195             =head1 VERSION
196              
197             version 1.01
198              
199             =head1 AUTHOR
200              
201             Markus Benning <ich@markusbenning.de>
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is Copyright (c) 2016 by Markus Benning <ich@markusbenning.de>.
206              
207             This is free software, licensed under:
208              
209             The GNU General Public License, Version 2, June 1991
210              
211             =cut