Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: mozilla/security/nss/lib/softoken/sftkmod.c

Issue 11362174: Update NSS to NSS 3.14 pre-release snapshot 2012-06-28 01:00:00 PDT. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/third_party/nss/
Patch Set: Update the snapshot timestamp in README.chromium Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 * The following code handles the storage of PKCS 11 modules used by the
6 * NSS. For the rest of NSS, only one kind of database handle exists:
7 *
8 * SFTKDBHandle
9 *
10 * There is one SFTKDBHandle for the each key database and one for each cert
11 * database. These databases are opened as associated pairs, one pair per
12 * slot. SFTKDBHandles are reference counted objects.
13 *
14 * Each SFTKDBHandle points to a low level database handle (SDB). This handle
15 * represents the underlying physical database. These objects are not
16 * reference counted, an are 'owned' by their respective SFTKDBHandles.
17 *
18 *
19 */
20 #include "sftkdb.h"
21 #include "sftkpars.h"
22 #include "prprf.h"
23 #include "prsystem.h"
24 #include "lgglue.h"
25 #include "secerr.h"
26 #include "secmodt.h"
27 #if defined (_WIN32)
28 #include <io.h>
29 #endif
30
31 /****************************************************************
32 *
33 * Secmod database.
34 *
35 * The new secmod database is simply a text file with each of the module
36 * entries. in the following form:
37 *
38 * #
39 * # This is a comment The next line is the library to load
40 * library=libmypkcs11.so
41 * name="My PKCS#11 module"
42 * params="my library's param string"
43 * nss="NSS parameters"
44 * other="parameters for other libraries and applications"
45 *
46 * library=libmynextpk11.so
47 * name="My other PKCS#11 module"
48 */
49
50 static char *
51 sftkdb_quote(const char *string, char quote)
52 {
53 char *newString = 0;
54 int escapes = 0, size = 0;
55 const char *src;
56 char *dest;
57
58 size=2;
59 for (src=string; *src ; src++) {
60 if ((*src == quote) || (*src == '\\')) escapes++;
61 size++;
62 }
63
64 dest = newString = PORT_ZAlloc(escapes+size+1);
65 if (newString == NULL) {
66 return NULL;
67 }
68
69 *dest++=quote;
70 for (src=string; *src; src++,dest++) {
71 if ((*src == '\\') || (*src == quote)) {
72 *dest++ = '\\';
73 }
74 *dest = *src;
75 }
76 *dest=quote;
77
78 return newString;
79 }
80
81 /*
82 * Smart string cat functions. Automatically manage the memory.
83 * The first parameter is the source string. If it's null, we
84 * allocate memory for it. If it's not, we reallocate memory
85 * so the the concanenated string fits.
86 */
87 static char *
88 sftkdb_DupnCat(char *baseString, const char *str, int str_len)
89 {
90 int len = (baseString ? PORT_Strlen(baseString) : 0) + 1;
91 char *newString;
92
93 len += str_len;
94 newString = (char *) PORT_Realloc(baseString,len);
95 if (newString == NULL) {
96 PORT_Free(baseString);
97 return NULL;
98 }
99 if (baseString == NULL) *newString = 0;
100 return PORT_Strncat(newString,str, str_len);
101 }
102
103 /* Same as sftkdb_DupnCat except it concatenates the full string, not a
104 * partial one */
105 static char *
106 sftkdb_DupCat(char *baseString, const char *str)
107 {
108 return sftkdb_DupnCat(baseString, str, PORT_Strlen(str));
109 }
110
111 /* function to free up all the memory associated with a null terminated
112 * array of module specs */
113 static SECStatus
114 sftkdb_releaseSpecList(char **moduleSpecList)
115 {
116 if (moduleSpecList) {
117 char **index;
118 for(index = moduleSpecList; *index; index++) {
119 PORT_Free(*index);
120 }
121 PORT_Free(moduleSpecList);
122 }
123 return SECSuccess;
124 }
125
126 #define SECMOD_STEP 10
127 static SECStatus
128 sftkdb_growList(char ***pModuleList, int *useCount, int last)
129 {
130 char **newModuleList;
131
132 *useCount += SECMOD_STEP;
133 newModuleList = (char **)PORT_Realloc(*pModuleList,
134 *useCount*sizeof(char *));
135 if (newModuleList == NULL) {
136 return SECFailure;
137 }
138 PORT_Memset(&newModuleList[last],0, sizeof(char *)*SECMOD_STEP);
139 *pModuleList = newModuleList;
140 return SECSuccess;
141 }
142
143 static
144 char *sftk_getOldSecmodName(const char *dbname,const char *filename)
145 {
146 char *file = NULL;
147 char *dirPath = PORT_Strdup(dbname);
148 char *sep;
149
150 sep = PORT_Strrchr(dirPath,*PATH_SEPARATOR);
151 #ifdef _WIN32
152 if (!sep) {
153 /* pkcs11i.h defines PATH_SEPARATOR as "/" for all platforms. */
154 sep = PORT_Strrchr(dirPath,'\\');
155 }
156 #endif
157 if (sep) {
158 *sep = 0;
159 file = PR_smprintf("%s"PATH_SEPARATOR"%s", dirPath, filename);
160 } else {
161 file = PR_smprintf("%s", filename);
162 }
163 PORT_Free(dirPath);
164 return file;
165 }
166
167 #ifdef XP_UNIX
168 #include <unistd.h>
169 #endif
170 #include <fcntl.h>
171
172 #ifndef WINCE
173 /* same as fopen, except it doesn't use umask, but explicit */
174 FILE *
175 lfopen(const char *name, const char *mode, int flags)
176 {
177 int fd;
178 FILE *file;
179
180 fd = open(name, flags, 0600);
181 if (fd < 0) {
182 return NULL;
183 }
184 file = fdopen(fd, mode);
185 if (!file) {
186 close(fd);
187 }
188 /* file inherits fd */
189 return file;
190 }
191 #endif
192
193 #define MAX_LINE_LENGTH 2048
194 #define SFTK_DEFAULT_INTERNAL_INIT1 "library= name=\"NSS Internal PKCS #11 Modul e\" parameters="
195 #define SFTK_DEFAULT_INTERNAL_INIT2 " NSS=\"Flags=internal,critical trustOrder=7 5 cipherOrder=100 slotParams=(1={"
196 #define SFTK_DEFAULT_INTERNAL_INIT3 " askpw=any timeout=30})\""
197
198 /*
199 * Read all the existing modules in out of the file.
200 */
201 char **
202 sftkdb_ReadSecmodDB(SDBType dbType, const char *appName,
203 const char *filename, const char *dbname,
204 char *params, PRBool rw)
205 {
206 FILE *fd = NULL;
207 char **moduleList = NULL;
208 int moduleCount = 1;
209 int useCount = SECMOD_STEP;
210 char line[MAX_LINE_LENGTH];
211 PRBool internal = PR_FALSE;
212 PRBool skipParams = PR_FALSE;
213 char *moduleString = NULL;
214 char *paramsValue=NULL;
215 PRBool failed = PR_TRUE;
216
217 if ((dbname != NULL) &&
218 ((dbType == SDB_LEGACY) || (dbType == SDB_MULTIACCESS))) {
219 return sftkdbCall_ReadSecmodDB(appName, filename, dbname, params, rw);
220 }
221
222 moduleList = (char **) PORT_ZAlloc(useCount*sizeof(char **));
223 if (moduleList == NULL) return NULL;
224
225 if (dbname == NULL) {
226 goto return_default;
227 }
228
229 /* do we really want to use streams here */
230 fd = fopen(dbname, "r");
231 if (fd == NULL) goto done;
232
233 /*
234 * the following loop takes line separated config lines and collapses
235 * the lines to a single string, escaping and quoting as necessary.
236 */
237 /* loop state variables */
238 moduleString = NULL; /* current concatenated string */
239 internal = PR_FALSE; /* is this an internal module */
240 skipParams = PR_FALSE; /* did we find an override parameter block*/
241 paramsValue = NULL; /* the current parameter block value */
242 while (fgets(line, sizeof(line), fd) != NULL) {
243 int len = PORT_Strlen(line);
244
245 /* remove the ending newline */
246 if (len && line[len-1] == '\n') {
247 len--;
248 line[len] = 0;
249 }
250 if (*line == '#') {
251 continue;
252 }
253 if (*line != 0) {
254 /*
255 * The PKCS #11 group standard assumes blocks of strings
256 * separated by new lines, clumped by new lines. Internally
257 * we take strings separated by spaces, so we may need to escape
258 * certain spaces.
259 */
260 char *value = PORT_Strchr(line,'=');
261
262 /* there is no value, write out the stanza as is */
263 if (value == NULL || value[1] == 0) {
264 if (moduleString) {
265 moduleString = sftkdb_DupnCat(moduleString," ", 1);
266 if (moduleString == NULL) goto loser;
267 }
268 moduleString = sftkdb_DupCat(moduleString, line);
269 if (moduleString == NULL) goto loser;
270 /* value is already quoted, just write it out */
271 } else if (value[1] == '"') {
272 if (moduleString) {
273 moduleString = sftkdb_DupnCat(moduleString," ", 1);
274 if (moduleString == NULL) goto loser;
275 }
276 moduleString = sftkdb_DupCat(moduleString, line);
277 if (moduleString == NULL) goto loser;
278 /* we have an override parameter section, remember that
279 * we found this (see following comment about why this
280 * is necessary). */
281 if (PORT_Strncasecmp(line, "parameters", 10) == 0) {
282 skipParams = PR_TRUE;
283 }
284 /*
285 * The internal token always overrides it's parameter block
286 * from the passed in parameters, so wait until then end
287 * before we include the parameter block in case we need to
288 * override it. NOTE: if the parameter block is quoted with ("),
289 * this override does not happen. This allows you to override
290 * the application's parameter configuration.
291 *
292 * parameter block state is controlled by the following variables:
293 * skipParams - Bool : set to true of we have an override param
294 * block (all other blocks, either implicit or explicit are
295 * ignored).
296 * paramsValue - char * : pointer to the current param block. In
297 * the absence of overrides, paramsValue is set to the first
298 * parameter block we find. All subsequent blocks are ignored.
299 * When we find an internal token, the application passed
300 * parameters take precident.
301 */
302 } else if (PORT_Strncasecmp(line, "parameters", 10) == 0) {
303 /* already have parameters */
304 if (paramsValue) {
305 continue;
306 }
307 paramsValue = sftkdb_quote(&value[1], '"');
308 if (paramsValue == NULL) goto loser;
309 continue;
310 } else {
311 /* may need to quote */
312 char *newLine;
313 if (moduleString) {
314 moduleString = sftkdb_DupnCat(moduleString," ", 1);
315 if (moduleString == NULL) goto loser;
316 }
317 moduleString = sftkdb_DupnCat(moduleString,line,value-line+1);
318 if (moduleString == NULL) goto loser;
319 newLine = sftkdb_quote(&value[1],'"');
320 if (newLine == NULL) goto loser;
321 moduleString = sftkdb_DupCat(moduleString,newLine);
322 PORT_Free(newLine);
323 if (moduleString == NULL) goto loser;
324 }
325
326 /* check to see if it's internal? */
327 if (PORT_Strncasecmp(line, "NSS=", 4) == 0) {
328 /* This should be case insensitive! reviewers make
329 * me fix it if it's not */
330 if (PORT_Strstr(line,"internal")) {
331 internal = PR_TRUE;
332 /* override the parameters */
333 if (paramsValue) {
334 PORT_Free(paramsValue);
335 }
336 paramsValue = sftkdb_quote(params, '"');
337 }
338 }
339 continue;
340 }
341 if ((moduleString == NULL) || (*moduleString == 0)) {
342 continue;
343 }
344
345 /*
346 * if we are here, we have found a complete stanza. Now write out
347 * any param section we may have found.
348 */
349 if (paramsValue) {
350 /* we had an override */
351 if (!skipParams) {
352 moduleString = sftkdb_DupnCat(moduleString," parameters=", 12);
353 if (moduleString == NULL) goto loser;
354 moduleString = sftkdb_DupCat(moduleString, paramsValue);
355 if (moduleString == NULL) goto loser;
356 }
357 PORT_Free(paramsValue);
358 paramsValue = NULL;
359 }
360
361 if ((moduleCount+1) >= useCount) {
362 SECStatus rv;
363 rv = sftkdb_growList(&moduleList, &useCount, moduleCount+1);
364 if (rv != SECSuccess) {
365 goto loser;
366 }
367 }
368
369 if (internal) {
370 moduleList[0] = moduleString;
371 } else {
372 moduleList[moduleCount] = moduleString;
373 moduleCount++;
374 }
375 moduleString = NULL;
376 internal = PR_FALSE;
377 skipParams = PR_FALSE;
378 }
379
380 if (moduleString) {
381 PORT_Free(moduleString);
382 moduleString = NULL;
383 }
384 done:
385 /* If we couldn't open a pkcs11 database, look for the old one.
386 * This is necessary to maintain the semantics of the transition from
387 * old to new DB's. If there is an old DB and not new DB, we will
388 * automatically use the old DB. If the DB was opened read/write, we
389 * create a new db and upgrade it from the old one. */
390 if (fd == NULL) {
391 char *olddbname = sftk_getOldSecmodName(dbname,filename);
392 PRStatus status;
393 char **oldModuleList;
394 int i;
395
396 /* couldn't get the old name */
397 if (!olddbname) {
398 goto bail;
399 }
400
401 /* old one doesn't exist */
402 status = PR_Access(olddbname, PR_ACCESS_EXISTS);
403 if (status != PR_SUCCESS) {
404 goto bail;
405 }
406
407 oldModuleList = sftkdbCall_ReadSecmodDB(appName, filename,
408 olddbname, params, rw);
409 /* old one had no modules */
410 if (!oldModuleList) {
411 goto bail;
412 }
413
414 /* count the modules */
415 for (i=0; oldModuleList[i]; i++) { }
416
417 /* grow the moduleList if necessary */
418 if (i >= useCount) {
419 SECStatus rv;
420 rv = sftkdb_growList(&moduleList,&useCount,moduleCount+1);
421 if (rv != SECSuccess) {
422 goto loser;
423 }
424 }
425
426 /* write each module out, and copy it */
427 for (i=0; oldModuleList[i]; i++) {
428 if (rw) {
429 sftkdb_AddSecmodDB(dbType,appName,filename,dbname,
430 oldModuleList[i],rw);
431 }
432 if (moduleList[i]) {
433 PORT_Free(moduleList[i]);
434 }
435 moduleList[i] = PORT_Strdup(oldModuleList[i]);
436 }
437
438 /* done with the old module list */
439 sftkdbCall_ReleaseSecmodDBData(appName, filename, olddbname,
440 oldModuleList, rw);
441 bail:
442 if (olddbname) {
443 PR_smprintf_free(olddbname);
444 }
445 }
446
447 return_default:
448
449 if (!moduleList[0]) {
450 char * newParams;
451 moduleString = PORT_Strdup(SFTK_DEFAULT_INTERNAL_INIT1);
452 newParams = sftkdb_quote(params,'"');
453 if (newParams == NULL) goto loser;
454 moduleString = sftkdb_DupCat(moduleString, newParams);
455 PORT_Free(newParams);
456 if (moduleString == NULL) goto loser;
457 moduleString = sftkdb_DupCat(moduleString, SFTK_DEFAULT_INTERNAL_INIT2);
458 if (moduleString == NULL) goto loser;
459 moduleString = sftkdb_DupCat(moduleString, SECMOD_SLOT_FLAGS);
460 if (moduleString == NULL) goto loser;
461 moduleString = sftkdb_DupCat(moduleString, SFTK_DEFAULT_INTERNAL_INIT3);
462 if (moduleString == NULL) goto loser;
463 moduleList[0] = moduleString;
464 moduleString = NULL;
465 }
466 failed = PR_FALSE;
467
468 loser:
469 /*
470 * cleanup
471 */
472 /* deal with trust cert db here */
473 if (moduleString) {
474 PORT_Free(moduleString);
475 moduleString = NULL;
476 }
477 if (paramsValue) {
478 PORT_Free(paramsValue);
479 paramsValue = NULL;
480 }
481 if (failed || (moduleList[0] == NULL)) {
482 /* This is wrong! FIXME */
483 sftkdb_releaseSpecList(moduleList);
484 moduleList = NULL;
485 failed = PR_TRUE;
486 }
487 if (fd != NULL) {
488 fclose(fd);
489 } else if (!failed && rw) {
490 /* update our internal module */
491 sftkdb_AddSecmodDB(dbType,appName,filename,dbname,moduleList[0],rw);
492 }
493 return moduleList;
494 }
495
496 SECStatus
497 sftkdb_ReleaseSecmodDBData(SDBType dbType, const char *appName,
498 const char *filename, const char *dbname,
499 char **moduleSpecList, PRBool rw)
500 {
501 if ((dbname != NULL) &&
502 ((dbType == SDB_LEGACY) || (dbType == SDB_MULTIACCESS))) {
503 return sftkdbCall_ReleaseSecmodDBData(appName, filename, dbname,
504 moduleSpecList, rw);
505 }
506 if (moduleSpecList) {
507 sftkdb_releaseSpecList(moduleSpecList);
508 }
509 return SECSuccess;
510 }
511
512
513 /*
514 * Delete a module from the Data Base
515 */
516 SECStatus
517 sftkdb_DeleteSecmodDB(SDBType dbType, const char *appName,
518 const char *filename, const char *dbname,
519 char *args, PRBool rw)
520 {
521 /* SHDB_FIXME implement */
522 FILE *fd = NULL;
523 FILE *fd2 = NULL;
524 char line[MAX_LINE_LENGTH];
525 char *dbname2 = NULL;
526 char *block = NULL;
527 char *name = NULL;
528 char *lib = NULL;
529 int name_len, lib_len;
530 PRBool skip = PR_FALSE;
531 PRBool found = PR_FALSE;
532
533 if (dbname == NULL) {
534 PORT_SetError(SEC_ERROR_INVALID_ARGS);
535 return SECFailure;
536 }
537
538 if ((dbType == SDB_LEGACY) || (dbType == SDB_MULTIACCESS)) {
539 return sftkdbCall_DeleteSecmodDB(appName, filename, dbname, args, rw);
540 }
541
542 if (!rw) {
543 PORT_SetError(SEC_ERROR_READ_ONLY);
544 return SECFailure;
545 }
546
547 dbname2 = strdup(dbname);
548 if (dbname2 == NULL) goto loser;
549 dbname2[strlen(dbname)-1]++;
550
551 /* do we really want to use streams here */
552 fd = fopen(dbname, "r");
553 if (fd == NULL) goto loser;
554 #ifdef WINCE
555 fd2 = fopen(dbname2, "w+");
556 #else
557 fd2 = lfopen(dbname2, "w+", O_CREAT|O_RDWR|O_TRUNC);
558 #endif
559 if (fd2 == NULL) goto loser;
560
561 name = sftk_argGetParamValue("name",args);
562 if (name) {
563 name_len = PORT_Strlen(name);
564 }
565 lib = sftk_argGetParamValue("library",args);
566 if (lib) {
567 lib_len = PORT_Strlen(lib);
568 }
569
570
571 /*
572 * the following loop takes line separated config files and collapses
573 * the lines to a single string, escaping and quoting as necessary.
574 */
575 /* loop state variables */
576 block = NULL;
577 skip = PR_FALSE;
578 while (fgets(line, sizeof(line), fd) != NULL) {
579 /* If we are processing a block (we haven't hit a blank line yet */
580 if (*line != '\n') {
581 /* skip means we are in the middle of a block we are deleting */
582 if (skip) {
583 continue;
584 }
585 /* if we haven't found the block yet, check to see if this block
586 * matches our requirements */
587 if (!found && ((name && (PORT_Strncasecmp(line,"name=",5) == 0) &&
588 (PORT_Strncmp(line+5,name,name_len) == 0)) ||
589 (lib && (PORT_Strncasecmp(line,"library=",8) == 0) &&
590 (PORT_Strncmp(line+8,lib,lib_len) == 0)))) {
591
592 /* yup, we don't need to save any more data, */
593 PORT_Free(block);
594 block=NULL;
595 /* we don't need to collect more of this block */
596 skip = PR_TRUE;
597 /* we don't need to continue searching for the block */
598 found =PR_TRUE;
599 continue;
600 }
601 /* not our match, continue to collect data in this block */
602 block = sftkdb_DupCat(block,line);
603 continue;
604 }
605 /* we've collected a block of data that wasn't the module we were
606 * looking for, write it out */
607 if (block) {
608 fwrite(block, PORT_Strlen(block), 1, fd2);
609 PORT_Free(block);
610 block = NULL;
611 }
612 /* If we didn't just delete the this block, keep the blank line */
613 if (!skip) {
614 fputs(line,fd2);
615 }
616 /* we are definately not in a deleted block anymore */
617 skip = PR_FALSE;
618 }
619 fclose(fd);
620 fclose(fd2);
621 if (found) {
622 /* rename dbname2 to dbname */
623 PR_Delete(dbname);
624 PR_Rename(dbname2,dbname);
625 } else {
626 PR_Delete(dbname2);
627 }
628 PORT_Free(dbname2);
629 PORT_Free(lib);
630 PORT_Free(name);
631 PORT_Free(block);
632 return SECSuccess;
633
634 loser:
635 if (fd != NULL) {
636 fclose(fd);
637 }
638 if (fd2 != NULL) {
639 fclose(fd2);
640 }
641 if (dbname2) {
642 PR_Delete(dbname2);
643 PORT_Free(dbname2);
644 }
645 PORT_Free(lib);
646 PORT_Free(name);
647 return SECFailure;
648 }
649
650 /*
651 * Add a module to the Data base
652 */
653 SECStatus
654 sftkdb_AddSecmodDB(SDBType dbType, const char *appName,
655 const char *filename, const char *dbname,
656 char *module, PRBool rw)
657 {
658 FILE *fd = NULL;
659 char *block = NULL;
660 PRBool libFound = PR_FALSE;
661
662 if (dbname == NULL) {
663 PORT_SetError(SEC_ERROR_INVALID_ARGS);
664 return SECFailure;
665 }
666
667 if ((dbType == SDB_LEGACY) || (dbType == SDB_MULTIACCESS)) {
668 return sftkdbCall_AddSecmodDB(appName, filename, dbname, module, rw);
669 }
670
671 /* can't write to a read only module */
672 if (!rw) {
673 PORT_SetError(SEC_ERROR_READ_ONLY);
674 return SECFailure;
675 }
676
677 /* remove the previous version if it exists */
678 (void) sftkdb_DeleteSecmodDB(dbType, appName, filename, dbname, module, rw);
679
680 #ifdef WINCE
681 fd = fopen(dbname, "a+");
682 #else
683 fd = lfopen(dbname, "a+", O_CREAT|O_RDWR|O_APPEND);
684 #endif
685 if (fd == NULL) {
686 return SECFailure;
687 }
688 module = sftk_argStrip(module);
689 while (*module) {
690 int count;
691 char *keyEnd = PORT_Strchr(module,'=');
692 char *value;
693
694 if (PORT_Strncmp(module, "library=", 8) == 0) {
695 libFound=PR_TRUE;
696 }
697 if (keyEnd == NULL) {
698 block = sftkdb_DupCat(block, module);
699 break;
700 }
701 block = sftkdb_DupnCat(block, module, keyEnd-module+1);
702 if (block == NULL) { goto loser; }
703 value = sftk_argFetchValue(&keyEnd[1], &count);
704 if (value) {
705 block = sftkdb_DupCat(block, sftk_argStrip(value));
706 PORT_Free(value);
707 }
708 if (block == NULL) { goto loser; }
709 block = sftkdb_DupnCat(block, "\n", 1);
710 module = keyEnd + 1 + count;
711 module = sftk_argStrip(module);
712 }
713 if (block) {
714 if (!libFound) {
715 fprintf(fd,"library=\n");
716 }
717 fwrite(block, PORT_Strlen(block), 1, fd);
718 fprintf(fd,"\n");
719 PORT_Free(block);
720 block = NULL;
721 }
722 fclose(fd);
723 return SECSuccess;
724
725 loser:
726 PORT_Free(block);
727 fclose(fd);
728 return SECFailure;
729 }
730
731
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698