3 # Kerberos login to IMAP server and extraction of provided cid: and mid: URIs.
5 # From: Rick van Rein <rick@openfortress.nl>
10 from base64 import b64encode, b64decode
23 def wrap (self, plaintext):
24 """Once a GSSAPI Context is complete, it can wrap plaintext
25 into ciphertext. This function operates on binary strings.
27 kerberos.authGSSClientWrap (self.ctx, b64encode (plaintext))
28 cipherdata = kerberos.authGSSClientResponse (self.ctx)
29 return (b64decode (cipherdata) if cipherdata else "")
31 def unwrap (self, ciphertext):
32 """Once a GSSAPI Context is complete, it can unwrap ciphertext
33 into plaintext. This function operates on binary strings.
35 kerberos.authGSSClientUnwrap (self.ctx, b64encode (ciphertext))
36 return b64decode (kerberos.authGSSClientResponse (self.ctx))
38 def processor (self, hostname):
39 # Currying function (needed to bind 'self')
41 #DEBUG# print 'New Call with Complete:', self.complete
42 #DEBUG# print 'Received:', '"' + b64encode (rcv) + '"'
44 # Initiate the GSSAPI Client
45 #ALT# rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname, gssflags=kerberos.GSS_C_SEQUENCE_FLAG)
46 #STD# rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname)
48 rc, self.ctx = kerberos.authGSSClientInit ('imap@' + hostname)
49 rc = kerberos.authGSSClientStep (self.ctx, b64encode (rcv))
50 #DEBUG# print 'ClientStep Result Code:', ['CONTINUE', 'COMPLETE'] [rc]
51 if rc == kerberos.AUTH_GSS_COMPLETE:
54 # print 'Error making a step'
56 snd = kerberos.authGSSClientResponse (self.ctx)
57 return (b64decode (snd) if snd else "")
59 # Unwrap and interpret the information token
60 rc = kerberos.authGSSClientUnwrap (self.ctx, b64encode (rcv))
62 # print 'Error unwrapping'
64 token = b64decode (kerberos.authGSSClientResponse (self.ctx))
66 #DEBUG# print 'Error unwrapping token after GSSAPI handshake'
68 flags = ord (token [0])
69 #DEBUG# print 'Flags:', '0x%02x' % flags
70 if flags & kerberos.GSS_C_INTEG_FLAG:
71 pass #DEBUG# print 'Integrity Supported'
72 if flags & kerberos.GSS_C_CONF_FLAG:
73 pass #DEBUG# print 'Confidentialtiy Supported'
74 maxlen = (ord (token [1]) << 16) | (ord (token [2]) << 8) | (ord (token [3]))
75 #DEBUG# print 'Maxlen:', maxlen
76 rettok = (chr (0) * 4) + 'ofo'
77 return self.wrap (rettok)
78 # kerberos.authGSSClientWrap (self.ctx, b64encode (rettok))
79 # snd = kerberos.authGSSClientResponse (self.ctx)
80 # return (b64decode (snd) if snd else "")
82 # The Currying surroundings return the internal function
83 # This is a strange necessity due to the IMAP assumption
84 # that it can call a closure, or a stateless function.
85 # What a lot of work to evade global variables... and it's
86 # all due to an ill-designed API, I think.
89 def clientname (self):
90 return kerberos.authGSSClientUserName (self.ctx)
95 # Check the commandline
97 if len (sys.argv) < 2:
98 sys.stderr.write ('Usage: ' + sys.argv [0] + ' mid:... cid:...\n\tTo retrieve the mid: and/or cid: URIs from your IMAP mailbox\nAuthentication and mailbox identities use your current Kerberos ticket\n')
102 # Turn the commandline into (messageid,contentid) pairs
105 def alsodo (todo, mid=None, cid=None):
107 mid = '<' + urllib.unquote (mid) + '>'
109 cid = '<' + urllib.unquote (cid) + '>'
110 todo.append ( (mid,cid) )
112 for arg in sys.argv [1:]:
113 if arg [:4].lower () == 'mid:':
114 slashpos = arg.find ('/')
116 alsodo (todo, mid=arg [4:slashpos], cid=arg [slashpos+1:])
118 alsodo (todo, mid=arg [4:])
119 elif arg [:4].lower () == 'cid:':
120 alsodo (todo, cid=arg [4:])
122 sys.stderr.write ('You should only use mid:... and cid:... arguments, see RFC 2392\n')
124 #DEBUG# print 'Searching for', todo [-1]
126 remote_hostname = 'popmini.opera'
128 im = imaplib.IMAP4 (remote_hostname, 143)
129 authctx = SASLTongue ()
130 authcpu = authctx.processor (remote_hostname)
131 #DEBUG# print 'AuthCPU:', authcpu, '::', type (authcpu)
132 im.authenticate ('GSSAPI', authcpu)
134 print 'Accessing IMAP as', authctx.clientname ()
136 ok,msgs = im.select ()
138 sys.stderr.write ('Failed to select INBOX\n')
141 for (mid,cid) in todo:
142 #DEBUG# print 'Retrieving', (mid,cid)
144 # This is relatively quick, Content-ID is much slower, even
145 # as an _added_ conition (huh... Dovecot?!?)
146 cc = '(HEADER Message-ID "' + mid + '")'
148 # Strange... no MIME-header search facilities in IMAP4rev1?!?
149 cc = '(TEXT "' + cid + '")'
150 #DEBUG# print 'Search criteria:', cc
151 ok,findings = im.uid ('search', None, cc)
153 sys.stderr.write ('Failed to search\n')
155 #DEBUG# print 'Found the following:', findings
157 #DEBUG# print 'Looking up UID', uid
158 ok,data = im.uid ('fetch', uid, 'BODYSTRUCTURE')
160 sys.stderr.wrote ('Error fetching body structure')
162 #DEBUG# print 'Found', data
166 sys.stderr.write ('Failed to locate content\n')
168 unquoted = data [0].split ('"')
169 for i in range (len (unquoted)):
171 # Even entries are unquoted
173 modulus = len (w) + 3
175 brapos = min (w.find ('(') % modulus, w.find (')') % modulus, w.find (' ') % modulus)
177 if w [:brapos] == 'NIL':
180 parsed.append (w [:brapos])
181 if w [brapos] == '(':
183 stack.append (parsed)
185 if w [brapos] == ')':
188 parsed = stack.pop ()
192 # Quoted word -- pass literally
193 parsed.append (unquoted [i])
194 # print 'Parsed it into', parsed
195 bodystructure = parsed [1] [3]
196 #DEBUG# print 'Body structure:', bodystructure
197 def printbody (bs, indents=0):
199 for i in range (len (bs)):
200 if type (bs [i]) == type ([]):
202 printbody (bs [i], indents=indents+1)
204 print ' ' * indents + '{%02d}' % i
207 print ' ' * indents + '[%02d]' % i, bs [i]
208 #DEBUG# printbody (bodystructure)
210 def matchcid (bs, cid, accupar, path=[]):
212 for i in range (len (bs)):
213 if type (bs [i]) == type ([]):
215 matchcid (bs [i], cid, accupar, path=path+[i])
218 pass #DEBUG# print 'Comparing', cid, 'with', bs [i]
219 if i == 3 and bs [i] == cid:
220 #DEBUG# print 'CID found on:', path
221 accupar.append (path)
225 matchcid (bodystructure, cid, accu, path=[1,3])
226 #DEBUG# print 'Result is:', accu
235 print 'MIME-Type =', here [0] + '/' + here [1]
236 print '[attr,value,...] =', here [2]
238 for i in range (0, len (here [2]), 2):
239 print 'Looking for name in', here [2][i]
240 if here [2][i].lower () == 'name':
242 print 'Filename:', name
243 print 'Content-ID =', here [3] if len (here) > 3 else ''
244 print 'Description =', here [4] if len (here) > 4 else ''
245 print 'Transfer-Encoding =', here [5] if len (here) > 5 else ''
246 encoding = here [5] if len (here) > 5 else ''
247 print 'Size =', here [6] if len (here) > 6 else '?'
250 for r in result [2:]:
251 bodyspec = bodyspec + dot + str (r+1)
253 bodyspec = bodyspec + ']'
254 if bodyspec == 'BODY]':
256 print 'Fetchable bodyspec', bodyspec, 'for UID', uid
257 ok,data = im.uid ('fetch', uid+':'+uid, '('+bodyspec+')')
259 sys.stderr.write ('Error fetching content')
261 #TODO# Be more subtle about encoding lists
262 if os.path.exists (absname):
263 sys.stderr.write ('Fatal: file ' + absname + ' already exists\nYou probably ran the command twice; or else the sender may attempt overwriting\n')
265 fh = open (absname, 'wb')
266 if encoding == 'base64':
267 fh.write (b64decode (data [0][1]))
269 fh.write (data [0][1])
271 print 'Written to:', absname
273 if not os.path.exists (name):
274 os.link (absname, name)
275 print 'Created a link from:', name
277 sys.stderr.write ('Warning: file ' + name + ' already exists, not linking\n')