21 февр. 2013 г.

Создание VPN точка-точка на OpenVPN

Возникла необходимость сделать VPN точка-точка на W7. Решили сделать на OpenVPN. Сделали, теперь пишу, чтобы не забыть.
1. Качаем инсталляцию с сайта. Для Windows сборки на 64 и 32 разряда разные.
2. Инсталлируем. Не забываем поставить GUI.
3. Одну машину ставим в режим сервера, другую в режим клиента.
Та, что будет в режиме сервера - с фиксированным IP, за firewall-ом. В нем ковыряем дырочку, по не стандартному порту, чтобы затруднить сканирование.
Применять будем протокол UDP, по 2-м причинам:
- опять-таки сканировать труднее,
- производительность даже визуально живее. Проверялось при обращении к мапированному диску в винде, чтении с него аксессовской БД.
Сначала сделали c shared keys. Заработало быстро.
Делов собственно два:
- сгенерировать ключ и разложить на обе машины. Можно прямо в каталог config.
<pre>
openvpn --genkey --secret secret.key
</pre>
- написать конфиг, положить туда же.
Клиент:
<pre>
remote 195.1.2.3 1194
dev    tun
client
proto  udp
ifconfig 10.10.10.2 10.10.10.1
secret secret.key
keepalive 60 300
comp-lzo
verb 1
mute 20
log-append ../connect.log
status ../status
</pre>
Сервер:
<pre>
port  1194
dev   tun
udp-server
proto udp
ifconfig 10.10.10.1 10.10.10.2
secret secret.key
comp-lzo
verb 1
mute 20
log-append ../connect.log 
</pre>

Все, на значке гуя на сервере щелкаем "подключить", он становится желтым.
Будет желтым, пока кто-нибудь не подцепится. Тогда станет зеленым.
На клиенте, том кто вызывает, просто запускаем гуй, "подключить", смотрим какое все зеленое. На сертификатах все сложнее, однако только на первый взгляд. В виндовой инсталляции есть каталог easy-rsa, где лежат скрипты для генерации необходимой инфраструктуры открытых ключей.
Сначала переименовываем vars.bat и правим его. В принципе, достаточно одной правки - указать каталог ключей. Но! Необходимо в этот каталог скопировать файлы index.txt и serial, переименовав их. Если этого не сделать, то CA сгенерируется нормально, а вот ключи для работы не будут подписываться, будут оставаться только запросами на сертификаты, а не сертификатами.
Из запущенного сеанса cmd.exe запускаем последовательно vars.bat, затем командные файлы, которые генерируют dh, ключ и сертификат CA, затем ключи и сертификаты сервера и клиентов. Важно при генерации указывать разные DN для каждого клиента, если только специальной опцией не разрешить проглатывать одинаковые DN.
При каждой генерации образуется закрытый ключ (с расширением key) и сертификат (с расширением srt). Если получается другое расширение, значит открытый ключ у нас сгенерировался, но не подписался ключом CA и остался грязнулей запросом на сертификат.
Для работы вся эта куча ключей не нужна. На каждой машине кладется сертификат CA, чтобы проверять подлинность сертификатов, предъявляемых другой стороной, свой закрытый ключ, свой сертификат.
Соответственно, закрытый ключ CA прячем, т.к. он нужен для подписания новых ключей, а значит на рабочих машинах ему делать нечего, а лежать он будет в укромном месте.
Потом правим конфиги на работу с сертификатами, кладем ca.srt, client.srt, client.key (или srv.srt и srv.key) прямо в конфиг, чтобы не искать. Далее - все так же как и с shared keys - щелкаем мышкой по гую, соединяемся, отсоединяемся.
Клиент:
remote         195.239.178.94 11940
dev        tun
tls-client
proto        udp
ifconfig 10.10.10.2 10.10.10.1
dh        dh1024.pem
ca        ca.crt
cert        client.crt
key        client.key
keepalive 60 300
comp-lzo
verb 1
mute 20
log-append ../connect.log
status ../status

Сервер:
port        11940
dev        tun
tls-server
proto        udp
ifconfig 10.10.10.1 10.10.10.2
dh        dh1024.pem
ca        ca.crt
cert        server.crt
key        server.key
comp-lzo
verb 1
mute 20
log-append ../connect.log 
Еще не раскрыта тема длины ключей, отозванных сертификатов и маршрутизации.

18 февр. 2013 г.

Доработка файлов по 365-П (GUID, переименование, поиск ошибок и т.д.)


unit main; 

interface 

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls, StrUtils, DateUtils, ComCtrls, ExtCtrls, ToolWin; 

type 
  TForm1 = class(TForm) 
    Go_Button:   TButton; 
    OpenDialog1: TOpenDialog; 
    OpenButton:  TButton; 
    SaveDialog1: TSaveDialog; 
    BtnRe: TButton; 
    ToolBar1: TToolBar; 
    Panel1: TPanel; 
    StatusBar1: TStatusBar; 
    ProgressBar1: TProgressBar; 
    SrcOpenDialog: TOpenDialog; 
    MProtocol: TRichEdit; 
    procedure FormCreate(Sender: TObject); 
    procedure BtnReClick(Sender: TObject); 
    procedure OpenButtonClick(Sender: TObject); 
    procedure Go_ButtonClick(Sender: TObject); 
    function  ToOEM(S:String):String; 
    function  ToAnsi(S:String):String; 
    function  WriteLog(Level:Integer; S:String):String; 
    function  FileSizeByName(Name:String): Integer; 
    function  isNum(const st:string): boolean; 

  private 
    { Private declarations } 
  public 
    { Public declarations } 
  end; 
const 
  OstPref='BOS1_'; 
  OstSrcPref='RBN'; 
  OperPref='BV100_'; 
  OperSrcPref='ZNO'; 
  NalschPref='BNS1_'; 
  NalschSrcPref='ZNO'; 
  ReceiptPref='PB1_'; 
  ChunkLimit=470000;    //524288 - 512К 
  Bic='10813823'; 
  INN='7744001497'; 
  ERR=0; 
  WARN=1; 
  INFO=2; 

var 
  Form1: TForm1; 
  InFile,OutFile: TextFile; 
  CurrentFileNum,NumOutFiles,OutFileSize: Integer; 
  SIn,SOut: String; 
  InFileSize, InFilePosition: Integer; 
  DateZapr,KodNO,NomZapr,NomResh,DateResh :String; 
  NumericTags: TStringList; 

implementation 

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
  DateZapr:=''; 
  NomZapr:=''; 
  NomResh:=''; 
  DateResh:=''; 
  NumericTags := TStringList.Create; 
  NumericTags.Add('ИННКО'); 
  NumericTags.Add('КППКО'); 
  NumericTags.Add('БИК'); 
  NumericTags.Add('ИНННП'); 
  NumericTags.Add('КППНП'); 
  NumericTags.Add('НомСчПП'); 
  NumericTags.Add('НомСч'); 
  NumericTags.Add('БИКБП'); 
  NumericTags.Add('НомКоррСч'); 
  NumericTags.Add('НомДок'); 
  NumericTags.Sort; //for Find method! 
end; 

procedure TForm1.OpenButtonClick(Sender: TObject); 
begin 
  OpenDialog1.execute; 
  DateZapr:=''; 
  NomZapr:=''; 
  NomResh:=''; 
  DateResh:=''; 
  MProtocol.Text:=''; 
  WriteLog(INFO,'Файл к обработке: '+OpenDialog1.Filename+''#13+#10); 
end; 

procedure TForm1.BtnReClick(Sender: TObject); 
var 
  FileName,DateStr: String; 
  NameLength: Integer; 
  Today: TDateTime; 
begin 
//Формирование подтерждения по заданному файлу 
//ZNO10813823_774820120203_000594### 
//10@@@ 
//2012-02-06@@@ 
//10:35:37@@@ 
//=== 
    if (AnsiPos('.',ExtractFileName(OpenDialog1.FileName))<>0) then 
      SaveDialog1.FileName:=ReceiptPref+MidStr(ExtractFileName(OpenDialog1.FileName),1,AnsiPos('.',ExtractFileName(OpenDialog1.FileName))-1)+'.TXT'
    else 
      SaveDialog1.FileName:=ReceiptPref+ExtractFileName(OpenDialog1.FileName)+'.TXT'; 
    SaveDialog1.Execute; 
    WriteLog(INFO,'Имя файла подтверждения: '+SaveDialog1.FileName+''#13+#10); 
    Rewrite(OutFile,SaveDialog1.FileName); 
    FileName:=ExtractFileName(OpenDialog1.FileName); 
    Writelog(INFO,''#13+#10); // empty line before receipt 
    Write(OutFile,MidStr(FileName,1,Length(FileName)-4)+'###'+''#13+#10); 
    WriteLog(INFO,MidStr(FileName,1,Length(FileName)-4)+'###'+''#13+#10); 
    Write(OutFile,'10@@@'+''#13+#10); 
    Writelog(INFO,'10@@@'+''#13+#10); 
    DateStr:=DateToStr(Now); 
    Write(OutFile,MidStr(DateStr,7,4)+'-'+MidStr(DateStr,4,2)+'-'+MidStr(DateStr,1,2)+'@@@'+''#13+#10); 
    Writelog(INFO,MidStr(DateStr,7,4)+'-'+MidStr(DateStr,4,2)+'-'+MidStr(DateStr,1,2)+'@@@'+''#13+#10); 
    Write(OutFile,TimeToStr(Now)+'@@@'+''#13+#10); 
    Writelog(INFO,TimeToStr(Now)+'@@@'+''#13+#10); 
    Write(OutFile,'==='+''#13+#10); 
    Writelog(INFO,'==='+''#13+#10); 
    Flush(OutFile); 
    CloseFile(OutFile); 
end; 

procedure TForm1.Go_ButtonClick(Sender: TObject); 
var 
  Id: TGUID; 
  StringNo, StringCount,Code: Integer; 
  Tag,Val,HeaderTag,HeaderVal,StrNo,StrFileNo,StrNumOutFiles,DateForName,NomForName, InfType, Pref:  String; 
  TagLength,HeaderTagLength,i:  Integer; 
  RetCode:Boolean; 
  TopHeader,ThirdHeader: Array of String; 
  TopHeaderLength,ThirdHeaderLength,HeaderState: Integer; 
  Index: Integer; 
begin 
  CurrentFileNum:=1; 
  OutFileSize:=0; 
  InFilePosition:=0; 
  MProtocol.Text:=''; 
  WriteLog(INFO,'Файл к обработке: '+OpenDialog1.Filename+''#13+#10); 
  if not (FileExists(OpenDialog1.Filename)) then  exit; 
  InFileSize:=FileSizeByName(OpenDialog1.Filename); 
  ProgressBar1.Min:=0; 
  ProgressBar1.Max:=InFileSize; 
  ProgressBar1.Position:=0; 
  AssignFile(InFile,OpenDialog1.Filename); 
//  Rewrite(OutFile, OpenDialog1.Filename+'.chk1'); 
  Rewrite(OutFile, OpenDialog1.Filename+'.chk'+Format('%.2d',[CurrentFileNum])); 
  Reset(InFile); 
  if InFileSize > ChunkLimit then 
  begin 
    WriteLog(WARN,'Размер файла больше предельного размера одной части'#13+#10); 
    NumOutFiles:=(InFileSize div ChunkLimit)+1; 
    Str(NumOutFiles,StrNumOutFiles); 
  end; 
  HeaderState:=0; 
  TopHeaderLength:=0; 
  ThirdHeaderLength:=0; 
  while not Eof(InFile) do 
  begin 
    Readln(InFile, SIn); 
    SOut:=ToAnsi(SIn); 
    if HeaderState < 3 then //Еще не считали все заголовки 
    begin 
      if HeaderState=0 then 
      begin //read top header 
        TopHeaderLength:=TopHeaderLength+1; 
        SetLength(TopHeader,TopHeaderLength); 
        TopHeader[TopHeaderLength-1]:=SOut; 
      end; 
      if HeaderState=2 then 
      begin //read 3rd header 
        ThirdHeaderLength:=ThirdHeaderLength+1; 
        SetLength(ThirdHeader,ThirdHeaderLength); 
        ThirdHeader[ThirdHeaderLength-1]:=SOut; 
      end; 
      if SOut='###' then HeaderState:=HeaderState+1; 
    end; 
    StringNo:=StringNo+1; 
    Str(StringNo,StrNo); 
    TagLength:=AnsiPos(':',SOut)-1; 
    if TagLength<>-1 then 
    begin 
      Tag:=MidStr(SOut,1,TagLength); 
      Val:=MidStr(SOut,TagLength+2,Length(SOut)-TagLength-1); 
      if (Tag = 'НомСч') and (HeaderState >=3) then // found new 3rd header when already read top 3 headers 
      begin 
        HeaderState:=2;   //return to 3rd header mode 
        ThirdHeaderLength:=1; //set 1st line of 3rd header 
        ThirdHeader[ThirdHeaderLength-1]:=SOut; // write it down 
        WriteLog(INFO,'Строка '+StrNo+' - Найдено:'+SOut); 
      end; 
      if Tag = 'ТипИнф' then 
      begin 
          InfType:=Val; 
          WriteLog(INFO,'Строка '+StrNo+' - Найден ТипИнф:'+InfType); 
      end; 
      if Tag = 'ИдФайл' then 
      begin 
          CreateGUID(Id); 
          SOut:='ИдФайл:'+ Val + MidStr(GUIDToString(Id),2,36); 
          WriteLog(INFO,'Строка '+StrNo+' - Добавлен GUID к ИдФайл'); 
      end; 
      if Tag = 'КодНО' then 
      begin 
          KodNO:=Val; 
          WriteLog(INFO,'Строка '+StrNo+' - Найден  КодНО:'+KodNO); 
      end; 
      if Tag = 'ИдДок' then 
      begin 
          CreateGUID(Id); 
          SOut:='ИдДок:'+ MidStr(GUIDToString(Id),2,36); 
          WriteLog(INFO,'Строка '+StrNo+' - добавлен GUID к ИдДок'); 
      end; 
      if (Tag = 'НомЧасти') and (InFileSize <= ChunkLimit) then 
      begin 
          SOut:=''; 
          WriteLog(INFO,'Строка '+StrNo+' - Удален НомЧасти'); 
      end; 
      if (Tag = 'КолДок') and (InFileSize > ChunkLimit) then 
      begin 
          SOut:='КолДок:'+StrNumOutFiles; 
          WriteLog(INFO,'Строка '+StrNo+' - КолДок исправлен'); 
      end; 
      if Tag = 'НомЗапр' then 
      begin 
        NomZapr:=''; 
        for i := length(Val) to 6 - 1 do   NomZapr:=NomZapr+'0'; 
          NomZapr:=NomZapr+Val; 
          WriteLog(INFO,'Строка '+StrNo+' - Найден НомЗапр:'+NomZapr); 
      end; 
      if Tag = 'ДатаЗапр' then 
      begin 
          DateZapr:=MidStr(Val,7,4)+MidStr(Val,4,2)+MidStr(Val,1,2); 
          WriteLog(INFO,'Строка '+StrNo+' - Найдена ДатаЗапр:'+DateZapr); 
      end; 
      if Tag = 'НомРеш' then 
      begin 
        NomResh:=''; 
        for i := length(Val) to 6 - 1 do   NomResh:=NomResh+'0'; 
          NomResh:=NomResh+Val; 
          WriteLog(INFO,'Строка '+StrNo+' - Найден НомРеш:'+NomResh); 
      end; 
      if Tag = 'ДатаРеш' then 
      begin 
          DateResh:=MidStr(Val,7,4)+MidStr(Val,4,2)+MidStr(Val,1,2); 
          WriteLog(INFO,'Строка '+StrNo+' - Найдена ДатаРеш:'+DateResh); 
      end; 
      if (NumericTags.Find(Tag,index)) then // Проверка корректности значений числовых тегов 
      begin 
          if not isNum(Val) then 
            WriteLog(ERR,'Строка '+StrNo+' - Ошибка: Не числовые значения:'+Tag+':'+Val); 
      end; 
      if (Val='') and (Tag<>'ИдДок') then // Только последним, после обработки других 
      begin 
          WriteLog(WARN,'Строка '+StrNo+' - Обнаружен пустой тег:'+Tag); 
      end; 
    end 
    else //No tags found! 
    begin 
      if (AnsiPos('Ошибка',SOut))<>0 then 
      begin 
          WriteLog(ERR,'Строка '+StrNo+' - Найдена ошибка: '+SOut); 
      end; 
      if (AnsiPos('Ожидается',SOut))<>0 then 
      begin 
          WriteLog(ERR,'Строка '+StrNo+' - Найдена ошибка: '+SOut); 
      end; 
    end; 
    if SOut<>'' then 
    begin 
      Writeln(OutFile,ToOEM(SOut)); 
      OutFileSize:=OutFileSize+Length(SOut); 
      InFilePosition:=InFilePosition+Length(SOut); 
      ProgressBar1.Position:=InFilePosition; 
      if  (OutFileSize > ChunkLimit) and (TagLength =-1) then 
      //достигли предела и не в середине документа 
      begin 
          Writeln(OutFile,'@@@'); 
          Writeln(OutFile,'==='); 
          OutFileSize:=0; 
          Flush(OutFile); 
          CloseFile(OutFile); 
          CurrentFileNum:=CurrentFileNum+1; 
          StrFileNo:=Format('%.2d',[CurrentFileNum]);  // for filename - with leading zero's 
          WriteLog(INFO,'Строка '+StrNo+' - Начало части '+StrFileNo); 
          Rewrite(OutFile, OpenDialog1.Filename+'.chk'+StrFileNo); 
          Str(CurrentFileNum,StrFileNo);              // for tag in the file - without leading zeros 
          for i:=0 to TopHeaderLength - 1 do 
          begin 
            HeaderTagLength:=AnsiPos(':',TopHeader[i])-1; 
            HeaderTag:=MidStr(TopHeader[i],1,HeaderTagLength); 
            HeaderVal:=MidStr(TopHeader[i],HeaderTagLength+2,Length(TopHeader[i])-HeaderTagLength-1); 
            if HeaderTag='ИдФайл' then 
              TopHeader[i]:='ИдФайл:'+ MidStr(HeaderVal,1,29) + MidStr(GUIDToString(Id),2,36); 
            if HeaderTag='НомЧасти' then 
              TopHeader[i]:='НомЧасти:'+ StrFileNo; 
            if HeaderTag='КолДок' then 
              TopHeader[i]:='КолДок:'+ StrNumOutFiles; 
            Writeln(OutFile,ToOEM(TopHeader[i])); 
          end; 
          Writeln(OutFile,ToOEM('@@@')); // добавить недостающие собаки после первого заголовка 
          for i:=0 to ThirdHeaderLength - 1 do 
            Writeln(OutFile,ToOEM(ThirdHeader[i])); 
      end; 
    end; 
  end; 
  Flush(OutFile); 
  CloseFile(OutFile); 
  SaveDialog1.FileName:=''; 
  if MessageDlg('Исходный запрос был получен электронно?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then 
  begin 
     SrcOpenDialog.Title:='Выберите файл исходного запроса'; 
     SrcOpenDialog.Execute(); 
     SaveDialog1.FileName:=MidStr(ExtractFileName(SrcOpenDialog.FileName),1,AnsiPos('.',ExtractFileName(SrcOpenDialog.FileName))-1); 
     if (InfType='ОСТАТКИ')      then Pref:=OstPref; 
     if (InfType='ОПЕРАЦИИПОСЧ') then Pref:=OperPref; 
     if (InfType='НАЛСЧЕТОВ')    then Pref:=NalschPref; 
     SaveDialog1.FileName:=Pref+SaveDialog1.FileName; 
  end 
  else // create name from file contents 
  begin 
     if (InfType='ОСТАТКИ')      then 
      SaveDialog1.FileName:=OstPref+OstSrcPref+Bic+'_'+KodNO+DateResh+'_'+NomResh; 
     if (InfType='ОПЕРАЦИИПОСЧ') then 
    SaveDialog1.FileName:=OperPref+OperSrcPref+Bic+'_'+KodNO; 
     if (InfType='НАЛСЧЕТОВ')  then 
    SaveDialog1.FileName:=NalschPref+NalschSrcPref+Bic+'_'+KodNO; 
     if ((InfType='ОПЕРАЦИИПОСЧ') or (InfType='НАЛСЧЕТОВ')) then 
    SaveDialog1.FileName:=SaveDialog1.FileName+DateZapr+'_'+NomZapr; 
  end; 
  SaveDialog1.FileName:=SaveDialog1.FileName+'.TXT'; 
  ProgressBar1.Position:=0; 
  if SaveDialog1.Execute then 
  begin 
    if NumOutFiles > 1 then 
    begin // больше чем один файл - нумеруем с "1" 
      for i := 1 to NumOutfiles do 
      begin 
//      Str(i,StrFileNo); 
        StrFileNo:=Format('%.2d',[i]); 
        RetCode:=RenameFile(OpenDialog1.Filename+'.chk'+StrFileNo, ReplaceStr(SaveDialog1.FileName,'BV100','BV1'+StrFileNo)); 
        if RetCode=false then 
          MessageDlg('Невозможно сохранить файл '+ReplaceStr(SaveDialog1.FileName,'BV100','BV1'+StrFileNo), mtError,[mbOk], 0, mbOk) 
        else 
          MessageDlg('Файл сохранен как: '+ReplaceStr(SaveDialog1.FileName,'BV100','BV1'+StrFileNo),mtInformation,[mbOk], 0, mbOk); 
      end; 
    end 
    else // один файл нумеруется с "0" 
    begin 
      StrFileNo:='01'; 
      RetCode:=RenameFile(OpenDialog1.Filename+'.chk'+StrFileNo,SaveDialog1.FileName); 
      if RetCode=false then 
        MessageDlg('Невозможно сохранить файл '+SaveDialog1.FileName, mtError,[mbOk], 0, mbOk) 
      else 
        MessageDlg('Файл сохранен как: '+SaveDialog1.FileName,mtInformation,[mbOk], 0, mbOk); 
    end; 
  end; 
end; 

function TForm1.WriteLog(Level:Integer; S:String):String; 
var 
  AColor:Integer; 
begin 
  case Level of 
    ERR : AColor := clRed; 
    INFO: AColor := clLime; 
    WARN: AColor := clYellow; 
  else 
    AColor := clLime; 
  end; 
  with MProtocol do 
   begin 
     SelStart := Length(Text); 
     SelAttributes.Color := AColor; 
     Lines.Add(S); 
   end; 
  MProtocol.SelStart := MaxInt; 
  SendMessage(MProtocol.handle, EM_SCROLLCARET,0,0); 
  Result:=S; 
end; 

function TForm1.ToOEM(S:String):String; 
{ ConvertAnsiToOem translates a string into the OEM-defined character set } 
{$IFNDEF WIN32} 
var 
  Source, Dest : array[0..255] of Char; 
{$ENDIF} 
begin 
  {$IFDEF WIN32} 
  SetLength(Result, Length(S)); 
  if Length(Result) > 0 then 
    AnsiToOem(PChar(S), PChar(Result)); 
  {$ELSE} 
  if Length(Result) > 0 then 
  begin 
    AnsiToOem(StrPCopy(Source, S), Dest); 
    Result := StrPas(Dest); 
  end; 
  {$ENDIF} 
end; { ConvertAnsiToOem } 

function TForm1.ToAnsi(S:String):String; 
{ ConvertOemToAnsi translates a string into the OEM-defined character set } 
{$IFNDEF WIN32} 
var 
  Source, Dest : array[0..255] of Char; 
{$ENDIF} 
begin 
  {$IFDEF WIN32} 
  SetLength(Result, Length(S)); 
  if Length(Result) > 0 then 
    OemToAnsi(PChar(S), PChar(Result)); 
  {$ELSE} 
  if Length(Result) > 0 then 
  begin 
    OemToAnsi(StrPCopy(Source, S), Dest); 
    Result := StrPas(Dest); 
  end; 
  {$ENDIF} 
end; { ConvertOemToAnsi } 

function  TForm1.FileSizeByName(Name:String): Integer; 
var 
  F: File of byte; 
begin 
  AssignFile(F, Name); 
  Reset(F); 
  Result:=FileSize(F); 
  CloseFile(F); 
end; 

function TForm1.isNum(const st:string): boolean; 
var 
  i: Integer; 
begin 
  isNum:=True; 
  for i := 1 to Length(st) do 
    if not (st[i] in ['0'..'9']) then isNum:=False; 
end; 

end.