gpgmime

Create mail agent parseable email from a PGP message
git clone git://git.defalsify.org/gigmime.git
Log | Files | Refs | LICENSE

main.rs (6191B)


      1 #![crate_name = "gpgmime"]
      2 
      3 //! gpgme creates multipart/encrypted emails from PGP messages.
      4 //!
      5 //! This multipart/encrypted structure can be successfully parsed 
      6 //! and decrypted inline by my favorite MUA, [mutt](http://mutt.org).
      7 //!
      8 //! Checking that it works - or getting it to work - with more PGP
      9 //! capable MUAs wouldn't hurt.
     10 //!
     11 //! 
     12 //! ## Compatibility
     13 //!
     14 //! The tool parses both `.gpg` and `.asc` as input.
     15 //!
     16 //!
     17 //! ## Example
     18 //!
     19 //! Let the follwing be the *ciphertext* stored in a file `msg.asc`
     20 //!
     21 //! ``` ignore,
     22 //! -----BEGIN PGP MESSAGE-----
     23 //!
     24 //! hQGMA1IUfpa99cfFAQv/YrkKsUAo+jlSj3u+pSTcx+eNtCRgv3LHUUV1O3BpDMGp
     25 //! tJYQrq+tRqhT29jnEZdJ+engC/gUHZYGOCburWFIKkStH1G4x5V4AWICtDcozPpK
     26 //! ENIihA3kncbMsJrFcpgX4wmqA6c28ao9fzEGPcGWvA1jUV4g6qukQ2lOYgbmK2O6
     27 //! wD5omaKLBguNVW6/PcTQ32kP4sGKwzS5B9R1X0FY7e3HJxMy0lnOpNQRY+g5AJZ0
     28 //! ryncq+PPUYmjULCtr/BPm8idX2TBStx+1iqlvYQiW5x3tQIocWARWqILtNeYdHZF
     29 //! a1CRSekj1bw4o5RzkJq92a9XqyiKlqna7X+W6E+59ZvoVUM3KgzCQ0MJkv1yQCo1
     30 //! VX9kLoQ1ia658rsDg0YCZUyJ/kvD3z5gcLEL9/WRhlhbwfeUa6pkUUtk3TbAjqdb
     31 //! GTj7wJDMUdA+Uuo5U5gglEL+Fl7wjDa4GnfTudtcfG0ImAoW433DstVp4Z7Qk2cW
     32 //! uAprBVHHJSR+fNjszWhU0j8BxgsH/FeK87rbCgPvzZ1xWan4kdCfZXWrAt6ZV90U
     33 //! Ic3i1AcP/R4tZ5oOxpLBXjkuXaOxI7YQ7LLI25t/Udo=
     34 //! =YHyf
     35 //! -----END PGP MESSAGE-----
     36 //! ```
     37 //! 
     38 //! An example command and output is:
     39 //!
     40 //! ```
     41 //! From: "foo@bar.com" <foo@bar.com>
     42 //! To: "merman@greyskull.com" <merman@greyskull.com>
     43 //! Subject: Skeletor is looking a bit pale
     44 //! Message-ID: <171d5d0aa79de7cf.2c1958e5681b1f5f.1dc0ff8081f726a3@piano>
     45 //! Date: Wed, 12 Oct 2022 15:48:57 +0000
     46 //! Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; 
     47 //!         boundary="171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3"
     48 //! Content-Disposition: inline
     49 //!
     50 //!
     51 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3
     52 //! Content-Type: application/pgp-encrypted
     53 //! Content-Transfer-Encoding: base64
     54 //!
     55 //! VmVyc2lvbjogMQ==
     56 //!
     57 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3
     58 //! Content-Type: application/octet-stream; charset="utf-8"
     59 //! Content-Transfer-Encoding: 7bit
     60 //!
     61 //! -----BEGIN PGP MESSAGE-----
     62 //!
     63 //! wcDMA1IUfpa99cfFAQv/ReJTs3mkbFYI9ifay1jhL1XW5u9cDMFTymdxMtWUhJvl
     64 //! AM5AgWPSnwfVVaS9Ger2robtLsR5UeAjNSOGXg8Vpu7SsbQGsDXdScCgvwEmSPPU
     65 //! atJ+qdw/wPKR2GTD41hypJDsIeoT9l9lfSIBgzXdu+PNrryLFzqpI+q8y3KMOjTG
     66 //! W52lvulIhYrFgRzhhOaX8Ss3Wnx7j1+4KXsvk0VY+rTdM0krsmEgzXHfHGECgKga
     67 //! zlYQ3c4OhpnsmYi0+rpzCqbBrbtxDdMuBif7nbzYettcrkQssRuS41mFHHYuJWu4
     68 //! A8fkoFmjlz5NVEXZSdsnMyPV6lSE9IcllUuFzKXPAGBy3xNay7JdcPU/1C7iylOh
     69 //! M9mrfoe1gpPRefwXrjkiVuJ/uhYwgCxcIdzmgJ7XBtoBvZzNt440pP+bZqCGJgPt
     70 //! HlmBvtGY5F6uopRC1e+PH7a+vIYss5rmJ4OQGdvWRgj2RDPo9cIfAsU6sFP530T6
     71 //! ErzvOCXiYDv+9IPy7LzG0j8BGdQeIVYbHWRYxc3aQhlfSDvR6WYMWAnmXNZYUiEW
     72 //! QS34XWww8FHxBGWnj8DbVXuvVYdjeiSteTdDmC4fsUY=
     73 //! =dhci
     74 //! -----END PGP MESSAGE-----
     75 //!
     76 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3--
     77 //!
     78 //! ```
     79 //!
     80 //!
     81 //! ## Shortcomings
     82 //!
     83 //! - The application/pgp-encrypted field will contain `Version: 1`
     84 //! no matter the version of the message.
     85 
     86 use std::io::Write;
     87 
     88 use sequoia_openpgp::parse::Parse;
     89 use sequoia_openpgp::Message;
     90 use sequoia_openpgp::serialize::SerializeInto;
     91 use sequoia_openpgp::armor::Writer as ArmorWriter;
     92 use sequoia_openpgp::armor::Kind;
     93 use mail_builder::MessageBuilder;
     94 use mail_builder::mime::MimePart;
     95 use clap::App;
     96 use clap::Arg;
     97 
     98 struct Settings {
     99     to: String,
    100     from: String,
    101     subject: String,
    102     path: String,
    103 }
    104 
    105 impl Settings {
    106     fn from_args() -> Settings {
    107         let mut o = App::new("gpgmime");
    108         o = o.version(env!("CARGO_PKG_VERSION"));
    109         o = o.author(env!("CARGO_PKG_AUTHORS"));
    110         o = o.arg(
    111             Arg::with_name("to")
    112                 .long("to")
    113                 .short("t")
    114                 .value_name("Email recipient")
    115                 .takes_value(true)
    116                 .required(true)
    117                 );
    118         o = o.arg(
    119             Arg::with_name("from")
    120                 .long("from")
    121                 .short("f")
    122                 .value_name("Email sender")
    123                 .takes_value(true)
    124                 .required(true)
    125                 );
    126         o = o.arg(
    127             Arg::with_name("subject")
    128                 .long("subject")
    129                 .short("s")
    130                 .value_name("Email subject")
    131                 .takes_value(true)
    132                 .required(true)
    133                 );
    134         o = o.arg(
    135             Arg::with_name("PATH")
    136                 .help("Path to PGP message")
    137                 .required(true)
    138                 );
    139         let arg = o.get_matches();
    140         let settings = Settings{
    141             to: arg.value_of("to").unwrap().to_string(),
    142             from: arg.value_of("from").unwrap().to_string(),
    143             subject: arg.value_of("subject").unwrap().to_string(),
    144             path: arg.value_of("PATH").unwrap().to_string(),
    145         };
    146         settings
    147     }
    148 }
    149 
    150 
    151 #[doc(hidden)]
    152 fn main() {
    153         let settings = Settings::from_args();
    154 
    155         let msg = Message::from_file(settings.path).unwrap();
    156 
    157         let mut msg_w = ArmorWriter::new(Vec::new(), Kind::Message).unwrap();
    158         let _c = msg_w.write_all(msg.to_vec().unwrap().as_ref());
    159         let msg_b = msg_w.finalize().unwrap();
    160         let msg_s = String::from_utf8_lossy(&msg_b);
    161 
    162         let enc_version_part = MimePart::new_binary(
    163             "application/pgp-encrypted",
    164             "Version: 1".as_bytes(),
    165             );
    166 
    167         let enc_contents_part = MimePart::new_text_other(
    168             "application/octet-stream",
    169             msg_s,
    170             );
    171 
    172         let enc_envelope = MimePart::new_multipart(
    173             "multipart/encrypted; protocol=\"application/pgp-encrypted\"",
    174             vec![
    175                 enc_version_part,
    176                 enc_contents_part,
    177             ],
    178             )
    179             .inline();
    180 
    181         let subject: &str = settings.subject.as_ref();
    182         let eml = MessageBuilder::new()
    183             .from((settings.from.as_ref(), settings.from.as_ref()))
    184             .to((settings.to.as_ref(), settings.to.as_ref()))
    185             .subject(subject)
    186             .body(enc_envelope);
    187 
    188         println!("{}", eml.write_to_string().unwrap());
    189 }