Develop and Download Open Source Software

Browse Subversion Repository

Annotation of /trunk/ttssh2/ttxssh/hosts.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2850 - (hide annotations) (download) (as text)
Sat Feb 18 07:37:02 2006 UTC (18 years, 1 month ago) by yutakakn
Original Path: ttssh2/trunk/ttxssh/hosts.c
File MIME type: text/x-csrc
File size: 17534 byte(s)
  ・コンパイラを Visual Studio 2005 Standard Edition に切り替えた。
  ・stricmp()を_stricmp()へ置換した
  ・strdup()を_strdup()へ置換した

1 yutakakn 2728 /*
2     Copyright (c) 1998-2001, Robert O'Callahan
3     All rights reserved.
4    
5     Redistribution and use in source and binary forms, with or without modification,
6     are permitted provided that the following conditions are met:
7    
8     Redistributions of source code must retain the above copyright notice, this list of
9     conditions and the following disclaimer.
10    
11     Redistributions in binary form must reproduce the above copyright notice, this list
12     of conditions and the following disclaimer in the documentation and/or other materials
13     provided with the distribution.
14    
15     The name of Robert O'Callahan may not be used to endorse or promote products derived from
16     this software without specific prior written permission.
17    
18     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
19     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21     THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22     EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27     */
28    
29     /*
30     This code is copyright (C) 1998-1999 Robert O'Callahan.
31     See LICENSE.TXT for the license.
32     */
33    
34     #include "ttxssh.h"
35     #include "util.h"
36     #include "resource.h"
37     #include "matcher.h"
38    
39     #include <openssl/bn.h>
40    
41     #include <fcntl.h>
42     #include <io.h>
43     #include <errno.h>
44     #include <sys/stat.h>
45    
46     static char FAR *FAR * parse_multi_path(char FAR * buf)
47     {
48     int i;
49     int ch;
50     int num_paths = 1;
51     char FAR *FAR * result;
52     int last_path_index;
53    
54     for (i = 0; (ch = buf[i]) != 0; i++) {
55     if (ch == ';') {
56     num_paths++;
57     }
58     }
59    
60     result =
61     (char FAR * FAR *) malloc(sizeof(char FAR *) * (num_paths + 1));
62    
63     last_path_index = 0;
64     num_paths = 0;
65     for (i = 0; (ch = buf[i]) != 0; i++) {
66     if (ch == ';') {
67     buf[i] = 0;
68     result[num_paths] = _strdup(buf + last_path_index);
69     num_paths++;
70     buf[i] = ch;
71     last_path_index = i + 1;
72     }
73     }
74     if (i > last_path_index) {
75     result[num_paths] = _strdup(buf + last_path_index);
76     num_paths++;
77     }
78     result[num_paths] = NULL;
79     return result;
80     }
81    
82     void HOSTS_init(PTInstVar pvar)
83     {
84     pvar->hosts_state.prefetched_hostname = NULL;
85     pvar->hosts_state.key_exp = NULL;
86     pvar->hosts_state.key_mod = NULL;
87     pvar->hosts_state.hosts_dialog = NULL;
88     pvar->hosts_state.file_names = NULL;
89     }
90    
91     void HOSTS_open(PTInstVar pvar)
92     {
93     pvar->hosts_state.file_names =
94     parse_multi_path(pvar->session_settings.KnownHostsFiles);
95     }
96    
97     static int begin_read_file(PTInstVar pvar, char FAR * name,
98     int suppress_errors)
99     {
100     int fd;
101     int length;
102     int amount_read;
103     char buf[2048];
104    
105     get_teraterm_dir_relative_name(buf, sizeof(buf), name);
106     fd = _open(buf, _O_RDONLY | _O_SEQUENTIAL | _O_BINARY);
107     if (fd == -1) {
108     if (!suppress_errors) {
109     if (errno == ENOENT) {
110     notify_nonfatal_error(pvar,
111     "An error occurred while trying to read a known_hosts file.\n"
112     "The specified filename does not exist.");
113     } else {
114     notify_nonfatal_error(pvar,
115     "An error occurred while trying to read a known_hosts file.");
116     }
117     }
118     return 0;
119     }
120    
121     length = (int) _lseek(fd, 0, SEEK_END);
122     _lseek(fd, 0, SEEK_SET);
123    
124     if (length >= 0 && length < 0x7FFFFFFF) {
125     pvar->hosts_state.file_data = malloc(length + 1);
126     if (pvar->hosts_state.file_data == NULL) {
127     if (!suppress_errors) {
128     notify_nonfatal_error(pvar,
129     "Memory ran out while trying to allocate space to read a known_hosts file.");
130     }
131     _close(fd);
132     return 0;
133     }
134     } else {
135     if (!suppress_errors) {
136     notify_nonfatal_error(pvar,
137     "An error occurred while trying to read a known_hosts file.");
138     }
139     _close(fd);
140     return 0;
141     }
142    
143     amount_read = _read(fd, pvar->hosts_state.file_data, length);
144     pvar->hosts_state.file_data[length] = 0;
145    
146     _close(fd);
147    
148     if (amount_read != length) {
149     if (!suppress_errors) {
150     notify_nonfatal_error(pvar,
151     "An error occurred while trying to read a known_hosts file.");
152     }
153     free(pvar->hosts_state.file_data);
154     pvar->hosts_state.file_data = NULL;
155     return 0;
156     } else {
157     return 1;
158     }
159     }
160    
161     static int end_read_file(PTInstVar pvar, int suppress_errors)
162     {
163     free(pvar->hosts_state.file_data);
164     pvar->hosts_state.file_data = NULL;
165     return 1;
166     }
167    
168     static int begin_read_host_files(PTInstVar pvar, int suppress_errors)
169     {
170     pvar->hosts_state.file_num = 0;
171     pvar->hosts_state.file_data = NULL;
172     return 1;
173     }
174    
175     static int eat_spaces(char FAR * data)
176     {
177     int index = 0;
178     int ch;
179    
180     while ((ch = data[index]) == ' ' || ch == '\t') {
181     index++;
182     }
183     return index;
184     }
185    
186     static int eat_digits(char FAR * data)
187     {
188     int index = 0;
189     int ch;
190    
191     while ((ch = data[index]) >= '0' && ch <= '9') {
192     index++;
193     }
194     return index;
195     }
196    
197     static int eat_to_end_of_line(char FAR * data)
198     {
199     int index = 0;
200     int ch;
201    
202     while ((ch = data[index]) != '\n' && ch != '\r' && ch != 0) {
203     index++;
204     }
205    
206     while ((ch = data[index]) == '\n' || ch == '\r') {
207     index++;
208     }
209    
210     return index;
211     }
212    
213     static int eat_to_end_of_pattern(char FAR * data)
214     {
215     int index = 0;
216     int ch;
217    
218     while (ch = data[index], is_pattern_char(ch)) {
219     index++;
220     }
221    
222     return index;
223     }
224    
225     static char FAR *parse_bignum(char FAR * data)
226     {
227     uint32 digits = 0;
228     BIGNUM *num = BN_new();
229     BIGNUM *billion = BN_new();
230     BIGNUM *digits_num = BN_new();
231     BN_CTX *ctx = BN_CTX_new();
232     char FAR *result;
233     int ch;
234     int leftover_digits = 1;
235    
236     BN_CTX_init(ctx);
237     BN_set_word(num, 0);
238     BN_set_word(billion, 1000000000L);
239    
240     while ((ch = *data) >= '0' && ch <= '9') {
241     if (leftover_digits == 1000000000L) {
242     BN_set_word(digits_num, digits);
243     BN_mul(num, num, billion, ctx);
244     BN_add(num, num, digits_num);
245     leftover_digits = 1;
246     digits = 0;
247     }
248    
249     digits = digits * 10 + ch - '0';
250     leftover_digits *= 10;
251     data++;
252     }
253    
254     BN_set_word(digits_num, digits);
255     BN_set_word(billion, leftover_digits);
256     BN_mul(num, num, billion, ctx);
257     BN_add(num, num, digits_num);
258    
259     result = (char FAR *) malloc(2 + BN_num_bytes(num));
260     set_ushort16_MSBfirst(result, BN_num_bits(num));
261     BN_bn2bin(num, result + 2);
262    
263     BN_CTX_free(ctx);
264     BN_free(digits_num);
265     BN_free(num);
266     BN_free(billion);
267    
268     return result;
269     }
270    
271     static int check_host_key(PTInstVar pvar, char FAR * hostname,
272     char FAR * data)
273     {
274     int index = eat_spaces(data);
275     int matched = 0;
276     int keybits = 0;
277    
278     if (data[index] == '#') {
279     return index + eat_to_end_of_line(data + index);
280     }
281    
282     /* if we find an empty line, then it won't have any patterns matching the hostname
283     and so we skip it */
284     index--;
285     do {
286     int negated;
287    
288     index++;
289     negated = data[index] == '!';
290    
291     if (negated) {
292     index++;
293     if (match_pattern(data + index, hostname)) {
294     return index + eat_to_end_of_line(data + index);
295     }
296     } else if (match_pattern(data + index, hostname)) {
297     matched = 1;
298     }
299    
300     index += eat_to_end_of_pattern(data + index);
301     } while (data[index] == ',');
302    
303     if (!matched) {
304     return index + eat_to_end_of_line(data + index);
305     } else {
306     index += eat_spaces(data + index);
307    
308     pvar->hosts_state.key_bits = atoi(data + index);
309     index += eat_digits(data + index);
310     index += eat_spaces(data + index);
311    
312     pvar->hosts_state.key_exp = parse_bignum(data + index);
313     index += eat_digits(data + index);
314     index += eat_spaces(data + index);
315    
316     pvar->hosts_state.key_mod = parse_bignum(data + index);
317    
318     if (pvar->hosts_state.key_bits < 0
319     || pvar->hosts_state.key_exp == NULL
320     || pvar->hosts_state.key_mod == NULL) {
321     pvar->hosts_state.key_bits = 0;
322     free(pvar->hosts_state.key_exp);
323     free(pvar->hosts_state.key_mod);
324     }
325    
326     return index + eat_to_end_of_line(data + index);
327     }
328     }
329    
330     static int read_host_key(PTInstVar pvar, char FAR * hostname,
331     int suppress_errors)
332     {
333     int i;
334    
335     for (i = 0; hostname[i] != 0; i++) {
336     int ch = hostname[i];
337    
338     if (!is_pattern_char(ch) || ch == '*' || ch == '?') {
339     if (!suppress_errors) {
340     notify_fatal_error(pvar,
341     "The host name contains an invalid character.\n"
342     "This session will be terminated.");
343     }
344     return 0;
345     }
346     }
347    
348     if (i == 0) {
349     if (!suppress_errors) {
350     notify_fatal_error(pvar, "The host name should not be empty.\n"
351     "This session will be terminated.");
352     }
353     return 0;
354     }
355    
356     pvar->hosts_state.key_bits = 0;
357     free(pvar->hosts_state.key_exp);
358     pvar->hosts_state.key_exp = NULL;
359     free(pvar->hosts_state.key_mod);
360     pvar->hosts_state.key_mod = NULL;
361    
362     do {
363     if (pvar->hosts_state.file_data == NULL
364     || pvar->hosts_state.file_data[pvar->hosts_state.
365     file_data_index] == 0) {
366     char FAR *filename;
367     int keep_going = 1;
368    
369     if (pvar->hosts_state.file_data != NULL) {
370     end_read_file(pvar, suppress_errors);
371     }
372    
373     do {
374     filename =
375     pvar->hosts_state.file_names[pvar->hosts_state.
376     file_num];
377    
378     if (filename == NULL) {
379     return 1;
380     } else {
381     pvar->hosts_state.file_num++;
382    
383     if (filename[0] != 0) {
384     if (begin_read_file
385     (pvar, filename, suppress_errors)) {
386     pvar->hosts_state.file_data_index = 0;
387     keep_going = 0;
388     }
389     }
390     }
391     } while (keep_going);
392     }
393    
394     pvar->hosts_state.file_data_index +=
395     check_host_key(pvar, hostname,
396     pvar->hosts_state.file_data +
397     pvar->hosts_state.file_data_index);
398     } while (pvar->hosts_state.key_bits == 0);
399    
400     return 1;
401     }
402    
403     static void finish_read_host_files(PTInstVar pvar, int suppress_errors)
404     {
405     if (pvar->hosts_state.file_data != NULL) {
406     end_read_file(pvar, suppress_errors);
407     }
408     }
409    
410     void HOSTS_prefetch_host_key(PTInstVar pvar, char FAR * hostname)
411     {
412     if (!begin_read_host_files(pvar, 1)) {
413     return;
414     }
415    
416     if (!read_host_key(pvar, hostname, 1)) {
417     return;
418     }
419    
420     free(pvar->hosts_state.prefetched_hostname);
421     pvar->hosts_state.prefetched_hostname = _strdup(hostname);
422    
423     finish_read_host_files(pvar, 1);
424     }
425    
426     static BOOL equal_mp_ints(unsigned char FAR * num1,
427     unsigned char FAR * num2)
428     {
429     if (num1 == NULL || num2 == NULL) {
430     return FALSE;
431     } else {
432     uint32 bytes = (get_ushort16_MSBfirst(num1) + 7) / 8;
433    
434     if (bytes != (get_ushort16_MSBfirst(num2) + 7) / 8) {
435     return FALSE; /* different byte lengths */
436     } else {
437     return memcmp(num1 + 2, num2 + 2, bytes) == 0;
438     }
439     }
440     }
441    
442     static BOOL match_key(PTInstVar pvar,
443     int bits, unsigned char FAR * exp,
444     unsigned char FAR * mod)
445     {
446     /* just check for equal exponent and modulus */
447     return equal_mp_ints(exp, pvar->hosts_state.key_exp)
448     && equal_mp_ints(mod, pvar->hosts_state.key_mod);
449     }
450    
451     static void init_hosts_dlg(PTInstVar pvar, HWND dlg)
452     {
453     char buf[1024];
454     char buf2[2048];
455     int i, j;
456     int ch;
457    
458     GetDlgItemText(dlg, IDC_HOSTWARNING, buf, sizeof(buf));
459     for (i = 0; (ch = buf[i]) != 0 && ch != '#'; i++) {
460     buf2[i] = ch;
461     }
462     if (sizeof(buf2) - i - 1 > 0) {
463     strncpy(buf2 + i, pvar->hosts_state.prefetched_hostname,
464     sizeof(buf2) - i - 1);
465     }
466     j = i + strlen(buf2 + i);
467     for (; buf[i] == '#'; i++) {
468     }
469     if (sizeof(buf2) - j - 1 > 0) {
470     strncpy(buf2 + j, buf + i, sizeof(buf2) - j - 1);
471     }
472     buf2[sizeof(buf2) - 1] = 0;
473    
474     SetDlgItemText(dlg, IDC_HOSTWARNING, buf2);
475     }
476    
477     static int print_mp_int(char FAR * buf, unsigned char FAR * mp)
478     {
479     int i = 0, j, k;
480     BIGNUM *num = BN_new();
481     int ch;
482    
483     BN_bin2bn(mp + 2, (get_ushort16_MSBfirst(mp) + 7) / 8, num);
484    
485     do {
486     buf[i] = (char) ((BN_div_word(num, 10)) + '0');
487     i++;
488     } while (!BN_is_zero(num));
489    
490     /* we need to reverse the digits */
491     for (j = 0, k = i - 1; j < k; j++, k--) {
492     ch = buf[j];
493     buf[j] = buf[k];
494     buf[k] = ch;
495     }
496    
497     buf[i] = 0;
498     return i;
499     }
500    
501     static char FAR *format_host_key(PTInstVar pvar)
502     {
503     int host_len = strlen(pvar->hosts_state.prefetched_hostname);
504     char FAR *result = (char FAR *) malloc(host_len
505     + 50 +
506     get_ushort16_MSBfirst(pvar->
507     hosts_state.
508     key_exp) /
509     3 +
510     get_ushort16_MSBfirst(pvar->
511     hosts_state.
512     key_mod) /
513     3);
514     int index;
515    
516     strcpy(result, pvar->hosts_state.prefetched_hostname);
517     index = host_len;
518    
519     sprintf(result + index, " %d ", pvar->hosts_state.key_bits);
520     index += strlen(result + index);
521     index += print_mp_int(result + index, pvar->hosts_state.key_exp);
522     result[index] = ' ';
523     index++;
524     index += print_mp_int(result + index, pvar->hosts_state.key_mod);
525     strcpy(result + index, " \r\n");
526    
527     return result;
528     }
529    
530     static void add_host_key(PTInstVar pvar)
531     {
532     char FAR *name = pvar->hosts_state.file_names[0];
533    
534     if (name == NULL || name[0] == 0) {
535     notify_nonfatal_error(pvar,
536     "The host and its key cannot be added, because no known-hosts file has been specified.\n"
537     "Restart Teraterm and specify a read/write known-hosts file in the TTSSH Setup dialog box.");
538     } else {
539     char FAR *keydata = format_host_key(pvar);
540     int length = strlen(keydata);
541     int fd =
542     _open(name,
543     _O_APPEND | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL |
544     _O_BINARY,
545     _S_IREAD | _S_IWRITE);
546     int amount_written;
547     int close_result;
548    
549     if (fd == -1) {
550     if (errno == EACCES) {
551     notify_nonfatal_error(pvar,
552     "An error occurred while trying to write the host key.\n"
553     "You do not have permission to write to the known-hosts file.");
554     } else {
555     notify_nonfatal_error(pvar,
556     "An error occurred while trying to write the host key.\n"
557     "The host key could not be written.");
558     }
559     return;
560     }
561    
562     amount_written = _write(fd, keydata, length);
563     free(keydata);
564     close_result = _close(fd);
565    
566     if (amount_written != length || close_result == -1) {
567     notify_nonfatal_error(pvar,
568     "An error occurred while trying to write the host key.\n"
569     "The host key could not be written.");
570     }
571     }
572     }
573    
574     static BOOL CALLBACK hosts_dlg_proc(HWND dlg, UINT msg, WPARAM wParam,
575     LPARAM lParam)
576     {
577     PTInstVar pvar;
578    
579     switch (msg) {
580     case WM_INITDIALOG:
581     pvar = (PTInstVar) lParam;
582     pvar->hosts_state.hosts_dialog = dlg;
583     SetWindowLong(dlg, DWL_USER, lParam);
584    
585     init_hosts_dlg(pvar, dlg);
586     return TRUE; /* because we do not set the focus */
587    
588     case WM_COMMAND:
589     pvar = (PTInstVar) GetWindowLong(dlg, DWL_USER);
590    
591     switch (LOWORD(wParam)) {
592     case IDC_CONTINUE:
593     if (IsDlgButtonChecked(dlg, IDC_ADDTOKNOWNHOSTS)) {
594     add_host_key(pvar);
595     }
596     SSH_notify_host_OK(pvar);
597    
598     pvar->hosts_state.hosts_dialog = NULL;
599    
600     EndDialog(dlg, 1);
601     return TRUE;
602    
603     case IDCANCEL: /* kill the connection */
604     pvar->hosts_state.hosts_dialog = NULL;
605     notify_closed_connection(pvar);
606     EndDialog(dlg, 0);
607     return TRUE;
608    
609     default:
610     return FALSE;
611     }
612    
613     default:
614     return FALSE;
615     }
616     }
617    
618     static char FAR *copy_mp_int(char FAR * num)
619     {
620     int len = (get_ushort16_MSBfirst(num) + 7) / 8 + 2;
621     char FAR *result = (char FAR *) malloc(len);
622    
623     if (result != NULL) {
624     memcpy(result, num, len);
625     }
626    
627     return result;
628     }
629    
630     void HOSTS_do_unknown_host_dialog(HWND wnd, PTInstVar pvar)
631     {
632     if (pvar->hosts_state.hosts_dialog == NULL) {
633     HWND cur_active = GetActiveWindow();
634    
635     DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SSHUNKNOWNHOST),
636     cur_active != NULL ? cur_active : wnd,
637     hosts_dlg_proc, (LPARAM) pvar);
638     }
639     }
640    
641     void HOSTS_do_different_host_dialog(HWND wnd, PTInstVar pvar)
642     {
643     if (pvar->hosts_state.hosts_dialog == NULL) {
644     HWND cur_active = GetActiveWindow();
645    
646     DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SSHDIFFERENTHOST),
647     cur_active != NULL ? cur_active : wnd,
648     hosts_dlg_proc, (LPARAM) pvar);
649     }
650     }
651    
652     BOOL HOSTS_check_host_key(PTInstVar pvar, char FAR * hostname,
653     int bits, unsigned char FAR * exp,
654     unsigned char FAR * mod)
655     {
656     int found_different_key = 0;
657    
658     if (pvar->hosts_state.prefetched_hostname != NULL
659 yutakakn 2850 && _stricmp(pvar->hosts_state.prefetched_hostname, hostname) == 0
660 yutakakn 2728 && match_key(pvar, bits, exp, mod)) {
661     SSH_notify_host_OK(pvar);
662     return TRUE;
663     }
664    
665     if (begin_read_host_files(pvar, 0)) {
666     do {
667     if (!read_host_key(pvar, hostname, 0)) {
668     break;
669     }
670    
671     if (pvar->hosts_state.key_bits > 0) {
672     if (match_key(pvar, bits, exp, mod)) {
673     finish_read_host_files(pvar, 0);
674     SSH_notify_host_OK(pvar);
675     return TRUE;
676     } else {
677     found_different_key = 1;
678     }
679     }
680     } while (pvar->hosts_state.key_bits > 0);
681    
682     finish_read_host_files(pvar, 0);
683     }
684    
685     pvar->hosts_state.key_bits = bits;
686     pvar->hosts_state.key_exp = copy_mp_int(exp);
687     pvar->hosts_state.key_mod = copy_mp_int(mod);
688     free(pvar->hosts_state.prefetched_hostname);
689     pvar->hosts_state.prefetched_hostname = _strdup(hostname);
690    
691     if (found_different_key) {
692     PostMessage(pvar->NotificationWindow, WM_COMMAND,
693     ID_SSHDIFFERENTHOST, 0);
694     } else {
695     PostMessage(pvar->NotificationWindow, WM_COMMAND,
696     ID_SSHUNKNOWNHOST, 0);
697     }
698    
699     return TRUE;
700     }
701    
702     void HOSTS_notify_disconnecting(PTInstVar pvar)
703     {
704     if (pvar->hosts_state.hosts_dialog != NULL) {
705     PostMessage(pvar->hosts_state.hosts_dialog, WM_COMMAND, IDCANCEL,
706     0);
707     /* the main window might not go away if it's not enabled. (see vtwin.cpp) */
708     EnableWindow(pvar->NotificationWindow, TRUE);
709     }
710     }
711    
712     void HOSTS_end(PTInstVar pvar)
713     {
714     int i;
715    
716     free(pvar->hosts_state.prefetched_hostname);
717     free(pvar->hosts_state.key_exp);
718     free(pvar->hosts_state.key_mod);
719    
720     if (pvar->hosts_state.file_names != NULL) {
721     for (i = 0; pvar->hosts_state.file_names[i] != NULL; i++) {
722     free(pvar->hosts_state.file_names[i]);
723     }
724     free(pvar->hosts_state.file_names);
725     }
726     }
727 yutakakn 2761
728     /*
729     * $Log: not supported by cvs2svn $
730 yutakakn 2850 * Revision 1.2 2004/12/19 15:39:42 yutakakn
731     * CVS LogID������
732     *
733 yutakakn 2761 */

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26