Download | Plain Text | No Line Numbers


  1. #!/usr/bin/perl
  2. #
  3. # Copyright (C) 2007 Manuel Mausz (manuel@mausz.at)
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later
  9. # version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software Foundation,
  18. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19.  
  20. use strict;
  21. use warnings;
  22. our $VERSION = '0.1';
  23.  
  24. use Mail::DKIM 0.29;
  25. use Mail::DKIM::Signer;
  26.  
  27. # enable support for "pretty" signatures, if available
  28. eval 'require Mail::DKIM::TextWrap';
  29.  
  30. =head
  31. config file structure
  32.  - missing settings will be merged from global
  33.  - domain-entry will also match its subdomains
  34.  - specify "none" in types-tag to omit signing
  35.  
  36. <dkimsign>
  37.   <global algorithm="rsa-sha256" domain="/var/qmail/control/me" keyfile="/var/qmail/control/dkim/global.key" method="simple" selector="beta">
  38.   <types>dkim</types>
  39.   </global>
  40.   <example.com selector="beta2">
  41.   <types>dkim</types>
  42.   <types>domainkey</types>
  43.   </example.com>
  44. </dkimsign>
  45. =cut
  46.  
  47. my $configfile = undef;
  48. $configfile = '/var/qmail/control/dkim/signconf.xml';
  49. my $debugfile = undef;
  50. #$debugfile = '/tmp/dkim.debug';
  51. my $qremote = '/var/qmail/bin/qmail-remote.orig';
  52. my $binary = 0;
  53. our $config;
  54. $config->{'global'} = {
  55. types => 'dkim',
  56. keyfile => '/var/qmail/control/dkim/global.key',
  57. algorithm => 'rsa-sha256',
  58. method => 'simple',
  59. selector => 'beta',
  60. # either string or file (first line of file will be used)
  61. domain => '/var/qmail/control/me'
  62. };
  63.  
  64. #-------------------------------------------------------------------------------
  65.  
  66. # read config file. safely
  67. if (defined($configfile) && -r $configfile)
  68. {
  69. eval 'use XML::Simple';
  70. if (!$@)
  71. {
  72. my $xmlconf;
  73. eval { $xmlconf = XMLin($configfile, ForceArray => 0); };
  74. qexit_deferral('Unable to read config file: ', $@)
  75. if ($@);
  76. ConfigMerge::merge($config, $xmlconf);
  77. }
  78. }
  79.  
  80. # open debug file
  81. my $debugfh = undef;
  82. if (defined($debugfile))
  83. {
  84. open($debugfh, '>', $debugfile)
  85. or qexit_deferral('Unable to open ', $debugfile, ' to writing: ', $!);
  86. }
  87.  
  88. # generate signatures
  89. my $dkim;
  90. my $mailbuf = '';
  91. eval
  92. {
  93. my $conf = $config->{'global'};
  94. $dkim = Mail::DKIM::Signer->new(
  95. Policy => 'MySignerPolicy',
  96. Debug_Canonicalization => $debugfh
  97. );
  98.  
  99. if ($binary)
  100. {
  101. binmode STDIN;
  102. }
  103.  
  104. while (<STDIN>)
  105. {
  106. $mailbuf .= $_;
  107. unless ($binary)
  108. {
  109. chomp $_;
  110. s/\015?$/\015\012/s;
  111. }
  112. $dkim->PRINT($_);
  113. }
  114. $dkim->CLOSE();
  115. };
  116. qexit_deferral('Error while signing: ', $@)
  117. if ($@);
  118.  
  119. # close debug file
  120. close($debugfh)
  121. if (defined($debugfh));
  122.  
  123. # execute qmail-remote
  124. open(QR, '|'.$qremote.' '.join(' ', @ARGV))
  125. or qexit_deferral('Unable to run qmail-remote: ', $!);
  126. foreach my $dkim_signature ($dkim->signatures)
  127. {
  128. my $sig = $dkim_signature->as_string;
  129. $sig =~ s/\015\012\t/\012\t/g;
  130. print QR $sig."\012";
  131. }
  132. print QR $mailbuf;
  133. close(QR);
  134.  
  135. #-------------------------------------------------------------------------------
  136.  
  137. sub qexit
  138. {
  139. print @_, "\0";
  140. exit(0);
  141. }
  142.  
  143. sub qexit_deferral
  144. {
  145. return qexit('Z', @_);
  146. }
  147.  
  148. sub qexit_failure
  149. {
  150. return qexit('D', @_);
  151. }
  152.  
  153. sub qexit_success
  154. {
  155. return qexit('K', @_);
  156. }
  157.  
  158. #-------------------------------------------------------------------------------
  159.  
  160. package ConfigMerge;
  161. our $VERSION = '0.1';
  162.  
  163. # merge config hashes. arrays and scalars will be copied.
  164. sub merge
  165. {
  166. my ($left, $right) = @_;
  167. foreach my $rkey (keys(%$right))
  168. {
  169. my $rtype = ref($right->{$rkey}) eq 'HASH' ? 'HASH'
  170. : ref($right->{$rkey}) eq 'ARRAY' ? 'ARRAY'
  171. : 'SCALAR';
  172. if (!defined($left->{$rkey}) || $rtype ne 'HASH')
  173. {
  174. $left->{$rkey} = $right->{$rkey};
  175. }
  176. else
  177. {
  178. merge($left->{$rkey}, $right->{$rkey});
  179. }
  180. }
  181. return;
  182. }
  183.  
  184. #-------------------------------------------------------------------------------
  185.  
  186. package MySignerPolicy;
  187. use Mail::DKIM::SignerPolicy;
  188. use base 'Mail::DKIM::SignerPolicy';
  189. use Mail::DKIM::Signature;
  190. use Mail::DKIM::DkSignature;
  191. use Carp;
  192. our $VERSION = '0.1';
  193.  
  194. sub apply
  195. {
  196. my ($self, $signer) = @_;
  197. my $domain = undef;
  198. $domain = lc($signer->message_sender->host)
  199. if (defined($signer->message_sender));
  200.  
  201. # merge configs
  202. while($domain)
  203. {
  204. if (defined($config->{$domain}))
  205. {
  206. ConfigMerge::merge($config->{'global'}, $config->{$domain});
  207. last;
  208. }
  209. (undef, $domain) = split(/\./, $domain, 2);
  210. }
  211.  
  212. my $conf = $config->{'global'};
  213. return 0
  214. if (!defined($conf->{'types'}) || ($conf->{'types'}) eq 'none');
  215.  
  216. # set key file
  217. $signer->key_file($conf->{'keyfile'});
  218.  
  219. # parse (signature) domain
  220. if (substr($conf->{'domain'}, 0, 1) eq '/')
  221. {
  222. open(FH, '<', $conf->{'domain'})
  223. or croak('Unable to open domain-file: '.$!);
  224. my $newdom = (split(/ /, <FH>))[0];
  225. close(FH);
  226. croak("Unable to read domain-file. Maybe empty file.")
  227. if (!$newdom);
  228. chomp($newdom);
  229. $conf->{'domain'} = $newdom;
  230. }
  231.  
  232. # generate signatures
  233. my $types = (ref($conf->{'types'}) eq 'ARRAY') ? $conf->{'types'} : [ $conf->{'types'} ];
  234. foreach my $type (@$types)
  235. {
  236. if ($type eq 'dkim')
  237. {
  238. $signer->add_signature(
  239. new Mail::DKIM::Signature(
  240. Algorithm => $conf->{'algorithm'} || $signer->algorithm,
  241. Method => $conf->{'method'} || $signer->method,
  242. Headers => $conf->{'headers'} || $signer->headers,
  243. Domain => $conf->{'domain'} || $signer->domain,
  244. Selector => $conf->{'selector'} || $signer->selector,
  245. Query => $conf->{'query'},
  246. Identity => $conf->{'identity'},
  247. Expiration => $conf->{'expiration'}
  248. )
  249. );
  250. }
  251. elsif ($type eq 'domainkey')
  252. {
  253. $signer->add_signature(
  254. new Mail::DKIM::DkSignature(
  255. Algorithm => 'rsa-sha1', # only rsa-sha1 supported
  256. Method => $conf->{'method'} || $signer->method,
  257. Headers => $conf->{'headers'} || $signer->headers,
  258. Domain => $conf->{'domain'} || $signer->domain,
  259. Selector => $conf->{'selector'} || $signer->selector,
  260. Query => $conf->{'query'}
  261. )
  262. );
  263. }
  264. }
  265.  
  266. return 1;
  267. }
  268.