crypt: use AssuanClient.send_fds() to send file descriptors to gpgme-tool.
[pgp-mime.git] / bin / send-pgp-mime.py
1 #!/usr/bin/env python3.3
2 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
3 #
4 # This file is part of pgp-mime.
5 #
6 # pgp-mime is free software: you can redistribute it and/or modify it under the
7 # terms of the GNU General Public License as published by the Free Software
8 # Foundation, either version 3 of the License, or (at your option) any later
9 # version.
10 #
11 # pgp-mime is distributed in the hope that it will be useful, but WITHOUT ANY
12 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # pgp-mime.  If not, see <http://www.gnu.org/licenses/>.
17
18 """Scriptable PGP MIME email using ``gpg``.
19
20 You can use ``gpg-agent`` for passphrase caching if your key requires
21 a passphrase (it better!).  Example usage would be to install
22 ``gpg-agent``, and then run::
23
24   $ export GPG_TTY=`tty`
25   $ eval $(gpg-agent --daemon)
26
27 in your shell before invoking this script.  See ``gpg-agent(1)`` for
28 more details.
29 """
30
31 import codecs as _codecs
32 import configparser as _configparser
33 import logging as _logging
34 import mimetypes as _mimetypes
35 import os.path as _os_path
36 import sys as _sys
37
38 from email.encoders import encode_base64 as _encode_base64
39 from email.mime.application import MIMEApplication as _MIMEApplication
40 from email.mime.audio import MIMEAudio as _MIMEAudio
41 from email.mime.image import MIMEImage as _MIMEImage
42 from email.mime.multipart import MIMEMultipart as _MIMEMultipart
43 from email.mime.nonmultipart import MIMENonMultipart as _MIMENonMultipart
44
45 import pgp_mime as _pgp_mime
46
47
48 STDIN_USED = False
49
50
51 def read_file(filename=None, encoding='us-ascii'):
52     global STDIN_USED
53     if filename == '-':
54         assert STDIN_USED == False, STDIN_USED
55         STDIN_USED = True
56         return _sys.stdin.read()
57     if filename:
58         return _codecs.open(filename, 'r', encoding).read()
59     raise ValueError('neither filename nor descriptor given for reading')
60
61 def load_attachment(filename, encoding='us-ascii'):
62     mimetype,content_encoding = _mimetypes.guess_type(filename)
63     if mimetype is None or content_encoding is not None:
64         mimetype = 'application/octet-stream'
65     maintype,subtype = mimetype.split('/', 1)
66     _pgp_mime.LOG.info('loading attachment {} as {} ({})'.format(
67             filename, mimetype, content_encoding))
68     if maintype == 'text':
69         text = read_file(filename=filename, encoding=encoding)
70         attachment = _pgp_mime.encodedMIMEText(text)
71         del attachment['content-disposition']
72     else:
73         data = open(filename, 'rb').read()
74         if maintype == 'application':
75             attachment = _MIMEApplication(data, subtype)
76         elif maintype == 'audio':
77             attachment = _MIMEAudio(data)
78         elif maintype == 'image':
79             attachment = _MIMEImage(data)
80         else:
81             attachment = _MIMENonMultipary(maintype, subtype)
82             attachment.set_payload(data, _encode_base64)
83     attachment.add_header(
84         'Content-Disposition', 'attachment', filename=filename)
85     return attachment
86
87
88 if __name__ == '__main__':
89     import argparse
90
91     doc_lines = __doc__.splitlines()
92     parser = argparse.ArgumentParser(
93         description = doc_lines[0],
94         epilog = '\n'.join(doc_lines[1:]).strip(),
95         version=_pgp_mime.__version__,
96         formatter_class=argparse.RawDescriptionHelpFormatter)
97     parser.add_argument(
98         '-e', '--encoding', metavar='ENCODING', default='utf-8',
99         help='encoding for input files')
100     parser.add_argument(
101         '-H', '--header-file', metavar='FILE',
102         help='file containing email header')
103     parser.add_argument(
104         '-B', '--body-file', metavar='FILE',
105         help='file containing email body')
106     parser.add_argument(
107         '-a', '--attachment', metavar='FILE', action='append',
108         help='add an attachment to your message')
109     parser.add_argument(
110         '-m', '--mode', default='sign', metavar='MODE',
111         choices=['sign', 'encrypt', 'sign-encrypt', 'plain'],
112         help='encryption mode')
113     parser.add_argument(
114         '-s', '--sign-as', metavar='KEY',
115         help="gpg key to sign with (gpg's -u/--local-user)")
116     parser.add_argument(
117         '-c', '--config', metavar='FILE',
118         default=_os_path.expanduser(_os_path.join(
119                 '~', '.config', 'smtplib.conf')),
120         help='SMTP config file for sending mail'),
121     parser.add_argument(
122         '--output', action='store_const', const=True,
123         help="don't mail the generated message, print it to stdout instead")
124     parser.add_argument(
125         '-V', '--verbose', default=0, action='count',
126         help='increment verbosity')
127
128     args = parser.parse_args()
129
130     if args.verbose:
131         _pgp_mime.LOG.setLevel(max(
132                 _logging.DEBUG, _pgp_mime.LOG.level - 10*args.verbose))
133
134     header_text = read_file(filename=args.header_file, encoding=args.encoding)
135     header = _pgp_mime.header_from_text(header_text)
136     body_text = read_file(filename=args.body_file, encoding=args.encoding)
137     body = _pgp_mime.encodedMIMEText(body_text)
138     if args.attachment:
139         b = _MIMEMultipart()
140         b.attach(body)
141         body = b
142         _mimetypes.init()
143         for attachment in args.attachment:
144             body.attach(load_attachment(
145                     filename=attachment, encoding=args.encoding))
146     if args.sign_as:
147         signers = [args.sign_as]
148     else:
149         signers = None
150     if 'encrypt' in args.mode:
151         recipients = [email for name,email in _pgp_mime.email_targets(header)]
152     if args.mode == 'sign':
153         body = _pgp_mime.sign(body, signers=signers, allow_default_signer=True)
154     elif args.mode == 'encrypt':
155         body = _pgp_mime.encrypt(body, recipients=recipients)
156     elif args.mode == 'sign-encrypt':
157         body = _pgp_mime.sign_and_encrypt(
158             body, signers=signers, recipients=recipients,
159             allow_default_signer=True)
160     elif args.mode == 'plain':
161         pass
162     else:
163         raise Exception('unrecognized mode {}'.format(args.mode))
164     message = _pgp_mime.attach_root(header, body)
165
166     if args.output:
167         print(message.as_string())
168     else:
169         config = _configparser.ConfigParser()
170         config.read(args.config)
171         params = _pgp_mime.get_smtp_params(config)
172         smtp = _pgp_mime.get_smtp(*params)
173         try:
174             _pgp_mime.mail(message, smtp)
175         finally:
176             _pgp_mime.LOG.info('disconnect from SMTP server')
177             smtp.quit()