/[winpt]/trunk/Src/wptKeygenDlg.cpp
ViewVC logotype

Contents of /trunk/Src/wptKeygenDlg.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 234 - (show annotations)
Tue Jun 27 10:16:41 2006 UTC (18 years, 8 months ago) by twoaday
File size: 19523 byte(s)


1 /* wptKeygenDlg.cpp - Key Generation dialog
2 * Copyright (C) 2000-2006 Timo Schulz
3 *
4 * This file is part of WinPT.
5 *
6 * WinPT is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * WinPT is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with WinPT; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <windows.h>
25 #include <time.h>
26
27 #include "resource.h"
28 #include "wptTypes.h"
29 #include "wptNLS.h"
30 #include "wptGPG.h"
31 #include "wptCommonCtl.h"
32 #include "wptContext.h" /* for passphrase_s */
33 #include "wptDlgs.h"
34 #include "wptW32API.h"
35 #include "wptVersion.h"
36 #include "wptErrors.h"
37
38
39 /* All valid key generation combination. */
40 enum gpg_keytype_t {
41 GPG_KEYGEN_NONE = 0,
42 GPG_KEYGEN_DSA_ELG = 1,
43 GPG_KEYGEN_DSA_RSA = 2,
44 GPG_KEYGEN_DSA_SIG = 3,
45 GPG_KEYGEN_RSA_SIG = 4,
46 GPG_KEYGEN_RSA = 5,
47 GPG_KEYGEN_RSA_RSA = 6 /*PGP*/
48 };
49
50
51 static const char key_params[] =
52 "<GnupgKeyParms format=\"internal\">\n"
53 "Key-Type: %s\n"
54 "Key-Usage: sign\n"
55 "Key-Length: %d\n"
56 "Subkey-Type: %s\n"
57 "Subkey-Length: %d\n"
58 "Name-Real: %s\n"
59 "Name-Email: %s\n"
60 "Expire-Date: %s\n"
61 "Passphrase: %s\n"
62 "</GnupgKeyParms>\n";
63
64 static const char key_params_with_comment[] =
65 "<GnupgKeyParms format=\"internal\">\n"
66 "Key-Type: %s\n"
67 "Key-Usage: sign\n"
68 "Key-Length: %d\n"
69 "Subkey-Type: %s\n"
70 "Subkey-Length: %d\n"
71 "Name-Real: %s\n"
72 "Name-Comment: %s\n"
73 "Name-Email: %s\n"
74 "Expire-Date: %s\n"
75 "Passphrase: %s\n"
76 "</GnupgKeyParms>\n";
77
78 static const char key_params_one[] =
79 "<GnupgKeyParms format=\"internal\">\n"
80 "Key-Type: %s\n"
81 "Key-Length: %d\n"
82 "Key-Usage: %s\n"
83 "Name-Real: %s\n"
84 "Name-Email: %s\n"
85 "Expire-Date: %s\n"
86 "Passphrase: %s\n"
87 "</GnupgKeyParms>\n";
88
89 static const char key_params_one_with_comment[] =
90 "<GnupgKeyParms format=\"internal\">\n"
91 "Key-Type: %s\n"
92 "Key-Length: %d\n"
93 "Key-Usage: %s\n"
94 "Name-Real: %s\n"
95 "Name-Comment: %s\n"
96 "Name-Email: %s\n"
97 "Expire-Date: %s\n"
98 "Passphrase: %s\n"
99 "</GnupgKeyParms>\n";
100
101
102 /* Generate the GPG specific genkey params with the given information.
103 @keytype: See valid combinations.
104 @bits: Length in bits.
105 @user: user-ID name
106 @comment: comment for the user-ID.
107 @email: email address.
108 @expdata: date of expiration or NULL.
109 @passphrase: the actual passphrase.
110 Return value: the gen. params. */
111 static char*
112 gpg_genkey_params (int keytype, int bits,
113 const char *user, const char *comment, const char *email,
114 const char *expdate, const char *passphrase)
115 {
116 char *p = NULL;
117 int addsize = strlen ("sign encrypt");
118 int size = 0;
119
120 if (keytype == GPG_KEYGEN_NONE)
121 return NULL;
122
123 size = strlen (user) + strlen (email) + strlen (passphrase) + addsize + 48;
124 if (comment && *comment)
125 size += strlen (key_params_with_comment) + strlen (comment);
126 else
127 size += strlen (key_params);
128 if (expdate)
129 size += strlen (expdate) + 1;
130 p = new char[size+1];
131 if (!p)
132 BUG (0);
133 /* XXX: simply the whole switch-case code. */
134 if (comment && *comment) {
135 switch( keytype ) {
136 case GPG_KEYGEN_DSA_ELG:
137 sprintf (p, key_params_with_comment,
138 "DSA", 1024, "ELG-E", bits, user, comment, email,
139 expdate ? expdate : "0", passphrase);
140 break;
141
142 case GPG_KEYGEN_DSA_RSA:
143 sprintf( p, key_params_with_comment,
144 "DSA", 1024, "RSA", bits, user, comment, email,
145 expdate ? expdate : "0", passphrase );
146 break;
147
148 case GPG_KEYGEN_DSA_SIG:
149 sprintf( p, key_params_one_with_comment,
150 "DSA", 1024, "sign",
151 user, comment, email,
152 expdate ? expdate : "0", passphrase );
153 break;
154
155 case GPG_KEYGEN_RSA_SIG:
156 sprintf( p, key_params_one_with_comment,
157 "RSA", bits, "sign",
158 user, comment, email,
159 expdate ? expdate : "0", passphrase );
160 break;
161
162 case GPG_KEYGEN_RSA:
163 sprintf( p, key_params_one_with_comment,
164 "RSA", bits, "sign encrypt",
165 user, comment, email,
166 expdate ? expdate : "0", passphrase );
167 break;
168
169 case GPG_KEYGEN_RSA_RSA:
170 sprintf( p, key_params_with_comment,
171 "RSA", bits, "RSA", bits, user, comment, email,
172 expdate? expdate : "0", passphrase );
173 break;
174
175 default:
176 free_if_alloc (p);
177 break;
178 }
179 }
180 else {
181 switch ( keytype ) {
182 case GPG_KEYGEN_DSA_ELG:
183 sprintf( p, key_params,
184 "DSA", 1024, "ELG-E", bits, user, email,
185 expdate ? expdate : "0", passphrase );
186 break;
187
188 case GPG_KEYGEN_DSA_RSA:
189 sprintf( p, key_params,
190 "DSA", 1024, "RSA", bits, user, email,
191 expdate ? expdate : "0", passphrase );
192 break;
193
194 case GPG_KEYGEN_DSA_SIG:
195 sprintf( p, key_params_one,
196 "DSA", 1024, "sign",
197 user, email,
198 expdate ? expdate : "0", passphrase );
199 break;
200
201 case GPG_KEYGEN_RSA_SIG:
202 sprintf( p, key_params_one,
203 "RSA", bits, "sign",
204 user, email,
205 expdate ? expdate : "0", passphrase );
206 break;
207
208 case GPG_KEYGEN_RSA:
209 sprintf( p, key_params_one,
210 "RSA", bits, "sign encrypt",
211 user, email,
212 expdate ? expdate : "0", passphrase );
213 break;
214
215 case GPG_KEYGEN_RSA_RSA:
216 sprintf( p, key_params,
217 "RSA", bits, "RSA", bits, user, email,
218 expdate? expdate : "0", passphrase );
219 break;
220
221 default:
222 free_if_alloc (p);
223 break;
224 }
225 }
226 return p;
227 }
228
229
230 /* Generate a key with the given params @params. @prog_cb is a user defined
231 progress callback which is called during the generation.
232 @fpr will store the fingerprint of the generated key.
233 Return value: 0 on success. */
234 gpgme_error_t
235 gpg_genkey (const char *params, gpgme_progress_cb_t prog_cb, char **fpr)
236 {
237 gpgme_error_t err = 0;
238 gpgme_ctx_t ctx;
239 gpgme_genkey_result_t res;
240
241 err = gpgme_new(&ctx);
242 if (err)
243 return err;
244 if (prog_cb)
245 gpgme_set_progress_cb (ctx, prog_cb, NULL);
246 err = gpgme_op_genkey (ctx, params, NULL, NULL);
247 if (!err) {
248 res = gpgme_op_genkey_result (ctx);
249 *fpr = res && res->fpr? m_strdup (res->fpr) : NULL;
250 }
251 gpgme_release (ctx);
252 return err;
253 }
254
255
256
257 /* Clear all dialog fields. */
258 static void
259 clear_dlg_fields (HWND dlg)
260 {
261 SetDlgItemText (dlg, IDC_KEYGEN_SUBKEYBITS, "");
262 SetDlgItemText (dlg, IDC_KEYGEN_NAME, "");
263 SetDlgItemText (dlg, IDC_KEYGEN_EMAIL, "");
264 SetDlgItemText (dlg, IDC_KEYGEN_COMMENT, "");
265 SetDlgItemText (dlg, IDC_KEYGEN_EXPDATE, "");
266 SetDlgItemText (dlg, IDC_KEYGEN_PASSPHRASE, "");
267 SetDlgItemText (dlg, IDC_KEYGEN_PWDCHECK, "");
268 }
269
270
271 /* Ask the user if a keyring backup is wanted and if so,
272 backup both keyrings to the selected folder. @dlg is
273 the handle of the parent window.*/
274 static void
275 backup_keyrings (HWND dlg)
276 {
277 const char *name;
278 char *path = NULL;
279 char *keyring = NULL;
280 int id;
281
282 path = get_gnupg_path ();
283 if (!path)
284 BUG (dlg);
285 id = msg_box (dlg,
286 _("It is STRONGLY recommend that you backup your keyrings because they both "
287 "contain VERY important data.\nRemember that your hard disk can crash or the "
288 "files can be deleted by accident; so it is a good\nidea to store them on "
289 "a different mass stoarge like a floppy or CDR!\n\n"
290 "Backup your keyrings now?"),
291 _("WARNING - Important hint" ), MB_YESNO);
292 if (id == IDYES) {
293 name = get_filesave_dlg (dlg, _("Destination for Public Keyring"),
294 NULL, "pubring.gpg");
295 if( name ) {
296 keyring = make_filename (path, "pubring", "gpg");
297 if (!CopyFile (keyring, name, FALSE))
298 log_box (_("Key Generation"), MB_ERR,
299 _("Could not copy %s -> %s"), keyring, name);
300 free_if_alloc (keyring);
301 }
302 name = get_filesave_dlg (dlg, _("Destination for Secret Keyring"),
303 NULL, "secring.gpg");
304 if (name) {
305 keyring = make_filename (path, "secring", "gpg");
306 if (!CopyFile (keyring, name, FALSE))
307 log_box (_("Key Generation"), MB_ERR,
308 _("Could not copy %s -> %s"), keyring, name);
309 free_if_alloc (keyring);
310 }
311 }
312 free_if_alloc (path);
313 }
314
315
316 /* Fill in all valid GPG algorithms. */
317 static void
318 fill_keytype_box (HWND dlg)
319 {
320 HWND cb = GetDlgItem (dlg, IDC_KEYGEN_KEYTYPE);
321
322 #define addstr(cb, str) \
323 SendMessage ((cb), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)(str))
324 addstr (cb, _("DSA and ELG (default)"));
325 addstr (cb, _("DSA and RSA"));
326 addstr (cb, _("DSA sign only"));
327 addstr (cb, _("RSA sign only"));
328 addstr (cb, _("RSA sign and encrypt"));
329 addstr (cb, _("RSA and RSA (PGP)") );
330 SendMessage (cb, CB_SETCURSEL, 0, 0);
331 #undef addstr
332 }
333
334
335 time_t w32_mktime (SYSTEMTIME *st);
336
337 /* Check that the given date lies not in the past.
338 Return value: 1 on success. */
339 int
340 keygen_check_date (SYSTEMTIME *st)
341 {
342 time_t dat, now;
343
344 dat = w32_mktime (st);
345 now = time (NULL);
346 if (dat >= now)
347 return 1;
348 return 0;
349 }
350
351
352 /* Dialog box procedure for key generation. */
353 BOOL CALLBACK
354 keygen_dlg_proc (HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam)
355 {
356 static genkey_s *ctx;
357 SYSTEMTIME st;
358 gpgme_error_t err;
359 char *utf8_name =NULL, *utf8_comment = NULL;
360 char email[128];
361 char *pwd;
362 char t[64], *expire = NULL, *fpr=NULL;
363 int bits, use_comment, keytype = 0;
364 int cancel = 0;
365 char *p;
366
367 switch (msg) {
368 case WM_INITDIALOG:
369 if (lparam != 0)
370 ctx = (genkey_s *)lparam;
371 SetWindowText (dlg, _("Key Generation"));
372 SetDlgItemText(dlg, IDC_KEYGEN_INFO,
373 _("NOTE: Key generation can be a lengthy process! Please wait until "
374 "you get the message that key generation was finished."));
375 SetDlgItemText (dlg, IDC_KEYGEN_SUBKEYINF, _("Subkey size in &bits"));
376 SetDlgItemText (dlg, IDC_KEYGEN_NAMEINF, _("&Real name"));
377 SetDlgItemText (dlg, IDC_KEYGEN_COMMINF, _("&Comment (optional)"));
378 SetDlgItemText (dlg, IDC_KEYGEN_EMAILINF, _("Email &address"));
379 SetDlgItemText (dlg, IDC_KEYGEN_EXPINF, _("&Expire date"));
380 SetDlgItemText (dlg, IDC_KEYGEN_KEYTYPEINF, _("Key &type"));
381 SetDlgItemText (dlg, IDC_KEYGEN_EXPNEVER, _("&Never"));
382 SetDlgItemText (dlg, IDCANCEL, _("&Cancel"));
383
384 SetDlgItemInt (dlg, IDC_KEYGEN_SUBKEYBITS, DFAULT_KEYSIZE, FALSE);
385 CheckDlgButton (dlg, IDC_KEYGEN_HIDEPWD, BST_CHECKED);
386 CheckDlgButton (dlg, IDC_KEYGEN_EXPNEVER, BST_CHECKED);
387 EnableWindow (GetDlgItem (dlg, IDC_KEYGEN_EXPDATE), FALSE);
388 fill_keytype_box (dlg);
389 center_window (dlg, NULL);
390 SetForegroundWindow (dlg);
391 return TRUE;
392
393 case WM_COMMAND:
394 if (HIWORD (wparam) == BN_CLICKED &&
395 LOWORD (wparam) == IDC_KEYGEN_EXPNEVER) {
396 int never = IsDlgButtonChecked (dlg, IDC_KEYGEN_EXPNEVER);
397 EnableWindow (GetDlgItem (dlg, IDC_KEYGEN_EXPDATE), !never);
398 }
399
400 switch (LOWORD (wparam)) {
401 case IDOK:
402 bits = GetDlgItemInt (dlg, IDC_KEYGEN_SUBKEYBITS, NULL, FALSE);
403 if (bits < 1024 || bits > 4096) {
404 msg_box (dlg, _("Invalid value. Allowed values 1024-4096 bits."),
405 _("Key Generation"), MB_ERR);
406 return FALSE;
407 }
408 if (bits > DFAULT_KEYSIZE) {
409 int id = msg_box (dlg, _("Do you really need such a large key?"),
410 _("Key Generation"), MB_YESNO);
411 if (id == IDNO)
412 bits = DFAULT_KEYSIZE;
413 }
414 if (!GetDlgItemText_utf8 (dlg, IDC_KEYGEN_NAME, &utf8_name)) {
415 msg_box (dlg, _("Please enter the name."), _("Key Generation"), MB_ERR);
416 return FALSE;
417 }
418 if (strchr (utf8_name, '@')) {
419 msg_box (dlg, _("Please do not enter the email address in the name field."),
420 _("Key Generation"), MB_INFO);
421 free_if_alloc (utf8_name);
422 return FALSE;
423 }
424 if (!GetDlgItemText (dlg, IDC_KEYGEN_EMAIL, email, sizeof (email) -1)
425 || check_email_address (email)) {
426 msg_box (dlg, _("Please enter a valid email address."),
427 _("Key Generation"), MB_ERR);
428 free_if_alloc (utf8_name);
429 return FALSE;
430 }
431 use_comment = GetDlgItemText_utf8 (dlg, IDC_KEYGEN_COMMENT,
432 &utf8_comment);
433 if (use_comment > 0 && strchr (utf8_comment, '@')) {
434 msg_box (dlg, _("Please do NOT enter the email address in the comment field."),
435 _("Key Generation"), MB_INFO);
436 free_if_alloc (utf8_name);
437 free_if_alloc (utf8_comment);
438 return TRUE;
439 }
440 keytype = SendDlgItemMessage (dlg, IDC_KEYGEN_KEYTYPE, CB_GETCURSEL, 0, 0) + 1;
441 if (IsDlgButtonChecked (dlg, IDC_KEYGEN_EXPNEVER))
442 expire = NULL;
443 else {
444 DateTime_GetSystemtime (GetDlgItem (dlg, IDC_KEYGEN_EXPDATE), &st);
445 if (!keygen_check_date (&st)) {
446 free_if_alloc (utf8_name);
447 free_if_alloc (utf8_comment);
448 msg_box (dlg, _("The date you have chosen lies in the past."),
449 _("Key Generation"), MB_ERR);
450 return TRUE;
451 }
452 _snprintf (t, DIM (t)-1, "%04d-%02d-%02d", st.wYear, st.wMonth, st.wDay);
453 expire = t;
454 }
455
456 pwd = request_passphrase2 (_("Key Generation"),
457 PASSDLG_STRICT|PASSDLG_WARN_UTF8, &cancel);
458 if (cancel) {
459 sfree_if_alloc (pwd);
460 free_if_alloc (utf8_name);
461 free_if_alloc (utf8_comment);
462 return FALSE;
463 }
464 if (!pwd) {
465 msg_box (dlg, _("Please enter the passphrase."),
466 _("Key Generation"), MB_ERR);
467 free_if_alloc (utf8_name);
468 free_if_alloc (utf8_comment);
469 return FALSE;
470 }
471
472 if (!use_comment && !utf8_comment)
473 p = gpg_genkey_params (keytype, bits, utf8_name, NULL,
474 email, expire, pwd);
475 else
476 p = gpg_genkey_params (keytype, bits, utf8_name, utf8_comment,
477 email, expire, pwd);
478 free_if_alloc (utf8_name);
479 free_if_alloc (utf8_comment);
480 keygen_cb_dlg_create ();
481 err = gpg_genkey (p, keygen_cb, &fpr);
482 sfree_if_alloc (pwd);
483 sfree_if_alloc (p); /* burn the passphrase! */
484 keygen_cb_dlg_destroy (1);
485 if (err) {
486 free_if_alloc (fpr);
487 msg_box (dlg, gpgme_strerror (err), _("Key Generation"), MB_ERR);
488 return FALSE;
489 }
490 status_box (dlg, _("Key Generation completed"), _("GnuPG Status"));
491
492 keycache_update (0, fpr);
493 keycache_update (1, fpr);
494 free_if_alloc (fpr);
495
496 clear_dlg_fields (dlg);
497 backup_keyrings (dlg);
498 if (ctx)
499 ctx->cancel = 0;
500 EndDialog (dlg, TRUE);
501 return TRUE;
502
503 case IDCANCEL:
504 if (ctx)
505 ctx->cancel = 1;
506 EndDialog (dlg, FALSE);
507 return FALSE;
508 }
509 break;
510 }
511
512 return FALSE;
513 }
514
515
516 /* Wizard like keygen dialog for novice users. */
517 BOOL CALLBACK
518 keygen_wizard_dlg_proc (HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam)
519 {
520 static genkey_s *ctx;
521 static int pubkey_algo = GPG_KEYGEN_DSA_ELG;
522 gpgme_error_t err;
523 char email[128];
524 char *utf8_name=NULL, *p, *fpr=NULL;
525 char *pass = NULL;
526 int cancel = 0;
527
528 switch (msg) {
529 case WM_INITDIALOG:
530 ctx = (genkey_s *)lparam;
531 SetDlgItemText (dlg, IDC_KEYWIZARD_USERSA, _("&Prefer RSA keys"));
532 SetDlgItemText (dlg, IDC_KEYWIZARD_NAMEINF, _("Real name:"));
533 SetDlgItemText (dlg, IDC_KEYWIZARD_EMAILINF, _("Email address:"));
534 SetDlgItemText (dlg, IDC_KEYWIZARD_TITLEINF, _("Name and E-Mail Assignment"));
535 SetDlgItemText (dlg, IDC_KEYWIZARD_TEXT1INF, _("Every key pair must have a name associated with it. The name and\nemail address let your correspondents know that your public key they\nare using belongs to us."));
536 SetDlgItemText (dlg, IDC_KEYWIZARD_TEXT2INF, _("By associating an email address with your key pair, you will enable WinPT to assist your correspondents in selecting the correct public\nkey when communicating with you."));
537 SetWindowText (dlg, _("Key Generation Wizard"));
538 SetDlgItemText (dlg, IDCANCEL, _("&Cancel"));
539 SetForegroundWindow (dlg);
540 center_window (dlg, NULL);
541 break;
542
543 case WM_COMMAND:
544 switch (LOWORD( wparam)) {
545 case IDOK:
546 if (!GetDlgItemText_utf8 (dlg, IDC_KEYWIZARD_NAME, &utf8_name)) {
547 msg_box (dlg, _("Please enter the name."),
548 _("Key Generation Wizard"), MB_ERR);
549 return FALSE;
550 }
551 if (strchr (utf8_name, '@')) {
552 msg_box (dlg, _("Please do not enter the email address in the name field."),
553 _("Key Generation Wizard"), MB_WARN);
554 free_if_alloc (utf8_name);
555 return FALSE;
556 }
557 if (!GetDlgItemText(dlg, IDC_KEYWIZARD_EMAIL, email, sizeof email-1 )
558 || check_email_address (email)) {
559 msg_box (dlg, _("Please enter a valid email address."),
560 _("Key Generation Wizard"), MB_ERR);
561 free_if_alloc (utf8_name);
562 return FALSE;
563 }
564 if (strchr (email, '<') || strchr (email, '>')) {
565 msg_box (dlg, _("Please do not add '<' or '>' to the email address."),
566 _("Key Generation Wizard"), MB_WARN);
567 free_if_alloc (utf8_name);
568 return FALSE;
569 }
570 pass = request_passphrase2 (_("Key Generation"),
571 PASSDLG_STRICT|PASSDLG_WARN_UTF8, &cancel);
572 if (cancel) {
573 free_if_alloc (utf8_name);
574 return FALSE;
575 }
576 if (IsDlgButtonChecked (dlg, IDC_KEYWIZARD_USERSA))
577 pubkey_algo = GPG_KEYGEN_DSA_RSA;
578 p = gpg_genkey_params (pubkey_algo, DFAULT_KEYSIZE, utf8_name,
579 NULL, email, NULL, pass);
580 free_if_alloc (utf8_name);
581 keygen_cb_dlg_create();
582 err = gpg_genkey (p, keygen_cb, &fpr);
583 keygen_cb_dlg_destroy (1);
584 sfree_if_alloc (p);
585 sfree_if_alloc (pass);
586 if (err) {
587 msg_box (dlg, gpgme_strerror (err), _("Key Generation Wizard"), MB_ERR);
588 free_if_alloc (fpr);
589 return FALSE;
590 }
591 status_box (dlg, _("Key Generation completed"), _("GnuPG Status"));
592 keycache_update (0, fpr);
593 keycache_update (1, fpr);
594 free_if_alloc (fpr);
595
596 backup_keyrings (dlg);
597 if (ctx)
598 ctx->cancel = 0;
599 EndDialog (dlg, TRUE);
600 break;
601
602 case IDCANCEL:
603 if (ctx)
604 ctx->cancel = 1;
605 EndDialog (dlg, FALSE);
606 break;
607 }
608 break;
609 }
610 return FALSE;
611 }

Properties

Name Value
svn:eol-style native

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26