First working version of midget/midput; for now, configure IMAP server manually
[midget] / midput.py
1 #!/usr/bin/env python
2 #
3 # Kerberos login to IMAP server and upload of provided files as a draft email.
4 #
5 # From: Rick van Rein <rick@openfortress.nl>
6
7
8 import os
9 import sys
10 from base64 import b64encode, b64decode
11 import imaplib
12 import urllib
13
14 import email.mime.base as mime
15 import email.mime.text as text
16 import email.mime.multipart as multipart
17
18 import kerberos
19
20
21 class SASLTongue:
22
23         def __init__ (self):
24                 self.ctx = None
25                 self.complete = False
26
27         def wrap (self, plaintext):
28                 """Once a GSSAPI Context is complete, it can wrap plaintext
29                    into ciphertext.  This function operates on binary strings.
30                 """
31                 kerberos.authGSSClientWrap (self.ctx, b64encode (plaintext))
32                 cipherdata = kerberos.authGSSClientResponse (self.ctx)
33                 return (b64decode (cipherdata) if cipherdata else "")
34
35         def unwrap (self, ciphertext):
36                 """Once a GSSAPI Context is complete, it can unwrap ciphertext
37                    into plaintext.  This function operates on binary strings.
38                 """
39                 kerberos.authGSSClientUnwrap (self.ctx, b64encode (ciphertext))
40                 return b64decode (kerberos.authGSSClientResponse (self.ctx))
41
42         def processor (self, hostname):
43                 # Currying function (needed to bind 'self')
44                 def step (rcv):
45                         #DEBUG# print 'New Call with Complete:', self.complete
46                         #DEBUG# print 'Received:', '"' + b64encode (rcv) + '"'
47                         if not self.complete:
48                                 # Initiate the GSSAPI Client
49                                 #ALT# rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname, gssflags=kerberos.GSS_C_SEQUENCE_FLAG)
50                                 #STD# rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname)
51                                 if not self.ctx:
52                                         rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname)
53                                 rc = kerberos.authGSSClientStep (self.ctx, b64encode (rcv))
54                                 #DEBUG# print 'ClientStep Result Code:', ['CONTINUE', 'COMPLETE'] [rc]
55                                 if rc == kerberos.AUTH_GSS_COMPLETE:
56                                         self.complete = True
57                                 # if rc != 0:
58                                 #       print 'Error making a step'
59                                 #       return None
60                                 snd = kerberos.authGSSClientResponse (self.ctx)
61                                 return (b64decode (snd) if snd else "")
62                         else:
63                                 # Unwrap and interpret the information token
64                                 rc = kerberos.authGSSClientUnwrap (self.ctx, b64encode (rcv))
65                                 # if rc != 0:
66                                 #       print 'Error unwrapping'
67                                 #       return None
68                                 token = b64decode (kerberos.authGSSClientResponse (self.ctx))
69                                 if len (token) != 4:
70                                         #DEBUG# print 'Error unwrapping token after GSSAPI handshake'
71                                         return None
72                                 flags = ord (token [0])
73                                 #DEBUG# print 'Flags:', '0x%02x' % flags
74                                 if flags & kerberos.GSS_C_INTEG_FLAG:
75                                         pass #DEBUG# print 'Integrity Supported'
76                                 if flags & kerberos.GSS_C_CONF_FLAG:
77                                         pass #DEBUG# print 'Confidentialtiy Supported'
78                                 maxlen = (ord (token [1]) << 16) | (ord (token [2]) << 8) | (ord (token [3]))
79                                 #DEBUG# print 'Maxlen:', maxlen
80                                 rettok = (chr (0) * 4) + 'ofo'
81                                 return self.wrap (rettok)
82                                 # kerberos.authGSSClientWrap (self.ctx, b64encode (rettok))
83                                 # snd = kerberos.authGSSClientResponse (self.ctx)
84                                 # return (b64decode (snd) if snd else "")
85
86                 # The Currying surroundings return the internal function
87                 # This is a strange necessity due to the IMAP assumption
88                 # that it can call a closure, or a stateless function.
89                 # What a lot of work to evade global variables... and it's
90                 # all due to an ill-designed API, I think.
91                 return step
92
93
94 #
95 # Process the commandline
96 #
97 if len (sys.argv) < 2:
98         sys.stderr.write ('Usage: ' + sys.argv [0] + ' attachment...\n\tThis command will create a draft email with the given files attached.\n')
99
100
101 attachments = [ ]
102 for arg in sys.argv [1:]:
103         ana = os.popen ('file --mime-type "' + arg + '"').read ()
104         (filenm,mimetp) = ana.split (': ', 1)
105         (major,minor) = mimetp.strip ().split ('/', 1)
106         filenm = arg.split (os.sep) [-1]
107         content = mime.MIMEBase (major, minor)
108         content.set_param ('name', filenm)
109         content.add_header ('Content-disposition', 'attachment', filename=filenm)
110         attachments.append (content)
111
112
113 remote_hostname = 'popmini.opera'
114
115 #
116 # Login to IMAP
117 #
118 im = imaplib.IMAP4 (remote_hostname, 143)
119 authctx = SASLTongue ()
120 authcpu = authctx.processor (remote_hostname)
121 #DEBUG# print 'AuthCPU:', authcpu, '::', type (authcpu)
122 im.authenticate ('GSSAPI', authcpu)
123
124 #
125 # Select a mailbox for uploading to
126 #
127 draftbox = 'Drafts'
128 ok,msgs = im.select (draftbox)
129 if ok != 'OK':
130         ok,msgs = im.select ()
131         if ok == 'OK':
132                 sys.stderr.write ('Warning: No ' + draftbox + ' folder found, posting to INBOX\n')
133                 draftbox = 'INBOX'
134         else:
135                 sys.stderr.write ('Failed to select both Drafts folder or even INBOX\n')
136                 sys.exit (1)
137
138
139 #
140 # Insert the content into the attachments
141 #
142 for (av,at) in zip (sys.argv [1:], attachments):
143         if major == 'text':
144                 at.set_payload (           open (av, 'r').read () )
145         else:
146                 at.set_payload (b64encode (open (av, 'r').read ()))
147
148 #
149 # Construct the email message to upload
150 #
151 introtxt = """Hello,
152
153 Attached, you will find 
154 """
155 intro = text.MIMEText (introtxt)
156 attachments.insert (0, intro)
157
158 msg = multipart.MIMEMultipart ()
159 for at in attachments:
160         msg.attach (at)
161
162 ok,data = im.append (draftbox, '(\\Flagged \\Draft)', None, msg.as_string ())
163 if ok != 'OK':
164         sys.stderr.write ('Problem appending the file')
165         sys.exit (1)