Commit | Line | Data |
f5baca29 |
1 | package Promulger::Dispatch; |
9e4ca637 |
2 | use Moo; |
9e4ca637 |
3 | use autodie ':all'; |
018a0a65 |
4 | use Scalar::Util 'blessed'; |
d16aba7a |
5 | use Try::Tiny; |
f5baca29 |
6 | |
8ad89cb2 |
7 | use Email::Address; |
b179831b |
8 | use Email::MIME; |
9e4ca637 |
9 | use Email::Sender::Simple (); |
9f8395b9 |
10 | # XXX not yet -- apeiron, 2010-06-25 |
11 | #use Mail::Verp; |
f5baca29 |
12 | |
21baaef0 |
13 | use Promulger::Config; |
d16aba7a |
14 | use Promulger::List; |
21baaef0 |
15 | |
9e4ca637 |
16 | has transport => ( |
17 | is => 'rw', |
18 | isa => sub { |
19 | my $proto = $_[0]; |
20 | blessed $proto and |
21 | $proto->can('does') and |
22 | $proto->does('Email::Sender::Transport') |
23 | or die "transport must do Email::Sender::Transport role"; |
24 | }, |
25 | default => sub { |
018a0a65 |
26 | my $config = Promulger::Config->config; |
27 | my $class; |
28 | if($class = $config->{mailer}) { |
29 | if($class !~ /::/) { |
30 | $class = "Email::Sender::Transport::${class}"; |
31 | } |
32 | } else { |
33 | $class = 'Email::Sender::Transport::Sendmail'; |
34 | } |
35 | Class::MOP::load_class($class); |
36 | $class->new; |
9e4ca637 |
37 | }, |
38 | ); |
39 | |
4a69a584 |
40 | sub dispatch { |
41 | my ($self, $message) = @_; |
21baaef0 |
42 | my $config = Promulger::Config->config; |
f5baca29 |
43 | |
b179831b |
44 | my $email = Email::MIME->new($message); |
f7a55eac |
45 | my $recipient = $email->header('To'); |
9e4ca637 |
46 | my $local_user = $self->user_for_address($recipient); |
f7a55eac |
47 | my $sender = $email->header('From'); |
48 | my $subject = $email->header('Subject'); |
f5baca29 |
49 | |
8ad89cb2 |
50 | my $list = Promulger::List->resolve($local_user); |
f5baca29 |
51 | unless($list) { |
9e4ca637 |
52 | $self->reject($recipient, $sender); |
8ad89cb2 |
53 | return; |
f5baca29 |
54 | } |
55 | |
8ad89cb2 |
56 | if($local_user =~ /-request$/) { |
9e4ca637 |
57 | $self->handle_request($list, $sender, $local_user, $subject, $config); |
8ad89cb2 |
58 | return; |
f5baca29 |
59 | } |
60 | |
61 | # they don't have a request for us, so they want to post a message |
9e4ca637 |
62 | $self->post_message($list, $email, $config); |
8ad89cb2 |
63 | return; |
f5baca29 |
64 | } |
65 | |
4a69a584 |
66 | sub handle_request { |
67 | my ($self, $list, $sender, $recipient, $subject) = @_; |
9e4ca637 |
68 | my $sender_address = $self->bare_address($sender); |
8ad89cb2 |
69 | if($subject =~ /^\s*subscribe/i) { |
70 | $list->subscribe($sender_address) |
9e4ca637 |
71 | or $self->already_subscribed($list, $recipient, $sender_address); |
8ad89cb2 |
72 | } elsif($subject =~ /^\s*unsubscribe/i) { |
73 | $list->unsubscribe($sender_address) |
9e4ca637 |
74 | or $self->not_subscribed($list, $recipient, $sender_address); |
f5baca29 |
75 | } |
76 | } |
77 | |
d16aba7a |
78 | # XXX this needs to be better -- apeiron, 2012-07-18 |
79 | sub handle_bounce { |
80 | my ($self, $raw_message) = @_; |
81 | my $message = Email::MIME->new($raw_message); |
82 | require Mail::DeliveryStatus::BounceParser; |
83 | my $bounce; |
84 | my $recipient = $message->header('To'); |
85 | my $recipient_address = Email::Address->parse($recipient); |
86 | try { |
87 | my $bounce = Mail::DeliveryStatus::BounceParser->new($message); |
88 | } catch { |
89 | my $domain = $recipient_address->host; |
90 | my $redirect = Email::MIME->create( |
91 | header_str => [ |
92 | From => $message->header('From'), |
93 | To => "postmaster@${domain}", |
94 | Subject => $message->header('Subject'), |
95 | ], |
96 | body_str => $message->body_str, |
97 | ); |
98 | $self->send_message($redirect); |
99 | }; |
100 | my @addresses = $bounce->addresses; |
101 | my $list = Promulger::List->resolve($recipient_address->address); |
102 | for my $addr (@addresses) { |
103 | my $a = Email::Address->parse($addr); |
104 | my $raw_address = $a->address; |
105 | $list->unsubscribe($raw_address); |
106 | } |
107 | } |
108 | |
4a69a584 |
109 | sub post_message { |
110 | my ($self, $list, $email, $config) = @_; |
f7a55eac |
111 | my $sender = $email->header('From'); |
9e4ca637 |
112 | my $sender_address = $self->bare_address($sender); |
f7a55eac |
113 | my $recipient = $email->header('To'); |
f5baca29 |
114 | |
8ad89cb2 |
115 | unless($list->accept_posts_from($sender_address) && $list->active) { |
9e4ca637 |
116 | $self->reject($recipient, $sender); |
8ad89cb2 |
117 | return; |
118 | } |
f5baca29 |
119 | |
f5baca29 |
120 | # XXX no MIME or other fancy handling for now -- apeiron, 2010-03-13 |
b179831b |
121 | my $body = $email->body_str; |
8ad89cb2 |
122 | for my $subscriber (keys %{$list->subscribers}) { |
f7a55eac |
123 | # my $verped_from = Mail::Verp->encode($recipient, $subscriber); |
124 | |
f5baca29 |
125 | # XXX we let the MTA create the message-id for us for now -- apeiron, |
126 | # 2010-03-13 |
b179831b |
127 | my $new_message = Email::MIME->create( |
f5baca29 |
128 | header => [ |
e9b4d3bc |
129 | From => $sender_address, |
130 | To => $subscriber, |
f7a55eac |
131 | Subject => $email->header('Subject'), |
e9b4d3bc |
132 | 'Reply-to' => $recipient, |
f5baca29 |
133 | ], |
134 | body => $body, |
135 | ); |
136 | # XXX no queuing or job distribution for now beyond what the MTA provides |
137 | # -- apeiron, 2010-03-13 |
9e4ca637 |
138 | $self->send_message($new_message); |
f5baca29 |
139 | } |
140 | } |
141 | |
4a69a584 |
142 | sub send_message { |
143 | my ($self, $message) = @_; |
9e4ca637 |
144 | Email::Sender::Simple::sendmail( |
f7a55eac |
145 | $message, |
146 | { |
9e4ca637 |
147 | transport => $self->transport, |
f7a55eac |
148 | } |
149 | ); |
150 | } |
151 | |
21baaef0 |
152 | # XXX make this actually not suck -- apeiron, 2010-03-13 |
4a69a584 |
153 | sub reject { |
154 | my ($self, $recipient, $sender) = @_; |
b179831b |
155 | my $email = Email::MIME->create( |
21baaef0 |
156 | header => [ |
157 | From => $recipient, |
158 | To => $sender, |
159 | Subject => 'Rejected', |
160 | ], |
161 | body => <<BODY, |
162 | Sorry, your message to $recipient has been denied. |
163 | BODY |
164 | ); |
9e4ca637 |
165 | $self->send_message($email); |
21baaef0 |
166 | } |
167 | |
4a69a584 |
168 | sub not_subscribed { |
169 | my ($self, $list, $recipient, $sender) = @_; |
b179831b |
170 | my $email = Email::MIME->create( |
8ad89cb2 |
171 | # XXX need admin address |
21baaef0 |
172 | header => [ |
8ad89cb2 |
173 | From => $recipient, |
21baaef0 |
174 | To => $sender, |
175 | Subject => 'Not subscribed', |
176 | ], |
177 | body => <<BODY, |
8ad89cb2 |
178 | Sorry, you are not subscribed to $list. |
21baaef0 |
179 | BODY |
180 | ); |
9e4ca637 |
181 | $self->send_message($email); |
21baaef0 |
182 | } |
183 | |
4a69a584 |
184 | sub already_subscribed { |
185 | my ($self, $list, $recipient, $sender) = @_; |
b179831b |
186 | my $email = Email::MIME->create( |
21baaef0 |
187 | header => [ |
8ad89cb2 |
188 | From => $recipient, |
21baaef0 |
189 | To => $sender, |
190 | Subject => 'Already subscribed', |
191 | ], |
192 | body => <<BODY, |
8ad89cb2 |
193 | Sorry, you are already subscribed to $list. |
21baaef0 |
194 | BODY |
195 | ); |
9e4ca637 |
196 | $self->send_message($email); |
21baaef0 |
197 | } |
f5baca29 |
198 | |
4a69a584 |
199 | sub bare_address { |
200 | my ($self, $full_addr) = @_; |
8ad89cb2 |
201 | my ($addr_obj) = Email::Address->parse($full_addr); |
202 | return $addr_obj->address; |
203 | } |
204 | |
4a69a584 |
205 | sub user_for_address { |
206 | my ($self, $full_addr) = @_; |
8ad89cb2 |
207 | my ($addr_obj) = Email::Address->parse($full_addr); |
208 | return $addr_obj->user; |
209 | } |
210 | |
f5baca29 |
211 | 'http://www.shadowcat.co.uk/blog/matt-s-trout/oh-subdispatch-oh-subdispatch/'; |