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 2861 - (hide annotations) (download) (as text)
Tue Apr 4 13:52:52 2006 UTC (18 years ago) by yutakakn
Original Path: ttssh2/trunk/ttxssh/hosts.c
File MIME type: text/x-csrc
File size: 33380 byte(s)
known_hostsファイルにおいてキー種別の異なる同一ホストのエントリがあった場合、古いキーを削除する機能を追加した。

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 yutakakn 2856 #include "ssh.h"
39     #include "hosts.h"
40 yutakakn 2728
41     #include <openssl/bn.h>
42 yutakakn 2856 #include <openssl/evp.h>
43     #include <openssl/rsa.h>
44     #include <openssl/dsa.h>
45 yutakakn 2728
46     #include <fcntl.h>
47     #include <io.h>
48     #include <errno.h>
49     #include <sys/stat.h>
50    
51 yutakakn 2856
52     // BASE64�\���������i��������'='�����������������j
53     static char base64[] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
54    
55    
56     // �z�X�g�L�[�������� (2006.3.21 yutaka)
57     static void init_hostkey(Key *key)
58     {
59     key->type = KEY_UNSPEC;
60    
61     // SSH1
62     key->bits = 0;
63     if (key->exp != NULL) {
64     free(key->exp);
65     key->exp = NULL;
66     }
67     if (key->mod != NULL) {
68     free(key->mod);
69     key->mod = NULL;
70     }
71    
72     // SSH2
73     if (key->dsa != NULL) {
74     DSA_free(key->dsa);
75     key->dsa = NULL;
76     }
77     if (key->rsa != NULL) {
78     RSA_free(key->rsa);
79     key->rsa = NULL;
80     }
81     }
82    
83    
84 yutakakn 2728 static char FAR *FAR * parse_multi_path(char FAR * buf)
85     {
86     int i;
87     int ch;
88     int num_paths = 1;
89     char FAR *FAR * result;
90     int last_path_index;
91    
92     for (i = 0; (ch = buf[i]) != 0; i++) {
93     if (ch == ';') {
94     num_paths++;
95     }
96     }
97    
98     result =
99     (char FAR * FAR *) malloc(sizeof(char FAR *) * (num_paths + 1));
100    
101     last_path_index = 0;
102     num_paths = 0;
103     for (i = 0; (ch = buf[i]) != 0; i++) {
104     if (ch == ';') {
105     buf[i] = 0;
106     result[num_paths] = _strdup(buf + last_path_index);
107     num_paths++;
108     buf[i] = ch;
109     last_path_index = i + 1;
110     }
111     }
112     if (i > last_path_index) {
113     result[num_paths] = _strdup(buf + last_path_index);
114     num_paths++;
115     }
116     result[num_paths] = NULL;
117     return result;
118     }
119    
120     void HOSTS_init(PTInstVar pvar)
121     {
122     pvar->hosts_state.prefetched_hostname = NULL;
123 yutakakn 2856 #if 0
124 yutakakn 2728 pvar->hosts_state.key_exp = NULL;
125     pvar->hosts_state.key_mod = NULL;
126 yutakakn 2856 #else
127     init_hostkey(&pvar->hosts_state.hostkey);
128     #endif
129 yutakakn 2728 pvar->hosts_state.hosts_dialog = NULL;
130     pvar->hosts_state.file_names = NULL;
131     }
132    
133     void HOSTS_open(PTInstVar pvar)
134     {
135     pvar->hosts_state.file_names =
136     parse_multi_path(pvar->session_settings.KnownHostsFiles);
137     }
138    
139 yutakakn 2856 //
140     // known_hosts�t�@�C�������e�������� pvar->hosts_state.file_data ����������
141     //
142 yutakakn 2728 static int begin_read_file(PTInstVar pvar, char FAR * name,
143     int suppress_errors)
144     {
145     int fd;
146     int length;
147     int amount_read;
148     char buf[2048];
149    
150     get_teraterm_dir_relative_name(buf, sizeof(buf), name);
151     fd = _open(buf, _O_RDONLY | _O_SEQUENTIAL | _O_BINARY);
152     if (fd == -1) {
153     if (!suppress_errors) {
154     if (errno == ENOENT) {
155     notify_nonfatal_error(pvar,
156     "An error occurred while trying to read a known_hosts file.\n"
157     "The specified filename does not exist.");
158     } else {
159     notify_nonfatal_error(pvar,
160     "An error occurred while trying to read a known_hosts file.");
161     }
162     }
163     return 0;
164     }
165    
166     length = (int) _lseek(fd, 0, SEEK_END);
167     _lseek(fd, 0, SEEK_SET);
168    
169     if (length >= 0 && length < 0x7FFFFFFF) {
170     pvar->hosts_state.file_data = malloc(length + 1);
171     if (pvar->hosts_state.file_data == NULL) {
172     if (!suppress_errors) {
173     notify_nonfatal_error(pvar,
174     "Memory ran out while trying to allocate space to read a known_hosts file.");
175     }
176     _close(fd);
177     return 0;
178     }
179     } else {
180     if (!suppress_errors) {
181     notify_nonfatal_error(pvar,
182     "An error occurred while trying to read a known_hosts file.");
183     }
184     _close(fd);
185     return 0;
186     }
187    
188     amount_read = _read(fd, pvar->hosts_state.file_data, length);
189     pvar->hosts_state.file_data[length] = 0;
190    
191     _close(fd);
192    
193     if (amount_read != length) {
194     if (!suppress_errors) {
195     notify_nonfatal_error(pvar,
196     "An error occurred while trying to read a known_hosts file.");
197     }
198     free(pvar->hosts_state.file_data);
199     pvar->hosts_state.file_data = NULL;
200     return 0;
201     } else {
202     return 1;
203     }
204     }
205    
206     static int end_read_file(PTInstVar pvar, int suppress_errors)
207     {
208     free(pvar->hosts_state.file_data);
209     pvar->hosts_state.file_data = NULL;
210     return 1;
211     }
212    
213     static int begin_read_host_files(PTInstVar pvar, int suppress_errors)
214     {
215     pvar->hosts_state.file_num = 0;
216     pvar->hosts_state.file_data = NULL;
217     return 1;
218     }
219    
220 yutakakn 2856 // MIME64�����������X�L�b�v����
221     static int eat_base64(char FAR * data)
222     {
223     int index = 0;
224     int ch;
225    
226     for (;;) {
227     ch = data[index];
228     if (ch == '=' || strchr(base64, ch)) {
229     // BASE64���\�������������������� index ���i����
230     index++;
231     } else {
232     break;
233     }
234     }
235    
236     return index;
237     }
238    
239 yutakakn 2728 static int eat_spaces(char FAR * data)
240     {
241     int index = 0;
242     int ch;
243    
244     while ((ch = data[index]) == ' ' || ch == '\t') {
245     index++;
246     }
247     return index;
248     }
249    
250     static int eat_digits(char FAR * data)
251     {
252     int index = 0;
253     int ch;
254    
255     while ((ch = data[index]) >= '0' && ch <= '9') {
256     index++;
257     }
258     return index;
259     }
260    
261     static int eat_to_end_of_line(char FAR * data)
262     {
263     int index = 0;
264     int ch;
265    
266     while ((ch = data[index]) != '\n' && ch != '\r' && ch != 0) {
267     index++;
268     }
269    
270     while ((ch = data[index]) == '\n' || ch == '\r') {
271     index++;
272     }
273    
274     return index;
275     }
276    
277     static int eat_to_end_of_pattern(char FAR * data)
278     {
279     int index = 0;
280     int ch;
281    
282     while (ch = data[index], is_pattern_char(ch)) {
283     index++;
284     }
285    
286     return index;
287     }
288    
289 yutakakn 2856 //
290     // BASE64�f�R�[�h�������s���B(rfc1521)
291     // src�o�b�t�@�� null-terminate ���������K�v�����B
292     //
293     static int uudecode(unsigned char *src, int srclen, unsigned char *target, int targsize)
294     {
295     char pad = '=';
296     int tarindex, state, ch;
297     char *pos;
298    
299     state = 0;
300     tarindex = 0;
301    
302     while ((ch = *src++) != '\0') {
303     if (isspace(ch)) /* Skip whitespace anywhere. */
304     continue;
305    
306     if (ch == pad)
307     break;
308    
309     pos = strchr(base64, ch);
310     if (pos == 0) /* A non-base64 character. */
311     return (-1);
312    
313     switch (state) {
314     case 0:
315     if (target) {
316     if (tarindex >= targsize)
317     return (-1);
318     target[tarindex] = (pos - base64) << 2;
319     }
320     state = 1;
321     break;
322     case 1:
323     if (target) {
324     if (tarindex + 1 >= targsize)
325     return (-1);
326     target[tarindex] |= (pos - base64) >> 4;
327     target[tarindex+1] = ((pos - base64) & 0x0f) << 4 ;
328     }
329     tarindex++;
330     state = 2;
331     break;
332     case 2:
333     if (target) {
334     if (tarindex + 1 >= targsize)
335     return (-1);
336     target[tarindex] |= (pos - base64) >> 2;
337     target[tarindex+1] = ((pos - base64) & 0x03) << 6;
338     }
339     tarindex++;
340     state = 3;
341     break;
342     case 3:
343     if (target) {
344     if (tarindex >= targsize)
345     return (-1);
346     target[tarindex] |= (pos - base64);
347     }
348     tarindex++;
349     state = 0;
350     break;
351     }
352     }
353    
354     /*
355     * We are done decoding Base-64 chars. Let's see if we ended
356     * on a byte boundary, and/or with erroneous trailing characters.
357     */
358    
359     if (ch == pad) { /* We got a pad char. */
360     ch = *src++; /* Skip it, get next. */
361     switch (state) {
362     case 0: /* Invalid = in first position */
363     case 1: /* Invalid = in second position */
364     return (-1);
365    
366     case 2: /* Valid, means one byte of info */
367     /* Skip any number of spaces. */
368     for (; ch != '\0'; ch = *src++)
369     if (!isspace(ch))
370     break;
371     /* Make sure there is another trailing = sign. */
372     if (ch != pad)
373     return (-1);
374     ch = *src++; /* Skip the = */
375     /* Fall through to "single trailing =" case. */
376     /* FALLTHROUGH */
377    
378     case 3: /* Valid, means two bytes of info */
379     /*
380     * We know this char is an =. Is there anything but
381     * whitespace after it?
382     */
383     for (; ch != '\0'; ch = *src++)
384     if (!isspace(ch))
385     return (-1);
386    
387     /*
388     * Now make sure for cases 2 and 3 that the "extra"
389     * bits that slopped past the last full byte were
390     * zeros. If we don't check them, they become a
391     * subliminal channel.
392     */
393     if (target && target[tarindex] != 0)
394     return (-1);
395     }
396     } else {
397     /*
398     * We ended by seeing the end of the string. Make sure we
399     * have no partial bytes lying around.
400     */
401     if (state != 0)
402     return (-1);
403     }
404    
405     return (tarindex);
406     }
407    
408    
409     // SSH2���� BASE64 �`�����i�[����������
410     static Key *parse_uudecode(char *data)
411     {
412     int count;
413     unsigned char *blob = NULL;
414     int len, n;
415     Key *key = NULL;
416     char ch;
417    
418     // BASE64���������T�C�Y������
419     count = eat_base64(data);
420     len = 2 * count;
421     blob = malloc(len);
422     if (blob == NULL)
423     goto error;
424    
425     // BASE64�f�R�[�h
426     ch = data[count];
427     data[count] = '\0'; // ���������s�R�[�h������������������������������������
428     n = uudecode(data, count, blob, len);
429     data[count] = ch;
430     if (n < 0) {
431     goto error;
432     }
433    
434     key = key_from_blob(blob, n);
435     if (key == NULL)
436     goto error;
437    
438     error:
439     if (blob != NULL)
440     free(blob);
441    
442     return (key);
443     }
444    
445    
446 yutakakn 2728 static char FAR *parse_bignum(char FAR * data)
447     {
448     uint32 digits = 0;
449     BIGNUM *num = BN_new();
450     BIGNUM *billion = BN_new();
451     BIGNUM *digits_num = BN_new();
452     BN_CTX *ctx = BN_CTX_new();
453     char FAR *result;
454     int ch;
455     int leftover_digits = 1;
456    
457     BN_CTX_init(ctx);
458     BN_set_word(num, 0);
459     BN_set_word(billion, 1000000000L);
460    
461     while ((ch = *data) >= '0' && ch <= '9') {
462     if (leftover_digits == 1000000000L) {
463     BN_set_word(digits_num, digits);
464     BN_mul(num, num, billion, ctx);
465     BN_add(num, num, digits_num);
466     leftover_digits = 1;
467     digits = 0;
468     }
469    
470     digits = digits * 10 + ch - '0';
471     leftover_digits *= 10;
472     data++;
473     }
474    
475     BN_set_word(digits_num, digits);
476     BN_set_word(billion, leftover_digits);
477     BN_mul(num, num, billion, ctx);
478     BN_add(num, num, digits_num);
479    
480     result = (char FAR *) malloc(2 + BN_num_bytes(num));
481     set_ushort16_MSBfirst(result, BN_num_bits(num));
482     BN_bn2bin(num, result + 2);
483    
484     BN_CTX_free(ctx);
485     BN_free(digits_num);
486     BN_free(num);
487     BN_free(billion);
488    
489     return result;
490     }
491    
492 yutakakn 2856 //
493     // known_hosts�t�@�C�������e���������A�w�������z�X�g�����J�����T���B
494     //
495 yutakakn 2728 static int check_host_key(PTInstVar pvar, char FAR * hostname,
496     char FAR * data)
497     {
498     int index = eat_spaces(data);
499     int matched = 0;
500     int keybits = 0;
501    
502     if (data[index] == '#') {
503     return index + eat_to_end_of_line(data + index);
504     }
505    
506     /* if we find an empty line, then it won't have any patterns matching the hostname
507     and so we skip it */
508     index--;
509     do {
510     int negated;
511    
512     index++;
513     negated = data[index] == '!';
514    
515     if (negated) {
516     index++;
517     if (match_pattern(data + index, hostname)) {
518     return index + eat_to_end_of_line(data + index);
519     }
520     } else if (match_pattern(data + index, hostname)) {
521     matched = 1;
522     }
523    
524     index += eat_to_end_of_pattern(data + index);
525     } while (data[index] == ',');
526    
527     if (!matched) {
528     return index + eat_to_end_of_line(data + index);
529     } else {
530 yutakakn 2856 // ���������������t�H�[�}�b�g��������
531     // �����A���������v�����G���g�����������������������B
532     /*
533     [SSH1]
534     192.168.1.2 1024 35 13032....
535 yutakakn 2728
536 yutakakn 2856 [SSH2]
537     192.168.1.2 ssh-rsa AAAAB3NzaC1....
538     192.168.1.2 ssh-dss AAAAB3NzaC1....
539     192.168.1.2 rsa AAAAB3NzaC1....
540     192.168.1.2 dsa AAAAB3NzaC1....
541     192.168.1.2 rsa1 AAAAB3NzaC1....
542     */
543     int rsa1_key_bits;
544 yutakakn 2728
545     index += eat_spaces(data + index);
546    
547 yutakakn 2856 rsa1_key_bits = atoi(data + index);
548     if (rsa1_key_bits > 0) { // RSA1������
549     if (!SSHv1(pvar)) { // SSH2��������������������
550     return index + eat_to_end_of_line(data + index);
551     }
552 yutakakn 2728
553 yutakakn 2856 pvar->hosts_state.hostkey.type = KEY_RSA1;
554    
555     pvar->hosts_state.hostkey.bits = rsa1_key_bits;
556     index += eat_digits(data + index);
557     index += eat_spaces(data + index);
558    
559     pvar->hosts_state.hostkey.exp = parse_bignum(data + index);
560     index += eat_digits(data + index);
561     index += eat_spaces(data + index);
562    
563     pvar->hosts_state.hostkey.mod = parse_bignum(data + index);
564    
565     /*
566     if (pvar->hosts_state.key_bits < 0
567     || pvar->hosts_state.key_exp == NULL
568     || pvar->hosts_state.key_mod == NULL) {
569     pvar->hosts_state.key_bits = 0;
570     free(pvar->hosts_state.key_exp);
571     free(pvar->hosts_state.key_mod);
572     }*/
573    
574     } else {
575     char *cp, *p;
576     Key *key;
577    
578     if (!SSHv2(pvar)) { // SSH1��������������������
579     return index + eat_to_end_of_line(data + index);
580     }
581    
582     cp = data + index;
583     p = strchr(cp, ' ');
584     if (p == NULL) {
585     return index + eat_to_end_of_line(data + index);
586     }
587     index += (p - cp); // setup index
588     *p = '\0';
589     pvar->hosts_state.hostkey.type = get_keytype_from_name(cp);
590     *p = ' ';
591    
592     index += eat_spaces(data + index); // update index
593    
594     // uudecode
595     key = parse_uudecode(data + index);
596     if (key == NULL) {
597     return index + eat_to_end_of_line(data + index);
598     }
599    
600     // setup
601     pvar->hosts_state.hostkey.type = key->type;
602     pvar->hosts_state.hostkey.dsa = key->dsa;
603     pvar->hosts_state.hostkey.rsa = key->rsa;
604    
605     index += eat_base64(data + index);
606     index += eat_spaces(data + index);
607 yutakakn 2728 }
608    
609     return index + eat_to_end_of_line(data + index);
610     }
611     }
612    
613 yutakakn 2856 //
614     // known_hosts�t�@�C�������z�X�g�������v�����s������
615     //
616 yutakakn 2728 static int read_host_key(PTInstVar pvar, char FAR * hostname,
617 yutakakn 2861 int suppress_errors, int return_always)
618 yutakakn 2728 {
619     int i;
620 yutakakn 2861 int while_flg;
621 yutakakn 2728
622     for (i = 0; hostname[i] != 0; i++) {
623     int ch = hostname[i];
624    
625     if (!is_pattern_char(ch) || ch == '*' || ch == '?') {
626     if (!suppress_errors) {
627     notify_fatal_error(pvar,
628     "The host name contains an invalid character.\n"
629     "This session will be terminated.");
630     }
631     return 0;
632     }
633     }
634    
635     if (i == 0) {
636     if (!suppress_errors) {
637     notify_fatal_error(pvar, "The host name should not be empty.\n"
638     "This session will be terminated.");
639     }
640     return 0;
641     }
642    
643 yutakakn 2856 #if 0
644 yutakakn 2728 pvar->hosts_state.key_bits = 0;
645     free(pvar->hosts_state.key_exp);
646     pvar->hosts_state.key_exp = NULL;
647     free(pvar->hosts_state.key_mod);
648     pvar->hosts_state.key_mod = NULL;
649 yutakakn 2856 #else
650     // hostkey type is KEY_UNSPEC.
651     init_hostkey(&pvar->hosts_state.hostkey);
652     #endif
653 yutakakn 2728
654     do {
655     if (pvar->hosts_state.file_data == NULL
656     || pvar->hosts_state.file_data[pvar->hosts_state.
657     file_data_index] == 0) {
658     char FAR *filename;
659     int keep_going = 1;
660    
661     if (pvar->hosts_state.file_data != NULL) {
662     end_read_file(pvar, suppress_errors);
663     }
664    
665     do {
666     filename =
667     pvar->hosts_state.file_names[pvar->hosts_state.
668     file_num];
669    
670     if (filename == NULL) {
671     return 1;
672     } else {
673     pvar->hosts_state.file_num++;
674    
675     if (filename[0] != 0) {
676     if (begin_read_file
677     (pvar, filename, suppress_errors)) {
678     pvar->hosts_state.file_data_index = 0;
679     keep_going = 0;
680     }
681     }
682     }
683     } while (keep_going);
684     }
685    
686     pvar->hosts_state.file_data_index +=
687     check_host_key(pvar, hostname,
688     pvar->hosts_state.file_data +
689     pvar->hosts_state.file_data_index);
690    
691 yutakakn 2861 if (!return_always) {
692     // �L�����L�[��������������
693     while_flg = (pvar->hosts_state.hostkey.type == KEY_UNSPEC);
694     }
695     else {
696     while_flg = 0;
697     }
698     } while (while_flg);
699    
700 yutakakn 2728 return 1;
701     }
702    
703     static void finish_read_host_files(PTInstVar pvar, int suppress_errors)
704     {
705     if (pvar->hosts_state.file_data != NULL) {
706     end_read_file(pvar, suppress_errors);
707     }
708     }
709    
710 yutakakn 2856 // �T�[�o�����������O���Aknown_hosts�t�@�C�������z�X�g���J�������������������B
711 yutakakn 2728 void HOSTS_prefetch_host_key(PTInstVar pvar, char FAR * hostname)
712     {
713     if (!begin_read_host_files(pvar, 1)) {
714     return;
715     }
716    
717 yutakakn 2861 if (!read_host_key(pvar, hostname, 1, 0)) {
718 yutakakn 2728 return;
719     }
720    
721     free(pvar->hosts_state.prefetched_hostname);
722     pvar->hosts_state.prefetched_hostname = _strdup(hostname);
723    
724     finish_read_host_files(pvar, 1);
725     }
726    
727     static BOOL equal_mp_ints(unsigned char FAR * num1,
728     unsigned char FAR * num2)
729     {
730     if (num1 == NULL || num2 == NULL) {
731     return FALSE;
732     } else {
733     uint32 bytes = (get_ushort16_MSBfirst(num1) + 7) / 8;
734    
735     if (bytes != (get_ushort16_MSBfirst(num2) + 7) / 8) {
736     return FALSE; /* different byte lengths */
737     } else {
738     return memcmp(num1 + 2, num2 + 2, bytes) == 0;
739     }
740     }
741     }
742    
743 yutakakn 2856 // ���J����������������������
744     static BOOL match_key(PTInstVar pvar, Key *key)
745 yutakakn 2728 {
746 yutakakn 2856 int bits;
747     unsigned char FAR * exp;
748     unsigned char FAR * mod;
749    
750     if (key->type == KEY_RSA1) { // SSH1 host public key
751     bits = key->bits;
752     exp = key->exp;
753     mod = key->mod;
754    
755     /* just check for equal exponent and modulus */
756     return equal_mp_ints(exp, pvar->hosts_state.hostkey.exp)
757     && equal_mp_ints(mod, pvar->hosts_state.hostkey.mod);
758     /*
759     return equal_mp_ints(exp, pvar->hosts_state.key_exp)
760     && equal_mp_ints(mod, pvar->hosts_state.key_mod);
761     */
762    
763     } else if (key->type == KEY_RSA) { // SSH2 RSA host public key
764    
765     return key->rsa != NULL && pvar->hosts_state.hostkey.rsa != NULL &&
766     BN_cmp(key->rsa->e, pvar->hosts_state.hostkey.rsa->e) == 0 &&
767     BN_cmp(key->rsa->n, pvar->hosts_state.hostkey.rsa->n) == 0;
768    
769     } else { // // SSH2 DSA host public key
770    
771     return key->dsa != NULL && pvar->hosts_state.hostkey.dsa &&
772     BN_cmp(key->dsa->p, pvar->hosts_state.hostkey.dsa->p) == 0 &&
773     BN_cmp(key->dsa->q, pvar->hosts_state.hostkey.dsa->q) == 0 &&
774     BN_cmp(key->dsa->g, pvar->hosts_state.hostkey.dsa->g) == 0 &&
775     BN_cmp(key->dsa->pub_key, pvar->hosts_state.hostkey.dsa->pub_key) == 0;
776    
777     }
778    
779 yutakakn 2728 }
780    
781     static void init_hosts_dlg(PTInstVar pvar, HWND dlg)
782     {
783     char buf[1024];
784     char buf2[2048];
785     int i, j;
786     int ch;
787 yutakakn 2857 char *fp;
788 yutakakn 2728
789 yutakakn 2857 // static text�� # �������z�X�g�����u������
790 yutakakn 2728 GetDlgItemText(dlg, IDC_HOSTWARNING, buf, sizeof(buf));
791     for (i = 0; (ch = buf[i]) != 0 && ch != '#'; i++) {
792     buf2[i] = ch;
793     }
794     if (sizeof(buf2) - i - 1 > 0) {
795     strncpy(buf2 + i, pvar->hosts_state.prefetched_hostname,
796     sizeof(buf2) - i - 1);
797     }
798     j = i + strlen(buf2 + i);
799     for (; buf[i] == '#'; i++) {
800     }
801     if (sizeof(buf2) - j - 1 > 0) {
802     strncpy(buf2 + j, buf + i, sizeof(buf2) - j - 1);
803     }
804     buf2[sizeof(buf2) - 1] = 0;
805    
806     SetDlgItemText(dlg, IDC_HOSTWARNING, buf2);
807 yutakakn 2857
808     // fingerprint����������
809     fp = key_fingerprint(&pvar->hosts_state.hostkey);
810     SendMessage(GetDlgItem(dlg, IDC_FINGER_PRINT), WM_SETTEXT, 0, (LPARAM)fp);
811 yutakakn 2728 }
812    
813     static int print_mp_int(char FAR * buf, unsigned char FAR * mp)
814     {
815     int i = 0, j, k;
816     BIGNUM *num = BN_new();
817     int ch;
818    
819     BN_bin2bn(mp + 2, (get_ushort16_MSBfirst(mp) + 7) / 8, num);
820    
821     do {
822     buf[i] = (char) ((BN_div_word(num, 10)) + '0');
823     i++;
824     } while (!BN_is_zero(num));
825    
826     /* we need to reverse the digits */
827     for (j = 0, k = i - 1; j < k; j++, k--) {
828     ch = buf[j];
829     buf[j] = buf[k];
830     buf[k] = ch;
831     }
832    
833     buf[i] = 0;
834     return i;
835     }
836    
837 yutakakn 2856 //
838     // known_hosts �t�@�C�������������G���g�������������B
839     //
840 yutakakn 2728 static char FAR *format_host_key(PTInstVar pvar)
841     {
842     int host_len = strlen(pvar->hosts_state.prefetched_hostname);
843 yutakakn 2856 char *result = NULL;
844     int index;
845     enum hostkey_type type = pvar->hosts_state.hostkey.type;
846    
847     if (type == KEY_RSA1) {
848     result = (char FAR *) malloc(host_len
849 yutakakn 2728 + 50 +
850 yutakakn 2856 get_ushort16_MSBfirst(pvar->hosts_state.hostkey.exp) /
851 yutakakn 2728 3 +
852 yutakakn 2856 get_ushort16_MSBfirst(pvar->hosts_state.hostkey.mod) /
853 yutakakn 2728 3);
854    
855 yutakakn 2856 strcpy(result, pvar->hosts_state.prefetched_hostname);
856     index = host_len;
857 yutakakn 2728
858 yutakakn 2856 sprintf(result + index, " %d ", pvar->hosts_state.hostkey.bits);
859     index += strlen(result + index);
860     index += print_mp_int(result + index, pvar->hosts_state.hostkey.exp);
861     result[index] = ' ';
862     index++;
863     index += print_mp_int(result + index, pvar->hosts_state.hostkey.mod);
864     strcpy(result + index, " \r\n");
865 yutakakn 2728
866 yutakakn 2856 } else if (type == KEY_RSA || type == KEY_DSA) {
867     Key *key = &pvar->hosts_state.hostkey;
868     char *blob = NULL;
869     int blen, uulen, msize;
870     char *uu = NULL;
871     int n;
872    
873     key_to_blob(key, &blob, &blen);
874     uulen = 2 * blen;
875     uu = malloc(uulen);
876     if (uu == NULL) {
877     goto error;
878     }
879     n = uuencode(blob, blen, uu, uulen);
880     if (n > 0) {
881     msize = host_len + 50 + uulen;
882     result = malloc(msize);
883     if (result == NULL) {
884     goto error;
885     }
886    
887     // setup
888     _snprintf(result, msize, "%s %s %s\r\n",
889     pvar->hosts_state.prefetched_hostname,
890     get_sshname_from_key(key),
891     uu);
892     }
893     error:
894     if (blob != NULL)
895     free(blob);
896     if (uu != NULL)
897     free(uu);
898    
899     } else {
900     return NULL;
901    
902     }
903    
904 yutakakn 2728 return result;
905     }
906    
907     static void add_host_key(PTInstVar pvar)
908     {
909     char FAR *name = pvar->hosts_state.file_names[0];
910    
911     if (name == NULL || name[0] == 0) {
912     notify_nonfatal_error(pvar,
913     "The host and its key cannot be added, because no known-hosts file has been specified.\n"
914     "Restart Teraterm and specify a read/write known-hosts file in the TTSSH Setup dialog box.");
915     } else {
916     char FAR *keydata = format_host_key(pvar);
917     int length = strlen(keydata);
918     int fd =
919     _open(name,
920     _O_APPEND | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL |
921     _O_BINARY,
922     _S_IREAD | _S_IWRITE);
923     int amount_written;
924     int close_result;
925    
926     if (fd == -1) {
927     if (errno == EACCES) {
928     notify_nonfatal_error(pvar,
929     "An error occurred while trying to write the host key.\n"
930     "You do not have permission to write to the known-hosts file.");
931     } else {
932     notify_nonfatal_error(pvar,
933     "An error occurred while trying to write the host key.\n"
934     "The host key could not be written.");
935     }
936     return;
937     }
938    
939     amount_written = _write(fd, keydata, length);
940     free(keydata);
941     close_result = _close(fd);
942    
943     if (amount_written != length || close_result == -1) {
944     notify_nonfatal_error(pvar,
945     "An error occurred while trying to write the host key.\n"
946     "The host key could not be written.");
947     }
948     }
949     }
950    
951 yutakakn 2861 static char FAR *copy_mp_int(char FAR * num)
952     {
953     int len = (get_ushort16_MSBfirst(num) + 7) / 8 + 2;
954     char FAR *result = (char FAR *) malloc(len);
955    
956     if (result != NULL) {
957     memcpy(result, num, len);
958     }
959    
960     return result;
961     }
962    
963 yutakakn 2856 //
964 yutakakn 2861 // �����z�X�g�����e���������L�[����������
965     // add_host_key ����������������
966     //
967     static void delete_different_key(PTInstVar pvar)
968     {
969     char FAR *name = pvar->hosts_state.file_names[0];
970    
971     if (name == NULL || name[0] == 0) {
972     notify_nonfatal_error(pvar,
973     "The host and its key cannot be added, because no known-hosts file has been specified.\n"
974     "Restart Teraterm and specify a read/write known-hosts file in the TTSSH Setup dialog box.");
975     }
976     else {
977     Key key; // ���������z�X�g���L�[
978     int length = strlen(name);
979     char filename[L_tmpnam];
980     int fd;
981     int amount_written = 0;
982     int close_result;
983     int data_index = 0;
984    
985     // �������������t�@�C�����J��
986     tmpnam(filename);
987     fd =
988     _open(filename,
989     _O_CREAT | _O_WRONLY | _O_SEQUENTIAL | _O_BINARY |
990     _O_TRUNC,
991     _S_IREAD | _S_IWRITE);
992    
993     if (fd == -1) {
994     if (errno == EACCES) {
995     notify_nonfatal_error(pvar,
996     "An error occurred while trying to write the host key.\n"
997     "You do not have permission to write to the known-hosts file.");
998     } else {
999     notify_nonfatal_error(pvar,
1000     "An error occurred while trying to write the host key.\n"
1001     "The host key could not be written.");
1002     }
1003     free(filename);
1004     return;
1005     }
1006    
1007     // ���������T�[�o���L�[����������
1008     if (pvar->hosts_state.hostkey.type == KEY_RSA1) { // SSH1
1009     key.type = KEY_RSA1;
1010     key.bits = pvar->hosts_state.hostkey.bits;
1011     key.exp = copy_mp_int(pvar->hosts_state.hostkey.exp);
1012     key.mod = copy_mp_int(pvar->hosts_state.hostkey.mod);
1013     } else if (pvar->hosts_state.hostkey.type == KEY_RSA) { // SSH2 RSA
1014     key.type = KEY_RSA;
1015     key.rsa = duplicate_RSA(pvar->hosts_state.hostkey.rsa);
1016     } else { // SSH2 DSA
1017     key.type = KEY_DSA;
1018     key.dsa = duplicate_DSA(pvar->hosts_state.hostkey.dsa);
1019     }
1020    
1021     // �t�@�C��������������
1022     begin_read_host_files(pvar, 0);
1023     do {
1024     int host_index = 0;
1025     int matched = 0;
1026     int keybits = 0;
1027     char FAR *data;
1028     int do_write = 0;
1029     length = amount_written = 0;
1030    
1031     if (!read_host_key(pvar, pvar->ssh_state.hostname, 0, 1)) {
1032     break;
1033     }
1034    
1035     if (data_index == pvar->hosts_state.file_data_index) {
1036     // index ���i������ == ��������������
1037     break;
1038     }
1039    
1040     data = pvar->hosts_state.file_data + data_index;
1041     host_index = eat_spaces(data);
1042    
1043     if (data[host_index] == '#') {
1044     do_write = 1;
1045     }
1046     else {
1047     // �z�X�g������
1048     host_index--;
1049     do {
1050     int negated;
1051    
1052     host_index++;
1053     negated = data[host_index] == '!';
1054    
1055     if (negated) {
1056     host_index++;
1057     if (match_pattern(data + host_index,
1058     pvar->ssh_state.hostname)) {
1059     matched = 0;
1060     // �����o�[�W�����`�F�b�N�������� host_index ���i��������������
1061     host_index--;
1062     do {
1063     host_index++;
1064     host_index += eat_to_end_of_pattern(data + host_index);
1065     } while (data[host_index] == ',');
1066     break;
1067     }
1068     }
1069     else if (match_pattern(data + host_index,
1070     pvar->ssh_state.hostname)) {
1071     matched = 1;
1072     }
1073     host_index += eat_to_end_of_pattern(data + host_index);
1074     } while (data[host_index] == ',');
1075    
1076     // �z�X�g�������������v�����L�[����������
1077     if (match_key(pvar, &key)) {
1078     do_write = 1;
1079     }
1080     // �z�X�g������������
1081     else if (!matched) {
1082     do_write = 1;
1083     }
1084     // �z�X�g�������� and �������o�[�W����������
1085     else {
1086     int rsa1_key_bits=0;
1087     rsa1_key_bits = atoi(data + host_index + eat_spaces(data + host_index));
1088    
1089     if (rsa1_key_bits > 0) { // �t�@�C�����L�[�� ssh1
1090     if (!SSHv1(pvar)) {
1091     do_write = 1;
1092     }
1093     }
1094     else { // �t�@�C�����L�[�� ssh2
1095     if (!SSHv2(pvar)) {
1096     do_write = 1;
1097     }
1098     }
1099     }
1100     }
1101    
1102     // ������������
1103     if (do_write) {
1104     length = pvar->hosts_state.file_data_index - data_index;
1105     amount_written =
1106     _write(fd, pvar->hosts_state.file_data + data_index,
1107     length);
1108    
1109     if (amount_written != length) {
1110     goto error1;
1111     }
1112     }
1113     data_index = pvar->hosts_state.file_data_index;
1114     } while (1); // ������������
1115    
1116     error1:
1117     close_result = _close(fd);
1118     if (amount_written != length || close_result == -1) {
1119     notify_nonfatal_error(pvar,
1120     "An error occurred while trying to write the host key.\n"
1121     "The host key could not be written.");
1122     goto error2;
1123     }
1124    
1125     // �������������t�@�C���������l�[��
1126     _unlink(pvar->hosts_state.file_names[0]);
1127     rename(filename, pvar->hosts_state.file_names[0]);
1128    
1129     error2:
1130     _unlink(filename);
1131    
1132     finish_read_host_files(pvar, 0);
1133     }
1134     }
1135    
1136     //
1137 yutakakn 2856 // Unknown host���z�X�g���J���� known_hosts �t�@�C����������������������
1138     // ���[�U���m�F�������B
1139     // TODO: finger print���\�����s���B
1140     // (2006.3.25 yutaka)
1141     //
1142 yutakakn 2861 static BOOL CALLBACK hosts_add_dlg_proc(HWND dlg, UINT msg, WPARAM wParam,
1143     LPARAM lParam)
1144 yutakakn 2728 {
1145     PTInstVar pvar;
1146    
1147     switch (msg) {
1148     case WM_INITDIALOG:
1149     pvar = (PTInstVar) lParam;
1150     pvar->hosts_state.hosts_dialog = dlg;
1151     SetWindowLong(dlg, DWL_USER, lParam);
1152    
1153     init_hosts_dlg(pvar, dlg);
1154 yutakakn 2856
1155     // add host check box���`�F�b�N���f�t�H���g������������
1156     SendMessage(GetDlgItem(dlg, IDC_ADDTOKNOWNHOSTS), BM_SETCHECK, BST_CHECKED, 0);
1157    
1158 yutakakn 2728 return TRUE; /* because we do not set the focus */
1159    
1160     case WM_COMMAND:
1161     pvar = (PTInstVar) GetWindowLong(dlg, DWL_USER);
1162    
1163     switch (LOWORD(wParam)) {
1164     case IDC_CONTINUE:
1165     if (IsDlgButtonChecked(dlg, IDC_ADDTOKNOWNHOSTS)) {
1166     add_host_key(pvar);
1167     }
1168    
1169 yutakakn 2856 if (SSHv1(pvar)) {
1170     SSH_notify_host_OK(pvar);
1171     } else { // SSH2
1172     // SSH2���������� SSH_notify_host_OK() �������B
1173     }
1174    
1175 yutakakn 2728 pvar->hosts_state.hosts_dialog = NULL;
1176    
1177     EndDialog(dlg, 1);
1178     return TRUE;
1179    
1180     case IDCANCEL: /* kill the connection */
1181     pvar->hosts_state.hosts_dialog = NULL;
1182     notify_closed_connection(pvar);
1183     EndDialog(dlg, 0);
1184     return TRUE;
1185    
1186     default:
1187     return FALSE;
1188     }
1189    
1190     default:
1191     return FALSE;
1192     }
1193     }
1194    
1195 yutakakn 2861 //
1196     // �u�����������m�F�_�C�A���O������
1197     //
1198     static BOOL CALLBACK hosts_replace_dlg_proc(HWND dlg, UINT msg, WPARAM wParam,
1199     LPARAM lParam)
1200 yutakakn 2728 {
1201 yutakakn 2861 PTInstVar pvar;
1202 yutakakn 2728
1203 yutakakn 2861 switch (msg) {
1204     case WM_INITDIALOG:
1205     pvar = (PTInstVar) lParam;
1206     pvar->hosts_state.hosts_dialog = dlg;
1207     SetWindowLong(dlg, DWL_USER, lParam);
1208    
1209     init_hosts_dlg(pvar, dlg);
1210    
1211     // �f�t�H���g���`�F�b�N����������
1212     return TRUE; /* because we do not set the focus */
1213    
1214     case WM_COMMAND:
1215     pvar = (PTInstVar) GetWindowLong(dlg, DWL_USER);
1216    
1217     switch (LOWORD(wParam)) {
1218     case IDC_CONTINUE:
1219     if (IsDlgButtonChecked(dlg, IDC_ADDTOKNOWNHOSTS)) {
1220     add_host_key(pvar);
1221     delete_different_key(pvar);
1222     }
1223    
1224     if (SSHv1(pvar)) {
1225     SSH_notify_host_OK(pvar);
1226     } else { // SSH2
1227     // SSH2���������� SSH_notify_host_OK() �������B
1228     }
1229    
1230     pvar->hosts_state.hosts_dialog = NULL;
1231    
1232     EndDialog(dlg, 1);
1233     return TRUE;
1234    
1235     case IDCANCEL: /* kill the connection */
1236     pvar->hosts_state.hosts_dialog = NULL;
1237     notify_closed_connection(pvar);
1238     EndDialog(dlg, 0);
1239     return TRUE;
1240    
1241     default:
1242     return FALSE;
1243     }
1244    
1245     default:
1246     return FALSE;
1247 yutakakn 2728 }
1248     }
1249    
1250     void HOSTS_do_unknown_host_dialog(HWND wnd, PTInstVar pvar)
1251     {
1252     if (pvar->hosts_state.hosts_dialog == NULL) {
1253     HWND cur_active = GetActiveWindow();
1254    
1255     DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SSHUNKNOWNHOST),
1256     cur_active != NULL ? cur_active : wnd,
1257 yutakakn 2861 hosts_add_dlg_proc, (LPARAM) pvar);
1258 yutakakn 2728 }
1259     }
1260    
1261     void HOSTS_do_different_host_dialog(HWND wnd, PTInstVar pvar)
1262     {
1263     if (pvar->hosts_state.hosts_dialog == NULL) {
1264     HWND cur_active = GetActiveWindow();
1265    
1266     DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SSHDIFFERENTHOST),
1267     cur_active != NULL ? cur_active : wnd,
1268 yutakakn 2861 hosts_replace_dlg_proc, (LPARAM) pvar);
1269 yutakakn 2728 }
1270     }
1271    
1272 yutakakn 2856 //
1273     // �T�[�o�����������������z�X�g���J�������������`�F�b�N����
1274     //
1275     // SSH2���������� (2006.3.24 yutaka)
1276     //
1277     BOOL HOSTS_check_host_key(PTInstVar pvar, char FAR * hostname, Key *key)
1278 yutakakn 2728 {
1279     int found_different_key = 0;
1280    
1281 yutakakn 2856 // ������ known_hosts �t�@�C�������z�X�g���J�����������������������A���������r�����B
1282 yutakakn 2728 if (pvar->hosts_state.prefetched_hostname != NULL
1283 yutakakn 2850 && _stricmp(pvar->hosts_state.prefetched_hostname, hostname) == 0
1284 yutakakn 2856 && match_key(pvar, key)) {
1285    
1286     if (SSHv1(pvar)) {
1287     SSH_notify_host_OK(pvar);
1288     } else {
1289     // SSH2���������� SSH_notify_host_OK() �������B
1290     }
1291 yutakakn 2728 return TRUE;
1292     }
1293    
1294 yutakakn 2856 // �������������������������A�������_���t�@�C��������������
1295 yutakakn 2728 if (begin_read_host_files(pvar, 0)) {
1296     do {
1297 yutakakn 2861 if (!read_host_key(pvar, hostname, 0, 0)) {
1298 yutakakn 2728 break;
1299     }
1300    
1301 yutakakn 2856 if (pvar->hosts_state.hostkey.type != KEY_UNSPEC) {
1302     if (match_key(pvar, key)) {
1303 yutakakn 2728 finish_read_host_files(pvar, 0);
1304 yutakakn 2859 // ���������G���g�����Q�������A���v�����L�[�������������������B
1305     // SSH2���������������������������B(2006.3.29 yutaka)
1306     if (SSHv1(pvar)) {
1307     SSH_notify_host_OK(pvar);
1308     } else {
1309     // SSH2���������� SSH_notify_host_OK() �������B
1310     }
1311 yutakakn 2728 return TRUE;
1312     } else {
1313 yutakakn 2856 // �L�[�� known_hosts ���������������A�L�[�����e���������B
1314 yutakakn 2728 found_different_key = 1;
1315     }
1316     }
1317 yutakakn 2856 } while (pvar->hosts_state.hostkey.type != KEY_UNSPEC); // �L�[�����������������������[�v����
1318 yutakakn 2728
1319     finish_read_host_files(pvar, 0);
1320     }
1321    
1322 yutakakn 2856
1323     // known_hosts �������������L�[���������t�@�C�������������������A�������������������B
1324     pvar->hosts_state.hostkey.type = key->type;
1325     if (key->type == KEY_RSA1) { // SSH1
1326     pvar->hosts_state.hostkey.bits = key->bits;
1327     pvar->hosts_state.hostkey.exp = copy_mp_int(key->exp);
1328     pvar->hosts_state.hostkey.mod = copy_mp_int(key->mod);
1329    
1330     } else if (key->type == KEY_RSA) { // SSH2 RSA
1331     pvar->hosts_state.hostkey.rsa = duplicate_RSA(key->rsa);
1332    
1333     } else { // SSH2 DSA
1334     pvar->hosts_state.hostkey.dsa = duplicate_DSA(key->dsa);
1335    
1336     }
1337 yutakakn 2728 free(pvar->hosts_state.prefetched_hostname);
1338     pvar->hosts_state.prefetched_hostname = _strdup(hostname);
1339    
1340     if (found_different_key) {
1341     PostMessage(pvar->NotificationWindow, WM_COMMAND,
1342     ID_SSHDIFFERENTHOST, 0);
1343     } else {
1344     PostMessage(pvar->NotificationWindow, WM_COMMAND,
1345     ID_SSHUNKNOWNHOST, 0);
1346     }
1347    
1348     return TRUE;
1349     }
1350    
1351     void HOSTS_notify_disconnecting(PTInstVar pvar)
1352     {
1353     if (pvar->hosts_state.hosts_dialog != NULL) {
1354     PostMessage(pvar->hosts_state.hosts_dialog, WM_COMMAND, IDCANCEL,
1355     0);
1356     /* the main window might not go away if it's not enabled. (see vtwin.cpp) */
1357     EnableWindow(pvar->NotificationWindow, TRUE);
1358     }
1359     }
1360    
1361     void HOSTS_end(PTInstVar pvar)
1362     {
1363     int i;
1364    
1365     free(pvar->hosts_state.prefetched_hostname);
1366 yutakakn 2856 #if 0
1367 yutakakn 2728 free(pvar->hosts_state.key_exp);
1368     free(pvar->hosts_state.key_mod);
1369 yutakakn 2856 #else
1370     init_hostkey(&pvar->hosts_state.hostkey);
1371     #endif
1372 yutakakn 2728
1373     if (pvar->hosts_state.file_names != NULL) {
1374     for (i = 0; pvar->hosts_state.file_names[i] != NULL; i++) {
1375     free(pvar->hosts_state.file_names[i]);
1376     }
1377     free(pvar->hosts_state.file_names);
1378     }
1379     }
1380 yutakakn 2761
1381     /*
1382     * $Log: not supported by cvs2svn $
1383 yutakakn 2861 * Revision 1.6 2006/03/29 14:56:52 yutakakn
1384     * known_hosts�t�@�C�����L�[�����������������z�X�g���G���g�����������A�A�v���P�[�V�����G���[�������o�O���C�������B
1385     *
1386 yutakakn 2859 * Revision 1.5 2006/03/26 17:07:17 yutakakn
1387     * fingerprint�\��������
1388     *
1389 yutakakn 2857 * Revision 1.4 2006/03/26 15:43:58 yutakakn
1390     * SSH2��known_hosts���������������B
1391     *
1392 yutakakn 2856 * Revision 1.3 2006/02/18 07:37:02 yutakakn
1393     * �E�R���p�C���� Visual Studio 2005 Standard Edition �������������B
1394     * �Estricmp()��_stricmp()���u������
1395     * �Estrdup()��_strdup()���u������
1396     *
1397 yutakakn 2850 * Revision 1.2 2004/12/19 15:39:42 yutakakn
1398     * CVS LogID������
1399     *
1400 yutakakn 2761 */

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