~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to extensions/enigmail/package/mimeVerify.jsm

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
 
 
5
 
 
6
 
/**
7
 
 *  Module for handling PGP/MIME signed messages
8
 
 *  implemented as JS module
9
 
 */
10
 
 
11
 
'use strict';
12
 
 
13
 
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
14
 
Components.utils.import("resource://enigmail/enigmailCommon.jsm");
15
 
 
16
 
var EXPORTED_SYMBOLS = [ "EnigmailVerify" ];
17
 
 
18
 
const Cc = Components.classes;
19
 
const Ci = Components.interfaces;
20
 
const Ec = EnigmailCommon;
21
 
 
22
 
const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
23
 
 
24
 
const maxBufferLen = 102400;
25
 
 
26
 
var gDebugLog = false;
27
 
 
28
 
function MimeVerify(verifyEmbedded, msgUrl)
29
 
{
30
 
  this.verifyEmbedded = verifyEmbedded;
31
 
  this.msgUrl = msgUrl;
32
 
}
33
 
 
34
 
 
35
 
// verify the signature of PGP/MIME signed messages
36
 
MimeVerify.prototype = {
37
 
  dataCount: 0,
38
 
  foundMsg: false,
39
 
  startMsgStr: "",
40
 
  msgWindow: null,
41
 
  msgUriSpec: null,
42
 
  statusDisplayed: false,
43
 
  exitCode: null,
44
 
  window: null,
45
 
  inStream: null,
46
 
 
47
 
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
48
 
 
49
 
  startStreaming: function(window, msgWindow, msgUriSpec) {
50
 
    DEBUG_LOG("mimeVerify.jsm: startStreaming\n");
51
 
 
52
 
    this.msgWindow = msgWindow;
53
 
    this.msgUriSpec = msgUriSpec;
54
 
    this.window = window;
55
 
    var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
56
 
    var msgSvc = messenger.messageServiceFromURI(this.msgUriSpec);
57
 
 
58
 
    this.inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream),
59
 
 
60
 
    msgSvc.streamMessage(this.msgUriSpec,
61
 
                    this,
62
 
                    this.msgWindow,
63
 
                    null,
64
 
                    false,
65
 
                    null,
66
 
                    false);
67
 
  },
68
 
 
69
 
  verifyData: function(window, msgWindow, msgUriSpec, data) {
70
 
    DEBUG_LOG("mimeVerify.jsm: streamFromChannel\n");
71
 
 
72
 
    this.msgWindow = msgWindow;
73
 
    this.msgUriSpec = msgUriSpec;
74
 
    this.window = window;
75
 
    this.onStartRequest();
76
 
    this.onTextData(data);
77
 
    this.onStopRequest();
78
 
  },
79
 
 
80
 
  onStartRequest: function() {
81
 
    DEBUG_LOG("mimeVerify.jsm: onStartRequest\n");
82
 
    this.dataCount = 0;
83
 
    this.foundMsg = false;
84
 
    this.startMsgStr = "";
85
 
    this.hash = "";
86
 
    this.boundary = "";
87
 
    this.proc = null;
88
 
    this.closePipe = false;
89
 
    this.pipe = null;
90
 
    this.writeMode = 0;
91
 
    this.keepData = "";
92
 
    this.outQueue = "";
93
 
    this.statusStr = "";
94
 
    this.returnStatus = null;
95
 
    this.statusDisplayed = false;
96
 
 
97
 
    if (!this.verifyEmbedded) this.startVerification();
98
 
  },
99
 
 
100
 
  onDataAvailable: function(req, sup, stream, offset, count) {
101
 
    DEBUG_LOG("mimeVerify.jsm: onDataAvailable: "+count+"\n");
102
 
    this.inStream.init(stream);
103
 
    var data = this.inStream.read(count);
104
 
    this.onTextData(data);
105
 
  },
106
 
 
107
 
  onTextData: function(data) {
108
 
    DEBUG_LOG("mimeVerify.jsm: onTextData\n");
109
 
    if (!this.foundMsg) {
110
 
      // check if mime part could be pgp/mime signed message
111
 
      if (this.dataCount > 10240) return;
112
 
      this.startMsgStr += data;
113
 
      let  i = this.startMsgStr.search(/^content-type:/im);
114
 
      if (i >= 0) {
115
 
        let s = data.substr(i).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
116
 
        if (s.search(/multipart\/signed/i) > 0 &&
117
 
          s.search(/micalg\s*=\s*[\"\']?pgp-[\"\']?/i) > 0 &&
118
 
          s.search(/protocol\s*=\s*[\'\"]application\/pgp-signature[\"\']/i) > 0) {
119
 
 
120
 
          DEBUG_LOG("mimeVerify.jsm: onTextData: found PGP/MIME signed message\n");
121
 
          this.foundMsg = true;
122
 
          let hdr = getHeaderData(s);
123
 
          this.boundary = hdr["boundary"].replace(/[\'\"]/g, "");
124
 
          this.hash = hdr["micalg"].replace(/[\'\"]/g, "").toUpperCase().replace(/^PGP-/, "");
125
 
        }
126
 
      }
127
 
    }
128
 
    this.dataCount += data.length;
129
 
 
130
 
    if (this.verifyEmbedded && this.foundMsg) {
131
 
      // process data as signed message
132
 
      if (! this.proc) {
133
 
        this.startVerification();
134
 
      }
135
 
    }
136
 
 
137
 
    this.keepData += data;
138
 
    if (this.writeMode == 0) {
139
 
      // header data
140
 
      let i = this.findNextMimePart();
141
 
      if (i >= 0) {
142
 
        i += 2 + this.boundary.length;
143
 
        if (this.keepData[i] == "\n") {
144
 
          ++i;
145
 
        }
146
 
        else if (this.keepData[i] == "\r") {
147
 
          ++i;
148
 
          if (this.keepData[i] == "\n") ++i;
149
 
        }
150
 
 
151
 
        this.keepData = this.keepData.substr(i);
152
 
        data = this.keepData;
153
 
        this.writeMode = 1;
154
 
      }
155
 
      else {
156
 
        this.keepData = data.substr(-this.boundary.length - 3);
157
 
      }
158
 
 
159
 
      if (! this.hash) this.hash = "SHA1";
160
 
 
161
 
      this.writeToPipe("-----BEGIN PGP SIGNED MESSAGE-----\n");
162
 
      this.writeToPipe("Hash: " + this.hash + "\n\n");
163
 
    }
164
 
 
165
 
    if (this.writeMode == 1) {
166
 
      // "real data"
167
 
      let i = this.findNextMimePart();
168
 
      if (i >= 0) {
169
 
        data = this.keepData.substr(0, i);
170
 
        this.keepData = this.keepData.substr(i);
171
 
        this.writeMode = 2;
172
 
      }
173
 
      else {
174
 
        data = this.keepData.substr(0, this.keepData.length - this.boundary.length - 3);
175
 
        this.keepData = this.keepData.substr(-this.boundary.length - 3);
176
 
      }
177
 
      this.writeToPipe(data.replace(/^-/gm, "- -"));
178
 
    }
179
 
 
180
 
    if (this.writeMode == 2) {
181
 
      // signature data "header"
182
 
      let i = this.keepData.search(/^-----BEGIN PGP /m);
183
 
      if (i>=0) {
184
 
        this.keepData = this.keepData.substr(i);
185
 
        this.writeMode = 3;
186
 
      }
187
 
    }
188
 
 
189
 
    if (this.writeMode == 3) {
190
 
      // signature data
191
 
      let i = this.keepData.search(/^-----END PGP /m);
192
 
      if (i >= 0) this.writeMode = 4;
193
 
      this.writeToPipe(this.keepData.substr(0, i + 30));
194
 
      this.keepData = "";
195
 
    }
196
 
 
197
 
  },
198
 
 
199
 
  findNextMimePart: function() {
200
 
    let startOk = false;
201
 
    let endOk = false;
202
 
 
203
 
    let i = this.keepData.indexOf("--"+this.boundary);
204
 
    if (i == 0) startOk = true;
205
 
    if (i > 0) {
206
 
      if (this.keepData[i-1] == '\r' || this.keepData[i-1] == '\n') startOk = true;
207
 
    }
208
 
 
209
 
    if (i + this.boundary.length + 2 < this.keepData) {
210
 
      if (this.keepData[i + this.boundary.length + 2] == '\r' ||
211
 
        this.keepData[i + this.boundary.length + 2] == '\n') endOk = true;
212
 
    }
213
 
    else
214
 
      endOk = true;
215
 
 
216
 
    if (i >= 0 && startOk && endOk) {
217
 
      return i;
218
 
    }
219
 
    return -1;
220
 
  },
221
 
 
222
 
  startVerification: function() {
223
 
    DEBUG_LOG("mimeVerify.jsm: startVerification\n");
224
 
      var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
225
 
      var win = windowManager.getMostRecentWindow(null);
226
 
      var statusFlagsObj = {};
227
 
      var errorMsgObj = {};
228
 
      this.proc = Ec.decryptMessageStart(win, true, this,
229
 
                      statusFlagsObj, errorMsgObj);
230
 
  },
231
 
 
232
 
  onStopRequest: function() {
233
 
    DEBUG_LOG("mimeVerify.jsm: onStopRequest\n");
234
 
    this.flushInput();
235
 
    if (this.pipe) {
236
 
      this.pipe.close()
237
 
    }
238
 
    else
239
 
      this.closePipe = true;
240
 
  },
241
 
 
242
 
  writeToPipe: function(str) {
243
 
    //DEBUG_LOG("mimeVerify.jsm: writeToPipe: "+str+"\n");
244
 
 
245
 
    if (this.pipe) {
246
 
      this.outQueue += str;
247
 
      if (this.outQueue.length > maxBufferLen)
248
 
        this.flushInput();
249
 
    }
250
 
    else
251
 
      this.outQueue += str;
252
 
  },
253
 
 
254
 
  flushInput: function() {
255
 
    DEBUG_LOG("mimeVerify.jsm: flushInput\n");
256
 
    if (! this.pipe) return;
257
 
    this.pipe.write(this.outQueue);
258
 
    this.outQueue = "";
259
 
  },
260
 
 
261
 
  // API for decryptMessage Listener
262
 
  stdin: function(pipe) {
263
 
    DEBUG_LOG("mimeVerify.jsm: stdin\n");
264
 
    if (this.outQueue.length > 0) {
265
 
      pipe.write(this.outQueue);
266
 
      this.outQueue = "";
267
 
      if (this.closePipe) pipe.close();
268
 
    }
269
 
    this.pipe = pipe;
270
 
  },
271
 
 
272
 
  stdout: function(s) {
273
 
    DEBUG_LOG("mimeVerify.jsm: stdout:"+s.length+"\n");
274
 
    this.dataLength += s.length;
275
 
  },
276
 
 
277
 
  stderr: function(s) {
278
 
    DEBUG_LOG("mimeVerify.jsm: stderr\n");
279
 
    this.statusStr += s;
280
 
  },
281
 
 
282
 
  done: function(exitCode) {
283
 
    DEBUG_LOG("mimeVerify.jsm: done: "+exitCode+"\n");
284
 
    this.exitCode = exitCode;
285
 
    //DEBUG_LOG("mimeVerify.jsm: "+this.statusStr+"\n");
286
 
 
287
 
    this.returnStatus = {};
288
 
    Ec.decryptMessageEnd(this.statusStr,
289
 
          this.exitCode,
290
 
          this.dataLength,
291
 
          true, // verifyOnly
292
 
          true,
293
 
          Ci.nsIEnigmail.UI_PGP_MIME,
294
 
          this.returnStatus);
295
 
    this.displayStatus();
296
 
 
297
 
  },
298
 
 
299
 
  setMsgWindow: function(msgWindow, msgUriSpec) {
300
 
    DEBUG_LOG("mimeVerify.jsm: setMsgWindow: "+msgUriSpec+"\n");
301
 
 
302
 
    if (! this.msgWindow) {
303
 
      this.msgWindow = msgWindow;
304
 
      this.msgUriSpec = msgUriSpec;
305
 
    }
306
 
  },
307
 
 
308
 
  displayStatus: function() {
309
 
    DEBUG_LOG("mimeVerify.jsm: displayStatus\n");
310
 
    if (this.exitCode == null || this.msgWindow == null || this.statusDisplayed)
311
 
      return;
312
 
 
313
 
    try {
314
 
      DEBUG_LOG("mimeVerify.jsm: displayStatus displaying result\n");
315
 
      let headerSink = this.msgWindow.msgHeaderSink.securityInfo.QueryInterface(Ci.nsIEnigMimeHeaderSink);
316
 
 
317
 
      if (headerSink) {
318
 
        headerSink.updateSecurityStatus(this.msgUriSpec,
319
 
            this.exitCode,
320
 
            this.returnStatus.statusFlags,
321
 
            this.returnStatus.keyId,
322
 
            this.returnStatus.userId,
323
 
            this.returnStatus.sigDetails,
324
 
            this.returnStatus.errorMsg,
325
 
            this.returnStatus.blockSeparation,
326
 
            null);
327
 
      }
328
 
      this.statusDisplayed = true;
329
 
    }
330
 
    catch(ex) {
331
 
      Ec.writeException("mimeVerify.jsm", ex);
332
 
    }
333
 
  }
334
 
}
335
 
 
336
 
var EnigmailVerify = {
337
 
  lastMsgWindow: null,
338
 
  lastMsgUri: null,
339
 
 
340
 
  setMsgWindow: function(msgWindow, msgUriSpec) {
341
 
    DEBUG_LOG("mimeVerify.jsm: setMsgWindow: "+msgUriSpec+"\n");
342
 
 
343
 
    this.lastMsgWindow = msgWindow;
344
 
    this.lastMsgUri = msgUriSpec;
345
 
  },
346
 
 
347
 
  newVerfier: function (embedded, msgUrl) {
348
 
    let v = new MimeVerify(embedded, msgUrl);
349
 
    return v;
350
 
  }
351
 
}
352
 
 
353
 
 
354
 
////////////////////////////////////////////////////////////////////
355
 
// General-purpose functions, not exported
356
 
 
357
 
// extract the data fields following a header.
358
 
// e.g. ContentType: xyz; Aa=b; cc=d
359
 
// returns aa=b and cc=d in an array of arrays
360
 
function getHeaderData(data) {
361
 
  DEBUG_LOG("mimeVerify.jsm: getHeaderData: "+data.substr(0, 100)+"\n");
362
 
  var a = data.split(/\n/);
363
 
  var res = [];
364
 
  for (let i = 0; i < a.length; i++) {
365
 
    if (a[i].length == 0) break;
366
 
    let b = a[i].split(/;/);
367
 
 
368
 
    // extract "abc = xyz" tuples
369
 
    for (let j=0; j < b.length; j++) {
370
 
      let m = b[j].match(/^(\s*)([^=\s;]+)(\s*)(=)(\s*)(.*)(\s*)$/);
371
 
      if (m) {
372
 
        // m[2]: identifier / m[6]: data
373
 
        res[m[2].toLowerCase()] = m[6].replace(/\s*$/, "");
374
 
        DEBUG_LOG("mimeVerify.jsm: getHeaderData: "+m[2].toLowerCase()+" = "+res[m[2].toLowerCase()] +"\n");
375
 
      }
376
 
    }
377
 
    if (i == 0 && a[i].indexOf(";") < 0) break;
378
 
    if (i > 0 && a[i].search(/^\s/) < 0) break;
379
 
  }
380
 
  return res;
381
 
}
382
 
 
383
 
 
384
 
function DEBUG_LOG(str) {
385
 
  if (gDebugLog) Ec.DEBUG_LOG(str);
386
 
}
387
 
 
388
 
function initModule() {
389
 
  try {
390
 
    var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
391
 
    var nspr_log_modules = env.get("NSPR_LOG_MODULES");
392
 
    var matches = nspr_log_modules.match(/mimeVerify:(\d+)/);
393
 
 
394
 
    if (matches && (matches.length > 1)) {
395
 
      if (matches[1] > 2) gDebugLog = true;
396
 
      dump("mimeVerify.jsm: enabled debug logging\n");
397
 
    }
398
 
  }
399
 
  catch (ex) {
400
 
    dump("caught error "+ex);
401
 
  }
402
 
}
403
 
 
404
 
initModule();
405
 
dump("mimeVerify.jsm: module initialized\n");