/[pcsclite]/tags/ccid/ccid-1.3.8/examples/scardcontrol.c
ViewVC logotype

Contents of /tags/ccid/ccid-1.3.8/examples/scardcontrol.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3113 - (show annotations) (download)
Sat Sep 6 14:14:30 2008 UTC (4 years, 8 months ago) by rousseau
File MIME type: text/plain
File size: 16593 byte(s)
release 1.3.8
1 /*
2 scardcontrol.c: sample code to use/test SCardControl() API
3 Copyright (C) 2004-2008 Ludovic Rousseau
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc., 51
17 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /*
21 * $Id$
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <sys/time.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <arpa/inet.h>
30 #ifdef __APPLE__
31 #include <PCSC/winscard.h>
32 #include <PCSC/wintypes.h>
33 #else
34 #include <winscard.h>
35 #endif
36 #include <reader.h>
37
38 #undef VERIFY_PIN
39 #define MODIFY_PIN
40
41 #ifndef TRUE
42 #define TRUE 1
43 #define FALSE 0
44 #endif
45
46 #define IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
47
48 /* PCSC error message pretty print */
49 #define PCSC_ERROR_EXIT(rv, text) \
50 if (rv != SCARD_S_SUCCESS) \
51 { \
52 printf(text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
53 goto end; \
54 } \
55 else \
56 printf(text ": OK\n\n");
57
58 #define PCSC_ERROR_CONT(rv, text) \
59 if (rv != SCARD_S_SUCCESS) \
60 printf(text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
61 else \
62 printf(text ": OK\n\n");
63
64 int main(int argc, char *argv[])
65 {
66 LONG rv;
67 SCARDCONTEXT hContext;
68 DWORD dwReaders;
69 LPSTR mszReaders = NULL;
70 char *ptr, **readers = NULL;
71 int nbReaders;
72 SCARDHANDLE hCard;
73 DWORD dwActiveProtocol, dwReaderLen, dwState, dwProt, dwAtrLen;
74 BYTE pbAtr[MAX_ATR_SIZE] = "";
75 char pbReader[MAX_READERNAME] = "";
76 int reader_nb;
77 unsigned int i;
78 unsigned char bSendBuffer[MAX_BUFFER_SIZE];
79 unsigned char bRecvBuffer[MAX_BUFFER_SIZE];
80 DWORD send_length, length;
81 DWORD verify_ioctl = 0;
82 DWORD modify_ioctl = 0;
83 SCARD_IO_REQUEST pioRecvPci;
84 SCARD_IO_REQUEST pioSendPci;
85 PCSC_TLV_STRUCTURE *pcsc_tlv;
86 #if defined(VERIFY_PIN) | defined(MODIFY_PIN)
87 int offset;
88 #endif
89 #ifdef VERIFY_PIN
90 PIN_VERIFY_STRUCTURE *pin_verify;
91 #endif
92 #ifdef MODIFY_PIN
93 PIN_MODIFY_STRUCTURE *pin_modify;
94 #endif
95
96 printf("SCardControl sample code\n");
97 printf("V 1.3 2004-2008, Ludovic Rousseau <ludovic.rousseau@free.fr>\n");
98
99 printf("\nTHIS PROGRAM IS NOT DESIGNED AS A TESTING TOOL!\n");
100 printf("Do NOT use it unless you really know what you do.\n\n");
101
102 rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
103 if (rv != SCARD_S_SUCCESS)
104 {
105 printf("SCardEstablishContext: Cannot Connect to Resource Manager %lX\n", rv);
106 return 1;
107 }
108
109 /* Retrieve the available readers list */
110 rv = SCardListReaders(hContext, NULL, NULL, &dwReaders);
111 PCSC_ERROR_EXIT(rv, "SCardListReaders")
112
113 mszReaders = malloc(sizeof(char)*dwReaders);
114 if (mszReaders == NULL)
115 {
116 printf("malloc: not enough memory\n");
117 goto end;
118 }
119
120 rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
121 if (rv != SCARD_S_SUCCESS)
122 printf("SCardListReader: %lX\n", rv);
123
124 /* Extract readers from the null separated string and get the total
125 * number of readers */
126 nbReaders = 0;
127 ptr = mszReaders;
128 while (*ptr != '\0')
129 {
130 ptr += strlen(ptr)+1;
131 nbReaders++;
132 }
133
134 if (nbReaders == 0)
135 {
136 printf("No reader found\n");
137 goto end;
138 }
139
140 /* allocate the readers table */
141 readers = calloc(nbReaders, sizeof(char *));
142 if (NULL == readers)
143 {
144 printf("Not enough memory for readers[]\n");
145 goto end;
146 }
147
148 /* fill the readers table */
149 nbReaders = 0;
150 ptr = mszReaders;
151 while (*ptr != '\0')
152 {
153 printf("%d: %s\n", nbReaders, ptr);
154 readers[nbReaders] = ptr;
155 ptr += strlen(ptr)+1;
156 nbReaders++;
157 }
158
159 if (argc > 1)
160 {
161 reader_nb = atoi(argv[1]);
162 if (reader_nb < 0 || reader_nb >= nbReaders)
163 {
164 printf("Wrong reader index: %d\n", reader_nb);
165 goto end;
166 }
167 }
168 else
169 reader_nb = 0;
170
171 /* connect to a reader (even without a card) */
172 dwActiveProtocol = -1;
173 rv = SCardConnect(hContext, readers[reader_nb], SCARD_SHARE_DIRECT,
174 SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
175 printf(" Protocol: %ld\n", dwActiveProtocol);
176 PCSC_ERROR_EXIT(rv, "SCardConnect")
177
178 /* get GemPC firmware */
179 printf(" Get GemPC Firmware\n");
180
181 /* this is specific to Gemplus readers */
182 bSendBuffer[0] = 0x02;
183 rv = SCardControl(hCard, IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE, bSendBuffer,
184 1, bRecvBuffer, sizeof(bRecvBuffer), &length);
185
186 printf(" Firmware: ");
187 for (i=0; i<length; i++)
188 printf("%02X ", bRecvBuffer[i]);
189 printf("\n");
190
191 bRecvBuffer[length] = '\0';
192 printf(" Firmware: %s (length %ld bytes)\n", bRecvBuffer, length);
193
194 PCSC_ERROR_CONT(rv, "SCardControl")
195
196 /* get card status */
197 dwAtrLen = sizeof(pbAtr);
198 dwReaderLen = sizeof(pbReader);
199 rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt,
200 pbAtr, &dwAtrLen);
201 printf(" Reader: %s (length %ld bytes)\n", pbReader, dwReaderLen);
202 printf(" State: 0x%04lX\n", dwState);
203 printf(" Prot: %ld\n", dwProt);
204 printf(" ATR (length %ld bytes):", dwAtrLen);
205 for (i=0; i<dwAtrLen; i++)
206 printf(" %02X", pbAtr[i]);
207 printf("\n");
208 PCSC_ERROR_CONT(rv, "SCardStatus")
209
210 if (dwState & SCARD_ABSENT)
211 {
212 printf("No card inserted\n");
213 goto end;
214 }
215
216 /* does the reader support PIN verification? */
217 rv = SCardControl(hCard, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
218 bRecvBuffer, sizeof(bRecvBuffer), &length);
219
220 printf(" TLV (%ld): ", length);
221 for (i=0; i<length; i++)
222 printf("%02X ", bRecvBuffer[i]);
223 printf("\n");
224
225 PCSC_ERROR_CONT(rv, "SCardControl(CM_IOCTL_GET_FEATURE_REQUEST)")
226
227 if (length % sizeof(PCSC_TLV_STRUCTURE))
228 {
229 printf("Inconsistent result! Bad TLV values!\n");
230 goto end;
231 }
232
233 /* get the number of elements instead of the complete size */
234 length /= sizeof(PCSC_TLV_STRUCTURE);
235
236 pcsc_tlv = (PCSC_TLV_STRUCTURE *)bRecvBuffer;
237 for (i = 0; i < length; i++)
238 {
239 if (pcsc_tlv[i].tag == FEATURE_VERIFY_PIN_DIRECT)
240 verify_ioctl = ntohl(pcsc_tlv[i].value);
241 if (pcsc_tlv[i].tag == FEATURE_MODIFY_PIN_DIRECT)
242 modify_ioctl = ntohl(pcsc_tlv[i].value);
243 }
244
245 if (0 == verify_ioctl)
246 {
247 printf("Reader %s does not support PIN verification\n",
248 readers[reader_nb]);
249 goto end;
250 }
251
252 /* connect to a reader (even without a card) */
253 dwActiveProtocol = -1;
254 rv = SCardReconnect(hCard, SCARD_SHARE_SHARED,
255 SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, SCARD_LEAVE_CARD,
256 &dwActiveProtocol);
257 printf(" Protocol: %ld\n", dwActiveProtocol);
258 PCSC_ERROR_EXIT(rv, "SCardReconnect")
259
260 switch(dwActiveProtocol)
261 {
262 case SCARD_PROTOCOL_T0:
263 pioSendPci = *SCARD_PCI_T0;
264 break;
265 case SCARD_PROTOCOL_T1:
266 pioSendPci = *SCARD_PCI_T1;
267 break;
268 default:
269 printf("Unknown protocol. No card present?\n");
270 return -1;
271 }
272
273 /* APDU select applet */
274 printf("Select applet: ");
275 send_length = 11;
276 memcpy(bSendBuffer, "\x00\xA4\x04\x00\x06\xA0\x00\x00\x00\x18\xFF",
277 send_length);
278 for (i=0; i<send_length; i++)
279 printf(" %02X", bSendBuffer[i]);
280 printf("\n");
281 length = sizeof(bRecvBuffer);
282 rv = SCardTransmit(hCard, &pioSendPci, bSendBuffer, send_length,
283 &pioRecvPci, bRecvBuffer, &length);
284 printf(" card response:");
285 for (i=0; i<length; i++)
286 printf(" %02X", bRecvBuffer[i]);
287 printf("\n");
288 PCSC_ERROR_EXIT(rv, "SCardTransmit")
289 if ((bRecvBuffer[0] != 0x90) || (bRecvBuffer[1] != 0x00))
290 {
291 printf("Error: test applet not found!\n");
292 goto end;
293 }
294
295 #ifdef VERIFY_PIN
296 /* verify PIN */
297 printf(" Secure verify PIN\n");
298 pin_verify = (PIN_VERIFY_STRUCTURE *)bSendBuffer;
299
300 /* PC/SC v2.0.2 Part 10 PIN verification data structure */
301 pin_verify -> bTimerOut = 0x00;
302 pin_verify -> bTimerOut2 = 0x00;
303 pin_verify -> bmFormatString = 0x82;
304 pin_verify -> bmPINBlockString = 0x04;
305 pin_verify -> bmPINLengthFormat = 0x00;
306 pin_verify -> wPINMaxExtraDigit = HOST_TO_CCID_16(0x0408); /* Min Max */
307 pin_verify -> bEntryValidationCondition = 0x02; /* validation key pressed */
308 pin_verify -> bNumberMessage = 0x01;
309 pin_verify -> wLangId = HOST_TO_CCID_16(0x0904);
310 pin_verify -> bMsgIndex = 0x00;
311 pin_verify -> bTeoPrologue[0] = 0x00;
312 pin_verify -> bTeoPrologue[1] = 0x00;
313 pin_verify -> bTeoPrologue[2] = 0x00;
314 /* pin_verify -> ulDataLength = 0x00; we don't know the size yet */
315
316 /* APDU: 00 20 00 00 08 30 30 30 30 00 00 00 00 */
317 offset = 0;
318 pin_verify -> abData[offset++] = 0x00; /* CLA */
319 pin_verify -> abData[offset++] = 0x20; /* INS: VERIFY */
320 pin_verify -> abData[offset++] = 0x00; /* P1 */
321 pin_verify -> abData[offset++] = 0x00; /* P2 */
322 pin_verify -> abData[offset++] = 0x08; /* Lc: 8 data bytes */
323 pin_verify -> abData[offset++] = 0x30; /* '0' */
324 pin_verify -> abData[offset++] = 0x30; /* '0' */
325 pin_verify -> abData[offset++] = 0x30; /* '0' */
326 pin_verify -> abData[offset++] = 0x30; /* '0' */
327 pin_verify -> abData[offset++] = 0x00; /* '\0' */
328 pin_verify -> abData[offset++] = 0x00; /* '\0' */
329 pin_verify -> abData[offset++] = 0x00; /* '\0' */
330 pin_verify -> abData[offset++] = 0x00; /* '\0' */
331 pin_verify -> ulDataLength = HOST_TO_CCID_32(offset); /* APDU size */
332
333 length = sizeof(PIN_VERIFY_STRUCTURE) + offset -1; /* -1 because PIN_VERIFY_STRUCTURE contains the first byte of abData[] */
334
335 printf(" command:");
336 for (i=0; i<length; i++)
337 printf(" %02X", bSendBuffer[i]);
338 printf("\n");
339 printf("Enter your PIN: ");
340 fflush(stdout);
341 rv = SCardControl(hCard, verify_ioctl, bSendBuffer,
342 length, bRecvBuffer, sizeof(bRecvBuffer), &length);
343
344 {
345 #ifndef S_SPLINT_S
346 fd_set fd;
347 #endif
348 struct timeval timeout;
349
350 FD_ZERO(&fd);
351 FD_SET(STDIN_FILENO, &fd); /* stdin */
352 timeout.tv_sec = 0; /* timeout = 0.1s */
353 timeout.tv_usec = 100000;
354
355 /* we only try to read stdin if the pinpad is on a keyboard
356 * we do not read stdin for a SPR 532 for example */
357 if (select(1, &fd, NULL, NULL, &timeout) > 0)
358 {
359 /* read the fake digits */
360 char in[40]; /* 4 digits + \n + \0 */
361 (void)fgets(in, sizeof(in), stdin);
362
363 printf("keyboard sent: %s", in);
364 }
365 else
366 /* if it is not a keyboard */
367 printf("\n");
368 }
369
370 printf(" card response:");
371 for (i=0; i<length; i++)
372 printf(" %02X", bRecvBuffer[i]);
373 printf("\n");
374 PCSC_ERROR_CONT(rv, "SCardControl")
375
376 /* verify PIN dump */
377 printf("\nverify PIN dump: ");
378 send_length = 5;
379 memcpy(bSendBuffer, "\x00\x40\x00\x00\xFF",
380 send_length);
381 for (i=0; i<send_length; i++)
382 printf(" %02X", bSendBuffer[i]);
383 printf("\n");
384 length = sizeof(bRecvBuffer);
385 rv = SCardTransmit(hCard, &pioSendPci, bSendBuffer, send_length,
386 &pioRecvPci, bRecvBuffer, &length);
387 printf(" card response:");
388 for (i=0; i<length; i++)
389 printf(" %02X", bRecvBuffer[i]);
390 printf("\n");
391 PCSC_ERROR_EXIT(rv, "SCardTransmit")
392
393 if ((2 == length) && (0x6C == bRecvBuffer[0]))
394 {
395 printf("\nverify PIN dump: ");
396 send_length = 5;
397 memcpy(bSendBuffer, "\x00\x40\x00\x00\xFF",
398 send_length);
399 bSendBuffer[4] = bRecvBuffer[1];
400 for (i=0; i<send_length; i++)
401 printf(" %02X", bSendBuffer[i]);
402 printf("\n");
403 length = sizeof(bRecvBuffer);
404 rv = SCardTransmit(hCard, &pioSendPci, bSendBuffer, send_length,
405 &pioRecvPci, bRecvBuffer, &length);
406 printf(" card response:");
407 for (i=0; i<length; i++)
408 printf(" %02X", bRecvBuffer[i]);
409 printf("\n");
410 PCSC_ERROR_EXIT(rv, "SCardTransmit")
411 }
412 #endif
413
414 /* check if the reader supports Modify PIN */
415 if (0 == modify_ioctl)
416 {
417 printf("Reader %s does not support PIN modification\n",
418 readers[reader_nb]);
419 goto end;
420 }
421
422 #ifdef MODIFY_PIN
423 /* Modify PIN */
424 printf(" Secure modify PIN\n");
425 pin_modify = (PIN_MODIFY_STRUCTURE *)bSendBuffer;
426
427 /* Table for bConfirmPIN and bNumberMessage
428 * bConfirmPIN = 3, bNumberMessage = 3: "Enter Pin" "New Pin" "Confirm Pin"
429 * bConfirmPIN = 2, bNumberMessage = 2: "Enter Pin" "New Pin"
430 * bConfirmPIN = 1, bNumberMessage = 2: "New Pin" "Confirm Pin"
431 * bConfirmPIN = 0, bNumberMessage = 1: "New Pin"
432 */
433 /* PC/SC v2.0.2 Part 10 PIN verification data structure */
434 pin_modify -> bTimerOut = 0x00;
435 pin_modify -> bTimerOut2 = 0x00;
436 pin_modify -> bmFormatString = 0x82;
437 pin_modify -> bmPINBlockString = 0x04;
438 pin_modify -> bmPINLengthFormat = 0x00;
439 pin_modify -> bInsertionOffsetOld = 0x00; /* offset from APDU start */
440 pin_modify -> bInsertionOffsetNew = 0x04; /* offset from APDU start */
441 pin_modify -> wPINMaxExtraDigit = HOST_TO_CCID_16(0x0408); /* Min Max */
442 pin_modify -> bConfirmPIN = 0x03; /* b0 set = confirmation requested */
443 /* b1 set = current PIN entry requested */
444 pin_modify -> bEntryValidationCondition = 0x02; /* validation key pressed */
445 pin_modify -> bNumberMessage = 0x03; /* see table above */
446 pin_modify -> wLangId = HOST_TO_CCID_16(0x0904);
447 pin_modify -> bMsgIndex1 = 0x00;
448 pin_modify -> bMsgIndex2 = 0x00;
449 pin_modify -> bMsgIndex3 = 0x00;
450 pin_modify -> bTeoPrologue[0] = 0x00;
451 pin_modify -> bTeoPrologue[1] = 0x00;
452 pin_modify -> bTeoPrologue[2] = 0x00;
453 /* pin_modify -> ulDataLength = 0x00; we don't know the size yet */
454
455 /* APDU: 00 20 00 00 08 30 30 30 30 00 00 00 00 */
456 offset = 0;
457 pin_modify -> abData[offset++] = 0x00; /* CLA */
458 pin_modify -> abData[offset++] = 0x24; /* INS: CHANGE/UNBLOCK */
459 pin_modify -> abData[offset++] = 0x00; /* P1 */
460 pin_modify -> abData[offset++] = 0x00; /* P2 */
461 pin_modify -> abData[offset++] = 0x08; /* Lc: 2x8 data bytes */
462 pin_modify -> abData[offset++] = 0x30; /* '0' old PIN */
463 pin_modify -> abData[offset++] = 0x30; /* '0' */
464 pin_modify -> abData[offset++] = 0x30; /* '0' */
465 pin_modify -> abData[offset++] = 0x30; /* '0' */
466 pin_modify -> abData[offset++] = 0x30; /* '0' new PIN */
467 pin_modify -> abData[offset++] = 0x30; /* '0' */
468 pin_modify -> abData[offset++] = 0x30; /* '0' */
469 pin_modify -> abData[offset++] = 0x30; /* '0' */
470 pin_modify -> ulDataLength = HOST_TO_CCID_32(offset); /* APDU size */
471
472 length = sizeof(PIN_MODIFY_STRUCTURE) + offset -1; /* -1 because PIN_MODIFY_STRUCTURE contains the first byte of abData[] */
473
474 printf(" command:");
475 for (i=0; i<length; i++)
476 printf(" %02X", bSendBuffer[i]);
477 printf("\n");
478 printf("Enter your PIN: ");
479 fflush(stdout);
480 rv = SCardControl(hCard, modify_ioctl, bSendBuffer,
481 length, bRecvBuffer, sizeof(bRecvBuffer), &length);
482
483 printf(" card response:");
484 for (i=0; i<length; i++)
485 printf(" %02X", bRecvBuffer[i]);
486 printf("\n");
487 PCSC_ERROR_CONT(rv, "SCardControl")
488
489 {
490 #ifndef S_SPLINT_S
491 fd_set fd;
492 #endif
493 struct timeval timeout;
494
495 /* old PIN, new PIN, confirmation PIN */
496 /* if the command is aborted we will not read every "PIN" */
497 for (i=0; i<3; i++)
498 {
499 FD_ZERO(&fd);
500 FD_SET(STDIN_FILENO, &fd); /* stdin */
501 timeout.tv_sec = 0; /* timeout = 0.1s */
502 timeout.tv_usec = 100000;
503
504 /* we only try to read stdin if the pinpad is on a keyboard
505 * we do not read stdin for a SPR 532 for example */
506 if (select(1, &fd, NULL, NULL, &timeout) > 0)
507 {
508 /* read the fake digits */
509 char in[40]; /* 4 digits + \n + \0 */
510
511 (void)fgets(in, sizeof(in), stdin);
512 printf("keyboard sent: %s", in);
513 }
514 }
515 }
516
517 /* modify PIN dump */
518 printf("\nmodify PIN dump: ");
519 send_length = 5;
520 memcpy(bSendBuffer, "\x00\x40\x00\x00\xFF",
521 send_length);
522 for (i=0; i<send_length; i++)
523 printf(" %02X", bSendBuffer[i]);
524 printf("\n");
525 length = sizeof(bRecvBuffer);
526 rv = SCardTransmit(hCard, &pioSendPci, bSendBuffer, send_length,
527 &pioRecvPci, bRecvBuffer, &length);
528 printf(" card response:");
529 for (i=0; i<length; i++)
530 printf(" %02X", bRecvBuffer[i]);
531 printf("\n");
532 PCSC_ERROR_EXIT(rv, "SCardTransmit")
533
534 if ((2 == length) && (0x6C == bRecvBuffer[0]))
535 {
536 printf("\nverify PIN dump: ");
537 send_length = 5;
538 memcpy(bSendBuffer, "\x00\x40\x00\x00\xFF",
539 send_length);
540 bSendBuffer[4] = bRecvBuffer[1];
541 for (i=0; i<send_length; i++)
542 printf(" %02X", bSendBuffer[i]);
543 printf("\n");
544 length = sizeof(bRecvBuffer);
545 rv = SCardTransmit(hCard, &pioSendPci, bSendBuffer, send_length,
546 &pioRecvPci, bRecvBuffer, &length);
547 printf(" card response:");
548 for (i=0; i<length; i++)
549 printf(" %02X", bRecvBuffer[i]);
550 printf("\n");
551 PCSC_ERROR_EXIT(rv, "SCardTransmit")
552 }
553 #endif
554
555 /* card disconnect */
556 rv = SCardDisconnect(hCard, SCARD_UNPOWER_CARD);
557 PCSC_ERROR_CONT(rv, "SCardDisconnect")
558
559 end:
560 /* We try to leave things as clean as possible */
561 rv = SCardReleaseContext(hContext);
562 if (rv != SCARD_S_SUCCESS)
563 printf("SCardReleaseContext: %s (0x%lX)\n", pcsc_stringify_error(rv),
564 rv);
565
566 /* free allocated memory */
567 if (mszReaders)
568 free(mszReaders);
569 if (readers)
570 free(readers);
571
572 return 0;
573 } /* main */
574

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.5