Add gpgme-tool.socket-path configuration to smtplib.conf.
[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         formatter_class=argparse.RawDescriptionHelpFormatter)
96     parser.add_argument(
97         '-v', '--version', action='version',
98         version='%(prog)s {}'.format(_pgp_mime.__version__))
99     parser.add_argument(
100         '-e', '--encoding', metavar='ENCODING', default='utf-8',
101         help='encoding for input files')
102     parser.add_argument(
103         '-H', '--header-file', metavar='FILE',
104         help='file containing email header')
105     parser.add_argument(
106         '-B', '--body-file', metavar='FILE',
107         help='file containing email body')
108     parser.add_argument(
109         '-a', '--attachment', metavar='FILE', action='append',
110         help='add an attachment to your message')
111     parser.add_argument(
112         '-m', '--mode', default='sign', metavar='MODE',
113         choices=['sign', 'encrypt', 'sign-encrypt', 'plain'],
114         help='encryption mode')
115     parser.add_argument(
116         '-s', '--sign-as', metavar='KEY',
117         help="gpg key to sign with (gpg's -u/--local-user)")
118     parser.add_argument(
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'),
123     parser.add_argument(
124         '--output', action='store_const', const=True,
125         help="don't mail the generated message, print it to stdout instead")
126     parser.add_argument(
127         '-V', '--verbose', default=0, action='count',
128         help='increment verbosity')
129
130     args = parser.parse_args()
131
132     if args.verbose:
133         _pgp_mime.LOG.setLevel(max(
134                 _logging.DEBUG, _pgp_mime.LOG.level - 10*args.verbose))
135
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)
140     if args.attachment:
141         b = _MIMEMultipart()
142         b.attach(body)
143         body = b
144         _mimetypes.init()
145         for attachment in args.attachment:
146             body.attach(load_attachment(
147                     filename=attachment, encoding=args.encoding))
148
149     config = _configparser.ConfigParser()
150     config.read(args.config)
151     client_params = _pgp_mime.get_client_params(config)
152
153     if args.sign_as:
154         signers = [args.sign_as]
155     else:
156         signers = None
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':
169         pass
170     else:
171         raise Exception('unrecognized mode {}'.format(args.mode))
172     message = _pgp_mime.attach_root(header, body)
173
174     if args.output:
175         print(message.as_string())
176     else:
177         smtp_params = _pgp_mime.get_smtp_params(config)
178         smtp = _pgp_mime.get_smtp(*smtp_params)
179         try:
180             _pgp_mime.mail(message, smtp)
181         finally:
182             _pgp_mime.LOG.info('disconnect from SMTP server')
183             smtp.quit()