├── notmuch-resync.py ├── README.md ├── decrypt-notmuch-emails └── decrypt-email.pl /notmuch-resync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from notmuch import * 3 | import os 4 | import sys 5 | 6 | db = Database(path=None, mode=Database.MODE.READ_WRITE) 7 | for line in sys.stdin: 8 | line=line.rstrip('\n') 9 | if not os.path.isfile(line): 10 | print('"'+line+'" does not exist!') 11 | continue 12 | print('"'+line+'"') 13 | print(db.remove_message(line)) 14 | print(db.add_message(line)) 15 | db.close() 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maildir-decrypt 2 | 3 | A collection of scripts that can be used to 4 | 5 | * convert pgp encrypted email files into clear text email files (preferably in a maildir folder) 6 | * automatically replace encrypted email files in a maildir with the corresponding decrypted files 7 | * update the notmuch database by indexing the changed content 8 | 9 | This "works for me". These scripts are potentially dangerous and can destroy / delete your emails. 10 | This is why I don't write down a tutorial here. Read the scripts, understand what they do, do backups, test the scripts first in a mode that does not modify your source maildir folders... 11 | 12 | Patches are welcome :-) 13 | 14 | -------------------------------------------------------------------------------- /decrypt-notmuch-emails: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p mail/{new,cur,tmp} 3 | mkdir backup 4 | 5 | declare -A failed 6 | while read line ; do 7 | failed["$line"]="x" 8 | done < failed.txt 9 | declare -A success 10 | while read line ; do 11 | success["$line"]="x" 12 | done < success.txt 13 | 14 | for file in `notmuch search --output=files tag:encrypted or "-----BEGIN PGP MESSAGE-----"` ; do 15 | if [ -n "${failed[$file]}" ]; then 16 | echo "$file already failed" 17 | continue 18 | fi 19 | if [ -n "${success[$file]}" ]; then 20 | echo "$file already succeeded" 21 | continue 22 | fi 23 | echo ">> at ${file}:" 24 | ./decrypt-email.pl < "$file" >tmp.eml 25 | if [ $? -eq 0 ]; then 26 | echo moving 27 | echo ${file} >>success.txt 28 | cp -v ${file} backup/ 29 | mv -v tmp.eml ${file}; 30 | # mv -v tmp.eml mail/new/`basename ${file}`; 31 | else 32 | echo ${file} >>failed.txt 33 | echo "${file} could not be decrypted, skipping" 34 | fi 35 | done 36 | 37 | -------------------------------------------------------------------------------- /decrypt-email.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use Mail::GnuPG; 3 | use MIME::Parser; 4 | #use Data::Dumper; 5 | 6 | use strict; 7 | 8 | 9 | sub stdin { 10 | my $stdin = ''; 11 | 12 | while(){ 13 | $stdin .= $_; 14 | } 15 | if ($stdin eq '') { 16 | die ("Error: stdin appears to have no content."); 17 | } 18 | return $stdin; 19 | } 20 | 21 | my $stdin = &stdin(); 22 | 23 | my $parser = MIME::Parser->new; 24 | $parser->output_to_core(1); 25 | my $tmpdir = '/tmp'; 26 | my $source; 27 | 28 | $parser->output_under($tmpdir); 29 | 30 | eval { $source = $parser->parse_data($stdin) }; 31 | 32 | #print Dumper($source); 33 | 34 | my $gnupg = new Mail::GnuPG(passphrase => $ENV{'GPG_PASSPHRASE'}); 35 | 36 | if (not $gnupg->is_encrypted($source)){ 37 | my $info=$source->head->get('from')." to ".$source->head->get('to').":".$source->head->get('subject'); 38 | $info =~ s/[\r\n]+//g; 39 | warn($info."\n"); 40 | die("is not encrypted\n"); 41 | } 42 | 43 | my ($return,$keyid,$uid) = $gnupg->decrypt($source); 44 | if ($return != 0){ 45 | # print Dumper(@retval); 46 | die("could not decrypt\n"); 47 | } 48 | 49 | my $decrypted=$gnupg->{decrypted}; 50 | 51 | #print Dumper($decrypted); 52 | 53 | my $head=$source->head(); 54 | if ($decrypted->effective_type eq "multipart/mixed" || defined $decrypted->preamble){ 55 | $head->replace('Content-Type','multipart/mixed; boundary="'.$head->multipart_boundary.'"'); 56 | $decrypted->head($head); 57 | $decrypted->sync_headers( 58 | 'Length' => 'COMPUTE', 59 | 'Nonstandard' => 'ERASE' 60 | ); 61 | } else { 62 | $head->replace('Content-Transfer-Encoding', $decrypted->head()->mime_encoding); 63 | my $charset=""; 64 | if ($source->mime_type eq "multipart/encrypted"){ 65 | $charset=$decrypted->head()->mime_attr('content-type.charset'); 66 | } else { 67 | $charset=$source->head()->mime_attr('content-type.charset'); 68 | } 69 | $head->replace('Content-Type', $decrypted->mime_type()."; charset=".$charset); 70 | $decrypted->head($head); 71 | } 72 | 73 | if(length($decrypted->stringify_body())==0){ 74 | die("body lenght is 0"); 75 | } 76 | $decrypted->print(\*STDOUT); 77 | 78 | $source->purge(); 79 | $decrypted->purge(); 80 | 81 | --------------------------------------------------------------------------------