1 #!/usr/bin/env python3.3
2 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
4 # This file is part of pgp-mime.
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
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.
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/>.
18 """Scriptable PGP MIME email using ``gpg``.
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::
24 $ export GPG_TTY=`tty`
25 $ eval $(gpg-agent --daemon)
27 in your shell before invoking this script. See ``gpg-agent(1)`` for
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
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
45 import pgp_mime as _pgp_mime
51 def read_file(filename=None, encoding='us-ascii'):
54 assert STDIN_USED == False, STDIN_USED
56 return _sys.stdin.read()
58 return _codecs.open(filename, 'r', encoding).read()
59 raise ValueError('neither filename nor descriptor given for reading')
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']
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)
81 attachment = _MIMENonMultipary(maintype, subtype)
82 attachment.set_payload(data, _encode_base64)
83 attachment.add_header(
84 'Content-Disposition', 'attachment', filename=filename)
88 if __name__ == '__main__':
91 doc_lines = __doc__.splitlines()
92 parser = argparse.ArgumentParser(
93 description = doc_lines[0],
94 epilog = '\n'.join(doc_lines[1:]).strip(),
95 formatter_class=argparse.RawDescriptionHelpFormatter)
97 '-v', '--version', action='version',
98 version='%(prog)s {}'.format(_pgp_mime.__version__))
100 '-e', '--encoding', metavar='ENCODING', default='utf-8',
101 help='encoding for input files')
103 '-H', '--header-file', metavar='FILE',
104 help='file containing email header')
106 '-B', '--body-file', metavar='FILE',
107 help='file containing email body')
109 '-a', '--attachment', metavar='FILE', action='append',
110 help='add an attachment to your message')
112 '-m', '--mode', default='sign', metavar='MODE',
113 choices=['sign', 'encrypt', 'sign-encrypt', 'plain'],
114 help='encryption mode')
116 '-s', '--sign-as', metavar='KEY',
117 help="gpg key to sign with (gpg's -u/--local-user)")
119 '-c', '--config', metavar='FILE',
120 default=_os_path.expanduser(_os_path.join(
121 '~', '.config', 'smtplib.conf')),
122 help='SMTP config file for sending mail'),
124 '--output', action='store_const', const=True,
125 help="don't mail the generated message, print it to stdout instead")
127 '-V', '--verbose', default=0, action='count',
128 help='increment verbosity')
130 args = parser.parse_args()
133 _pgp_mime.LOG.setLevel(max(
134 _logging.DEBUG, _pgp_mime.LOG.level - 10*args.verbose))
136 header_text = read_file(filename=args.header_file, encoding=args.encoding)
137 header = _pgp_mime.header_from_text(header_text)
138 body_text = read_file(filename=args.body_file, encoding=args.encoding)
139 body = _pgp_mime.encodedMIMEText(body_text)
145 for attachment in args.attachment:
146 body.attach(load_attachment(
147 filename=attachment, encoding=args.encoding))
149 config = _configparser.ConfigParser()
150 config.read(args.config)
151 client_params = _pgp_mime.get_client_params(config)
154 signers = [args.sign_as]
157 if 'encrypt' in args.mode:
158 recipients = [email for name,email in _pgp_mime.email_targets(header)]
159 if args.mode == 'sign':
160 body = _pgp_mime.sign(
161 body, signers=signers, allow_default_signer=True, **client_params)
162 elif args.mode == 'encrypt':
163 body = _pgp_mime.encrypt(body, recipients=recipients, **client_params)
164 elif args.mode == 'sign-encrypt':
165 body = _pgp_mime.sign_and_encrypt(
166 body, signers=signers, recipients=recipients,
167 allow_default_signer=True, **client_params)
168 elif args.mode == 'plain':
171 raise Exception('unrecognized mode {}'.format(args.mode))
172 message = _pgp_mime.attach_root(header, body)
175 print(message.as_string())
177 smtp_params = _pgp_mime.get_smtp_params(config)
178 smtp = _pgp_mime.get_smtp(*smtp_params)
180 _pgp_mime.mail(message, smtp)
182 _pgp_mime.LOG.info('disconnect from SMTP server')