Develop and Download Open Source Software

Browse Subversion Repository

Annotation of /trunk/teraterm/teraterm/telnet.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3494 - (hide annotations) (download) (as text)
Mon Jun 15 18:37:05 2009 UTC (14 years, 9 months ago) by doda
File MIME type: text/x-csrc
File size: 15613 byte(s)
Line at a timeモードに仮対応

1 maya 3227 /* Tera Term
2     Copyright(C) 1994-1998 T. Teranishi
3     All rights reserved. */
4    
5     /* TERATERM.EXE, TELNET routines */
6    
7     #include "teraterm.h"
8     #include "tttypes.h"
9     #include <stdio.h>
10     #include <string.h>
11     #include "ttcommon.h"
12     #include "ttwinman.h"
13     #include "commlib.h"
14     #include <time.h>
15     #include <process.h>
16    
17     #include "telnet.h"
18     #include "tt_res.h"
19    
20     int TelStatus;
21    
22     enum OptStatus {No, Yes, WantNo, WantYes};
23     enum OptQue {Empty, Opposite};
24    
25     typedef struct {
26     BOOL Accept;
27     enum OptStatus Status;
28     enum OptQue Que;
29     } TelOpt;
30     typedef TelOpt *PTelOpt;
31    
32     typedef struct {
33     TelOpt MyOpt[MaxTelOpt+1];
34     TelOpt HisOpt[MaxTelOpt+1];
35     BYTE SubOptBuff[51];
36     int SubOptCount;
37     BOOL SubOptIAC;
38     BOOL ChangeWinSize;
39     POINT WinSize;
40     int LogFile;
41     } TelRec;
42     typedef TelRec *PTelRec;
43    
44     static TelRec tr;
45    
46     static HANDLE keepalive_thread = (HANDLE)-1L;
47     static HWND keepalive_dialog = NULL;
48     int nop_interval = 0;
49    
50     void DefaultTelRec()
51     {
52     int i;
53    
54     for (i=0 ; i <= MaxTelOpt ; i++)
55     {
56     tr.MyOpt[i].Accept = FALSE;
57     tr.MyOpt[i].Status = No;
58     tr.MyOpt[i].Que = Empty;
59     tr.HisOpt[i].Accept = FALSE;
60     tr.HisOpt[i].Status = No;
61     tr.HisOpt[i].Que = Empty;
62     }
63    
64     tr.SubOptCount = 0;
65     tr.SubOptIAC = FALSE;
66     tr.ChangeWinSize = FALSE;
67     }
68    
69     void InitTelnet()
70     {
71     TelStatus = TelIdle;
72    
73     DefaultTelRec();
74     tr.MyOpt[BINARY].Accept = TRUE;
75     tr.HisOpt[BINARY].Accept = TRUE;
76     tr.MyOpt[SGA].Accept = TRUE;
77     tr.HisOpt[SGA].Accept = TRUE;
78     tr.HisOpt[ECHO].Accept = TRUE;
79     tr.MyOpt[TERMTYPE].Accept = TRUE;
80     tr.MyOpt[NAWS].Accept = TRUE;
81     tr.HisOpt[NAWS].Accept = TRUE;
82     tr.WinSize.x = ts.TerminalWidth;
83     tr.WinSize.y = ts.TerminalHeight;
84    
85     if ((ts.LogFlag & LOG_TEL) != 0)
86     tr.LogFile = _lcreat("TELNET.LOG",0);
87     else
88     tr.LogFile = 0;
89     }
90    
91     void EndTelnet()
92     {
93     if (tr.LogFile != 0)
94     {
95     tr.LogFile = 0;
96     _lclose(tr.LogFile);
97     }
98    
99     TelStopKeepAliveThread();
100     }
101    
102     void TelWriteLog1(BYTE b)
103     {
104     BYTE Temp[3];
105     BYTE Ch;
106    
107     Temp[0] = 0x20;
108     Ch = b / 16;
109     if (Ch <= 9)
110     Ch = Ch + 0x30;
111     else
112     Ch = Ch + 0x37;
113     Temp[1] = Ch;
114    
115     Ch = b & 15;
116     if (Ch <= 9)
117     Ch = Ch + 0x30;
118     else
119     Ch = Ch + 0x37;
120     Temp[2] = Ch;
121     _lwrite(tr.LogFile,Temp,3);
122     }
123    
124     void TelWriteLog(PCHAR Buf, int C)
125     {
126     int i;
127    
128     _lwrite(tr.LogFile,"\015\012>",3);
129     for (i = 0 ; i<= C-1 ; i++)
130     TelWriteLog1(Buf[i]);
131     }
132    
133     void SendBack(BYTE a, BYTE b)
134     {
135     BYTE Str3[3];
136    
137     Str3[0] = IAC;
138     Str3[1] = a;
139     Str3[2] = b;
140     CommRawOut(&cv,Str3,3);
141     if (tr.LogFile!=0)
142     TelWriteLog(Str3,3);
143     }
144    
145     void SendWinSize()
146     {
147     int i;
148     BYTE TmpBuff[21];
149    
150     i = 0;
151    
152     TmpBuff[i] = IAC;
153     i++;
154     TmpBuff[i] = SB;
155     i++;
156     TmpBuff[i] = NAWS;
157     i++;
158     TmpBuff[i] = HIBYTE(tr.WinSize.x);
159     i++;
160     /* if (LOBYTE(tr.WinSize.x) == IAC)
161     {
162     tr.SendBackBuff[i] = IAC;
163     i++;
164     } */
165     TmpBuff[i] = LOBYTE(tr.WinSize.x);
166     i++;
167     TmpBuff[i] = HIBYTE(tr.WinSize.y);
168     i++;
169     /* if (LOBYTE(tr.WinSize.y) == IAC)
170     {
171     tr.SendBackBuff[i] = IAC;
172     i++;
173     } */
174     TmpBuff[i] = LOBYTE(tr.WinSize.y);
175     i++;
176     TmpBuff[i] = IAC;
177     i++;
178     TmpBuff[i]= SE;
179     i++;
180    
181     CommRawOut(&cv,TmpBuff,i);
182     if (tr.LogFile!=0)
183     TelWriteLog(TmpBuff,i);
184     }
185    
186     void ParseTelIAC(BYTE b)
187     {
188     switch (b) {
189     case SE: break;
190     case NOP:
191     case DM:
192     case BREAK:
193     case IP:
194     case AO:
195     case AYT:
196     case EC:
197     case EL:
198     case GOAHEAD:
199     TelStatus = TelIdle;
200     break;
201     case SB:
202     TelStatus = TelSB;
203     tr.SubOptCount = 0;
204     break;
205     case WILLTEL:
206     TelStatus = TelWill;
207     break;
208     case WONTTEL:
209     TelStatus = TelWont;
210     break;
211     case DOTEL:
212     TelStatus = TelDo;
213     break;
214     case DONTTEL:
215     TelStatus = TelDont;
216     break;
217     case IAC:
218     TelStatus = TelIdle;
219     break;
220     default:
221     TelStatus = TelIdle;
222     }
223     }
224    
225     void ParseTelSB(BYTE b)
226     {
227     BYTE TmpStr[51];
228     int i;
229    
230     if (tr.SubOptIAC)
231     {
232     tr.SubOptIAC = FALSE;
233     switch (b) {
234     case SE:
235     if ((tr.MyOpt[TERMTYPE].Status == Yes) &&
236     (tr.SubOptCount >= 2) &&
237     (tr.SubOptBuff[0] == TERMTYPE) &&
238     (tr.SubOptBuff[1] == 1))
239     {
240     #if 1
241     _snprintf_s(TmpStr, sizeof(TmpStr), _TRUNCATE, "%c%c%c%c%s%c%c", IAC, SB, TERMTYPE, 0, ts.TermType, IAC, SE);
242     // 4 �o�C�g���� 0 �����������A������������������
243     i = strlen(TmpStr + 4) + 4;
244     #else
245     TmpStr[0] = IAC;
246     TmpStr[1] = SB;
247     TmpStr[2] = TERMTYPE;
248     TmpStr[3] = 0;
249     strcpy(&TmpStr[4],ts.TermType);
250     i = 4 + strlen(ts.TermType);
251     TmpStr[i] = IAC;
252     i++;
253     TmpStr[i] = SE;
254     i++;
255     #endif
256     CommRawOut(&cv,TmpStr,i);
257    
258     if (tr.LogFile!=0)
259     TelWriteLog(TmpStr,i);
260     }
261     else if ( /* (tr.HisOpt[NAWS].Status == Yes) && */
262     (tr.SubOptCount >= 5) &&
263     (tr.SubOptBuff[0] == NAWS))
264     {
265     tr.WinSize.x = tr.SubOptBuff[1]*256+
266     tr.SubOptBuff[2];
267     tr.WinSize.y = tr.SubOptBuff[3]*256+
268     tr.SubOptBuff[4];
269     tr.ChangeWinSize = TRUE;
270     }
271     tr.SubOptCount = 0;
272     TelStatus = TelIdle;
273     return ;
274     /* case IAC: braek; */
275     default:
276     if (tr.SubOptCount >= sizeof(tr.SubOptBuff)-1)
277     {
278     tr.SubOptCount = 0;
279     TelStatus = TelIdle;
280     return;
281     }
282     else {
283     tr.SubOptBuff[tr.SubOptCount] = IAC;
284     tr.SubOptCount++;
285     if (b==IAC)
286     {
287     tr.SubOptIAC = TRUE;
288     return;
289     }
290     }
291     }
292     }
293     else
294     if (b==IAC)
295     {
296     tr.SubOptIAC = TRUE;
297     return;
298     }
299    
300     if (tr.SubOptCount >= sizeof(tr.SubOptBuff)-1)
301     {
302     tr.SubOptCount = 0;
303     tr.SubOptIAC = FALSE;
304     TelStatus = TelIdle;
305     }
306     else {
307     tr.SubOptBuff[tr.SubOptCount] = b;
308     tr.SubOptCount++;
309     }
310     }
311    
312     void ParseTelWill(BYTE b)
313     {
314     if (b <= MaxTelOpt)
315     {
316     switch (tr.HisOpt[b].Status) {
317     case No:
318     if (tr.HisOpt[b].Accept)
319     {
320     SendBack(DOTEL,b);
321     tr.HisOpt[b].Status = Yes;
322     }
323     else
324     SendBack(DONTTEL,b);
325     break;
326    
327     case WantNo:
328     switch (tr.HisOpt[b].Que) {
329     case Empty:
330     tr.HisOpt[b].Status = No;
331     break;
332     case Opposite:
333     tr.HisOpt[b].Status = Yes;
334     break;
335     }
336     break;
337    
338     case WantYes:
339     switch (tr.HisOpt[b].Que) {
340     case Empty:
341     tr.HisOpt[b].Status = Yes;
342     break;
343     case Opposite:
344     tr.HisOpt[b].Status = WantNo;
345     tr.HisOpt[b].Que = Empty;
346     SendBack(DONTTEL,b);
347     break;
348     }
349     break;
350     }
351     }
352     else
353     SendBack(DONTTEL,b);
354    
355     switch (b) {
356     case ECHO:
357     if (ts.TelEcho>0)
358     switch (tr.HisOpt[ECHO].Status) {
359     case Yes:
360     ts.LocalEcho = 0;
361 doda 3494 cv.TelLineMode = FALSE;
362 maya 3227 break;
363     case No:
364     ts.LocalEcho = 1;
365     break;
366     }
367     break;
368     case BINARY:
369     switch (tr.HisOpt[BINARY].Status) {
370     case Yes:
371     cv.TelBinRecv = TRUE;
372     break;
373     case No:
374     cv.TelBinRecv = FALSE;
375     break;
376     }
377     break;
378     }
379     TelStatus = TelIdle;
380     }
381    
382     void ParseTelWont(BYTE b)
383     {
384     if (b <= MaxTelOpt)
385     {
386     switch (tr.HisOpt[b].Status) {
387     case Yes:
388     tr.HisOpt[b].Status = No;
389     SendBack(DONTTEL,b);
390     break;
391    
392     case WantNo:
393     switch (tr.HisOpt[b].Que) {
394     case Empty:
395     tr.HisOpt[b].Status = No;
396     break;
397     case Opposite:
398     tr.HisOpt[b].Status = WantYes;
399     tr.HisOpt[b].Que = Empty;
400     SendBack(DOTEL,b);
401     break;
402     }
403     break;
404    
405     case WantYes:
406     switch (tr.HisOpt[b].Que) {
407     case Empty:
408     tr.HisOpt[b].Status = No;
409     break;
410     case Opposite:
411     tr.HisOpt[b].Status = No;
412     tr.HisOpt[b].Que = Empty;
413     break;
414     }
415     break;
416     }
417     }
418     else
419     SendBack(DONTTEL,b);
420    
421     switch (b) {
422     case ECHO:
423     if (ts.TelEcho>0)
424     switch (tr.HisOpt[ECHO].Status) {
425     case Yes:
426     ts.LocalEcho = 0;
427     break;
428     case No:
429     ts.LocalEcho = 1;
430     break;
431     }
432     break;
433     case BINARY:
434     switch (tr.HisOpt[BINARY].Status) {
435     case Yes:
436     cv.TelBinRecv = TRUE;
437     break;
438     case No:
439     cv.TelBinRecv = FALSE;
440     break;
441     }
442     break;
443     }
444     TelStatus = TelIdle;
445     }
446    
447     void ParseTelDo(BYTE b)
448     {
449     if (b <= MaxTelOpt)
450     {
451     switch (tr.MyOpt[b].Status) {
452     case No:
453     if (tr.MyOpt[b].Accept)
454     {
455     tr.MyOpt[b].Status = Yes;
456     SendBack(WILLTEL,b);
457     }
458     else
459     SendBack(WONTTEL,b);
460     break;
461    
462     case WantNo:
463     switch (tr.MyOpt[b].Que) {
464     case Empty:
465     tr.MyOpt[b].Status = No;
466     break;
467     case Opposite:
468     tr.MyOpt[b].Status = Yes;
469     break;
470     }
471     break;
472    
473     case WantYes:
474     switch (tr.MyOpt[b].Que) {
475     case Empty:
476     tr.MyOpt[b].Status = Yes;
477     break;
478     case Opposite:
479     tr.MyOpt[b].Status = WantNo;
480     tr.MyOpt[b].Que = Empty;
481     SendBack(WONTTEL,b);
482     break;
483     }
484     break;
485     }
486     }
487     else
488     SendBack(WONTTEL,b);
489    
490     switch (b) {
491     case BINARY:
492     switch (tr.MyOpt[BINARY].Status) {
493     case Yes:
494     cv.TelBinSend = TRUE;
495     break;
496     case No:
497     cv.TelBinSend = FALSE;
498     break;
499     }
500     break;
501     case NAWS:
502     if (tr.MyOpt[NAWS].Status==Yes)
503     SendWinSize();
504     break;
505 doda 3494 case SGA:
506     if (tr.MyOpt[SGA].Status==Yes)
507     cv.TelLineMode = FALSE;
508     break;
509 maya 3227 }
510     TelStatus = TelIdle;
511     }
512    
513     void ParseTelDont(BYTE b)
514     {
515     if (b <= MaxTelOpt)
516     {
517     switch (tr.MyOpt[b].Status) {
518     case Yes:
519     tr.MyOpt[b].Status = No;
520     SendBack(WONTTEL,b);
521     break;
522    
523     case WantNo:
524     switch (tr.MyOpt[b].Que) {
525     case Empty:
526     tr.MyOpt[b].Status = No;
527     break;
528     case Opposite:
529     tr.MyOpt[b].Status = WantYes;
530     tr.MyOpt[b].Que = Empty;
531     SendBack(WILLTEL,b);
532     break;
533     }
534     break;
535    
536     case WantYes:
537     switch (tr.MyOpt[b].Que) {
538     case Empty:
539     tr.MyOpt[b].Status = No;
540     break;
541     case Opposite:
542     tr.MyOpt[b].Status = No;
543     tr.MyOpt[b].Que = Empty;
544     break;
545     }
546     break;
547     }
548     }
549     else
550     SendBack(WONTTEL,b);
551    
552     switch (b) {
553     case BINARY:
554     switch (tr.MyOpt[BINARY].Status) {
555     case Yes:
556     cv.TelBinSend = TRUE;
557     break;
558     case No:
559     cv.TelBinSend = FALSE;
560     break;
561     }
562     break;
563     }
564     TelStatus = TelIdle;
565     }
566    
567     void ParseTel(BOOL *Size, int *nx, int *ny)
568     {
569     BYTE b;
570     int c;
571    
572     c = CommReadRawByte(&cv,&b);
573    
574     while ((c>0) && (cv.TelMode))
575     {
576     if (tr.LogFile!=0)
577     {
578     if (TelStatus==TelIAC)
579     {
580     _lwrite(tr.LogFile,"\015\012<",3);
581     TelWriteLog1(0xff);
582     }
583     TelWriteLog1(b);
584     }
585    
586     tr.ChangeWinSize = FALSE;
587    
588     switch (TelStatus) {
589     case TelIAC: ParseTelIAC(b); break;
590     case TelSB: ParseTelSB(b); break;
591     case TelWill: ParseTelWill(b); break;
592     case TelWont: ParseTelWont(b); break;
593     case TelDo: ParseTelDo(b); break;
594     case TelDont: ParseTelDont(b); break;
595     case TelNop: TelStatus = TelIdle; break;
596     }
597     if (TelStatus == TelIdle) cv.TelMode = FALSE;
598    
599     if (cv.TelMode) c = CommReadRawByte(&cv,&b);
600     }
601    
602     *Size = tr.ChangeWinSize;
603     *nx = tr.WinSize.x;
604     *ny = tr.WinSize.x;
605     }
606    
607     void TelEnableHisOpt(BYTE b)
608     {
609     if (b <= MaxTelOpt)
610     {
611     switch (tr.HisOpt[b].Status) {
612     case No:
613     tr.HisOpt[b].Status = WantYes;
614     SendBack(DOTEL,b);
615     break;
616    
617     case WantNo:
618     if (tr.HisOpt[b].Que==Empty)
619     tr.HisOpt[b].Que = Opposite;
620     break;
621    
622     case WantYes:
623     if (tr.HisOpt[b].Que==Opposite)
624     tr.HisOpt[b].Que = Empty;
625     break;
626     }
627     }
628     }
629    
630     void TelDisableHisOpt(BYTE b)
631     {
632     if (b <= MaxTelOpt)
633     {
634     switch (tr.HisOpt[b].Status) {
635     case Yes:
636     tr.HisOpt[b].Status = WantNo;
637     SendBack(DONTTEL,b);
638     break;
639    
640     case WantNo:
641     if (tr.HisOpt[b].Que==Opposite)
642     tr.HisOpt[b].Que = Empty;
643     break;
644    
645     case WantYes:
646     if (tr.HisOpt[b].Que==Empty)
647     tr.HisOpt[b].Que = Opposite;
648     break;
649     }
650     }
651     }
652    
653     void TelEnableMyOpt(BYTE b)
654     {
655     if (b <= MaxTelOpt)
656     {
657     switch (tr.MyOpt[b].Status) {
658     case No:
659     tr.MyOpt[b].Status = WantYes;
660     SendBack(WILLTEL,b);
661     break;
662    
663     case WantNo:
664     if (tr.MyOpt[b].Que==Empty)
665     tr.MyOpt[b].Que = Opposite;
666     break;
667    
668     case WantYes:
669     if (tr.MyOpt[b].Que==Opposite)
670     tr.MyOpt[b].Que = Empty;
671     break;
672     }
673     }
674     }
675    
676     void TelDisableMyOpt(BYTE b)
677     {
678     if (b <= MaxTelOpt)
679     {
680     switch (tr.MyOpt[b].Status) {
681     case Yes:
682     tr.MyOpt[b].Status = WantNo;
683     SendBack(WONTTEL,b);
684     break;
685    
686     case WantNo:
687     if (tr.MyOpt[b].Que==Opposite)
688     tr.MyOpt[b].Que = Empty;
689     break;
690    
691     case WantYes:
692     if (tr.MyOpt[b].Que==Empty)
693     tr.MyOpt[b].Que = Opposite;
694     break;
695     }
696     }
697     }
698    
699     void TelInformWinSize(int nx, int ny)
700     {
701     if ((tr.MyOpt[NAWS].Status==Yes) &&
702     ((nx!=tr.WinSize.x) ||
703     (ny!=tr.WinSize.y)))
704     {
705     tr.WinSize.x = nx;
706     tr.WinSize.y = ny;
707     SendWinSize();
708     }
709     }
710    
711     void TelSendAYT()
712     {
713     BYTE Str[2];
714    
715     Str[0] = IAC;
716     Str[1] = AYT;
717     CommRawOut(&cv,Str,2);
718     CommSend(&cv);
719     if (tr.LogFile!=0)
720     TelWriteLog(Str,2);
721     }
722    
723     void TelSendBreak()
724     {
725     BYTE Str[2];
726    
727     Str[0] = IAC;
728     Str[1] = BREAK;
729     CommRawOut(&cv,Str,2);
730     CommSend(&cv);
731     if (tr.LogFile!=0)
732     TelWriteLog(Str,2);
733     }
734    
735     void TelChangeEcho()
736     {
737     if (ts.LocalEcho==0)
738     TelEnableHisOpt(ECHO);
739     else
740     TelDisableHisOpt(ECHO);
741     }
742    
743     void TelSendNOP()
744     {
745     BYTE Str[2];
746    
747     Str[0] = IAC;
748     Str[1] = NOP;
749     CommRawOut(&cv,Str,2);
750     CommSend(&cv);
751     if (tr.LogFile!=0)
752     TelWriteLog(Str,2);
753     }
754    
755     #define WM_SEND_HEARTBEAT (WM_USER + 1)
756    
757     static LRESULT CALLBACK telnet_heartbeat_dlg_proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
758     {
759    
760     switch (msg) {
761     case WM_INITDIALOG:
762     return FALSE;
763    
764     case WM_SEND_HEARTBEAT:
765     TelSendNOP();
766     return TRUE;
767     break;
768    
769     case WM_COMMAND:
770     switch (wp) {
771     }
772    
773     switch (LOWORD(wp)) {
774     case IDOK:
775     {
776     return TRUE;
777     }
778    
779     case IDCANCEL:
780     EndDialog(hWnd, 0);
781     return TRUE;
782     default:
783     return FALSE;
784     }
785     break;
786    
787     case WM_CLOSE:
788     // close�{�^���������������� window ���������������������B
789     return TRUE;
790    
791     case WM_DESTROY:
792     return TRUE;
793    
794     default:
795     return FALSE;
796     }
797     return TRUE;
798     }
799    
800    
801     static unsigned _stdcall TelKeepAliveThread(void *dummy) {
802     static int instance = 0;
803    
804     if (instance > 0)
805     return 0;
806     instance++;
807    
808     while (cv.Open && nop_interval > 0) {
809     if (time(NULL) >= cv.LastSendTime + nop_interval) {
810     SendMessage(keepalive_dialog, WM_SEND_HEARTBEAT, 0, 0);
811     }
812    
813     Sleep(100);
814     }
815     instance--;
816     return 0;
817     }
818    
819     void TelStartKeepAliveThread() {
820     unsigned tid;
821    
822     if (ts.TelKeepAliveInterval > 0) {
823     nop_interval = ts.TelKeepAliveInterval;
824    
825     // ���[�h���X�_�C�A���O������ (2007.12.26 yutaka)
826     keepalive_dialog = CreateDialog(hInst, MAKEINTRESOURCE(IDD_BROADCAST_DIALOG),
827     HVTWin, (DLGPROC)telnet_heartbeat_dlg_proc);
828    
829     keepalive_thread = (HANDLE)_beginthreadex(NULL, 0, TelKeepAliveThread, NULL, 0, &tid);
830     if (keepalive_thread == (HANDLE)-1) {
831     nop_interval = 0;
832     }
833     }
834     }
835    
836     void TelStopKeepAliveThread() {
837     if (keepalive_thread != (HANDLE)-1L) {
838     nop_interval = 0;
839     WaitForSingleObject(keepalive_thread, INFINITE);
840     CloseHandle(keepalive_thread);
841     keepalive_thread = (HANDLE)-1L;
842    
843     DestroyWindow(keepalive_dialog);
844     }
845     }
846    
847     void TelUpdateKeepAliveInterval() {
848     if (cv.Open && cv.TelFlag && ts.TCPPort==ts.TelPort) {
849     if (ts.TelKeepAliveInterval > 0 && keepalive_thread == (HANDLE)-1)
850     TelStartKeepAliveThread();
851     else if (ts.TelKeepAliveInterval == 0 && keepalive_thread != (HANDLE)-1)
852     TelStopKeepAliveThread();
853     else
854     nop_interval = ts.TelKeepAliveInterval;
855     }
856     }

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