Delphi-RouterOS-API

Форк
0
/
RouterOSAPI.pas 
694 строки · 19.2 Кб
1
{*******************************************************************************
2

3
Author:        Pavel Skuratovich (aka Chupaka), Minsk, Belarus
4
Description:   Implementation of MikroTik RouterOS API Client
5
Version:       1.3
6
E-Mail:        chupaka@gmail.com
7
Support:       http://forum.mikrotik.com/viewtopic.php?t=31555
8
Dependencies:  Uses Ararat Synapse Library (http://synapse.ararat.cz/)
9
Legal issues:  Copyright © by Pavel Skuratovich
10

11
               This source code is provided 'as-is', without any express or
12
               implied warranty. In no event will the author be held liable
13
               for any damages arising from the use of this software.
14

15
               Permission is granted to anyone to use this software for any
16
               purpose, including commercial applications, and to alter it
17
               and redistribute it freely, subject to the following
18
               restrictions:
19

20
               1. The origin of this software must not be misrepresented,
21
                  you must not claim that you wrote the original software.
22
                  If you use this software in a product, an acknowledgment
23
                  in the product documentation would be appreciated but is
24
                  not required.
25

26
               2. Altered source versions must be plainly marked as such, and
27
                  must not be misrepresented as being the original software.
28

29
               3. This notice may not be removed or altered from any source
30
                  distribution.
31

32
********************************************************************************
33

34
  API over TLS notes:
35

36
    Added in RouterOS v6.1. Only TLS without certificate is currently supported.
37
    Add 'ssl_openssl' to your project uses
38
    (http://synapse.ararat.cz/doku.php/public:howto:sslplugin)
39
    and then call TRosApiClient.SSLConnect() instead of TRosApiClient.Connect()
40

41
********************************************************************************
42

43
Version history:
44
1.3     June 04, 2018
45
        Added support for RouterOS 6.43+ API login method
46

47
1.2     June 12, 2013
48
        Added basic support for API over TLS
49

50
1.1     November 5, 2009
51
        Delphi 2009 compatibility (thanks to Anton Ekermans for testing)
52
        Requires Synapse Release 39
53

54
1.0     May 1, 2009
55
        First public release
56

57
0.1     April 18, 2009
58
        Unit was rewritten to implement database-like interface
59

60
0.0     May 10, 2008
61
        The beginning
62

63
*******************************************************************************}
64

65
unit RouterOSAPI;
66

67
interface
68

69
uses
70
  SysUtils, Classes, StrUtils, blcksock, synautil, synsock, synacode;
71

72
type
73
  TRosApiWord = record
74
    Name,
75
    Value: AnsiString;
76
  end;
77

78
  TRosApiSentence = array of TROSAPIWord;
79

80
  TRosApiClient = class;
81

82
  TRosApiResult = class
83
  private
84
    Client: TROSAPIClient;
85
    Tag: AnsiString;
86
    Sentences: array of TRosApiSentence;
87
    FTrap: Boolean;
88
    FTrapMessage: AnsiString;
89
    FDone: Boolean;
90

91
    constructor Create;
92

93
    function GetValueByName(const Name: AnsiString): AnsiString;
94
    function GetValues: TRosApiSentence;
95
    function GetEof: Boolean;
96
    function GetRowsCount: Integer;
97
  public
98
    property ValueByName[const Name: AnsiString]: AnsiString read GetValueByName; default;
99
    property Values: TRosApiSentence read GetValues;
100
    function GetOne(const Wait: Boolean): Boolean;
101
    function GetAll: Boolean;
102

103
    property RowsCount: Integer read GetRowsCount;
104

105
    property Eof: Boolean read GetEof;
106
    property Trap: Boolean read FTrap;          
107
    property Done: Boolean read FDone;
108
    procedure Next;
109

110
    procedure Cancel;
111
  end;
112

113
  TRosApiClient = class
114
  private
115
    FNextTag: Cardinal;
116
    FSock: TTCPBlockSocket;
117
    FTimeout: Integer;
118

119
    FLastError: AnsiString;
120

121
    Sentences: array of TRosApiSentence;
122

123
    function SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
124
    function SockRecvBufferStr(Length: Cardinal): AnsiString;
125

126
    procedure SendWord(s: AnsiString);
127

128
    function RecvWord(const Wait: Boolean; out w: AnsiString): Boolean;
129
    function RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
130
    function GetSentenceWithTag(const Tag: AnsiString; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
131
    procedure ClearSentenceTag(var Sentence: TRosApiSentence);
132
    function DoLogin(const Username, Password: AnsiString): Boolean;
133
  public
134
    function Connect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8728'): Boolean;
135
    function SSLConnect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8729'): Boolean;
136
    function Query(const Request: array of AnsiString;
137
      const GetAllAfterQuery: Boolean): TROSAPIResult;
138
    function Execute(const Request: array of AnsiString): Boolean;
139

140
    property Timeout: Integer read FTimeout write FTimeout;
141
    property LastError: AnsiString read FLastError;
142

143
    constructor Create;
144
    destructor Destroy; override;
145
    
146
    procedure Disconnect;
147

148
    function GetWordValueByName(Sentence: TROSAPISentence; Name: AnsiString;
149
      RaiseErrorIfNotFound: Boolean = False): AnsiString;
150
  end;
151

152
implementation
153

154
{******************************************************************************}
155

156
function HexToStr(hex: AnsiString): AnsiString;
157
const
158
  Convert: array['0'..'f'] of SmallInt =
159
    ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
160
     -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
161
     -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
162
     -1,10,11,12,13,14,15);
163
var
164
  i: Integer;
165
begin
166
  Result := '';
167

168
  if Length(hex) mod 2 <> 0 then
169
    raise Exception.Create('Invalid hex value') at @HexToStr;
170

171
  SetLength(Result, Length(hex) div 2);
172

173
  for i := 1 to Length(hex) div 2 do
174
  begin
175
    if not (hex[i * 2 - 1] in ['0'..'9', 'a'..'f']) or not (hex[i * 2] in ['0'..'9', 'a'..'f']) then
176
      raise Exception.Create('Invalid hex value') at @HexToStr;
177
    Result[i] := AnsiChar((Convert[hex[i * 2 - 1]] shl 4) + Convert[hex[i * 2]]);
178
  end;
179
end;
180

181
{******************************************************************************}
182

183
constructor TRosApiResult.Create;
184
begin
185
  inherited Create;
186
  FTrap := False;
187
  FTrapMessage := '';
188
  FDone := False;
189
  SetLength(Sentences, 0);
190
end;
191

192
{******************************************************************************}
193

194
constructor TRosApiClient.Create;
195
begin
196
  inherited Create;
197
  FNextTag := 1;
198
  FTimeout := 30000;
199
  FLastError := '';
200
  FSock := TTCPBlockSocket.Create;
201
end;
202
          
203
{******************************************************************************}
204

205
destructor TRosApiClient.Destroy;
206
begin
207
  FSock.Free;
208
  inherited Destroy;
209
end;
210
         
211
{******************************************************************************}
212

213
function TRosApiClient.Connect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8728'): Boolean;
214
begin
215
  FLastError := '';
216
  FSock.CloseSocket;
217
  FSock.LineBuffer := '';
218
  FSock.Connect(Hostname, Port);
219
  Result := FSock.LastError = 0;
220
  FLastError := FSock.LastErrorDesc;
221
  if not Result then Exit;
222

223
  Result := DoLogin(Username, Password);
224
end;
225
                       
226
{******************************************************************************}
227

228
function TRosApiClient.SSLConnect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8729'): Boolean;
229
begin
230
  if FSock.SSL.LibName = 'ssl_none' then
231
  begin
232
    FLastError := 'No SSL/TLS support compiled';
233
    Result := False;
234
    Exit;
235
  end;
236

237
  FLastError := '';
238
  FSock.CloseSocket;
239
  FSock.LineBuffer := '';
240
  FSock.Connect(Hostname, Port);
241
  Result := FSock.LastError = 0;
242
  FLastError := FSock.LastErrorDesc;
243
  if not Result then Exit;
244

245
  FSock.SSL.Ciphers := 'ADH';
246
  FSock.SSL.SSLType := LT_TLSv1;
247
  FSock.SSLDoConnect;
248
  Result := FSock.LastError = 0;
249
  FLastError := FSock.LastErrorDesc;
250
  if not Result then Exit;
251

252
  Result := DoLogin(Username, Password);
253
end;
254

255
{******************************************************************************}
256

257
function TRosApiClient.DoLogin(const Username, Password: AnsiString): Boolean;
258
var
259
  Res, Res2: TRosApiResult;
260
begin
261
  Result := False;
262

263
  // post-6.43 login method
264
  Res := Query(['/login', '=name=' + Username, '=password=' + Password], True);
265
  if Res.Trap then
266
    // login error
267
    FSock.CloseSocket
268
  else
269
    if Res.Done then
270
    begin
271
      if High(Res.Sentences) <> -1 then
272
      begin
273
        // fallback to pre-6.43 login method
274
        Res2 := Query(['/login', '=name=' + Username, '=response=00' +
275
          StrToHex(MD5(#0 + Password + HexToStr(Res['=ret'])))], True);
276
        if Res2.Trap then
277
          FSock.CloseSocket
278
        else
279
          Result := True;
280
        Res2.Free;
281
      end
282
      else
283
        Result := True;
284
    end
285
    else
286
        raise Exception.Create('Invalid response: ''' + Res.Values[0].Name + ''', expected ''!done''');
287
  
288
  Res.Free;
289
end;
290

291
{******************************************************************************}
292

293
procedure TRosApiClient.Disconnect;
294
begin
295
  FSock.CloseSocket;      
296
  FSock.LineBuffer := '';
297
end;
298
      
299
{******************************************************************************}
300

301
function TRosApiClient.SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
302
begin
303
  Result := True;
304

305
  if Wait then
306
    b := FSock.RecvByte(FTimeout)
307
  else
308
    b := FSock.RecvByte(0);
309

310
  if (FSock.LastError = WSAETIMEDOUT) and (not Wait) then
311
    Result := False;
312
  if (FSock.LastError = WSAETIMEDOUT) and Wait then
313
    raise Exception.Create('Socket recv timeout in SockRecvByte');
314
end;
315
     
316
{******************************************************************************}
317

318
function TRosApiClient.SockRecvBufferStr(Length: Cardinal): AnsiString;
319
begin
320
  Result := FSock.RecvBufferStr(Length, FTimeout);
321

322
  if FSock.LastError = WSAETIMEDOUT then
323
  begin
324
    Result := '';
325
    raise Exception.Create('Socket recv timeout in SockRecvBufferStr');
326
  end;
327
end;
328
       
329
{******************************************************************************}
330

331
procedure TRosApiClient.SendWord(s: AnsiString);
332
var
333
  l: Cardinal;
334
begin
335
  l := Length(s);
336
  if l < $80 then
337
    FSock.SendByte(l) else
338
  if l < $4000 then begin
339
    l := l or $8000;
340
    FSock.SendByte((l shr 8) and $ff);
341
    FSock.SendByte(l and $ff); end else
342
  if l < $200000 then begin
343
    l := l or $c00000;
344
    FSock.SendByte((l shr 16) and $ff);
345
    FSock.SendByte((l shr 8) and $ff);
346
    FSock.SendByte(l and $ff); end else
347
  if l < $10000000 then begin          
348
    l := l or $e0000000;
349
    FSock.SendByte((l shr 24) and $ff);
350
    FSock.SendByte((l shr 16) and $ff);
351
    FSock.SendByte((l shr 8) and $ff);
352
    FSock.SendByte(l and $ff); end
353
  else begin
354
    FSock.SendByte($f0);
355
    FSock.SendByte((l shr 24) and $ff);
356
    FSock.SendByte((l shr 16) and $ff);
357
    FSock.SendByte((l shr 8) and $ff);
358
    FSock.SendByte(l and $ff);
359
  end;
360

361
  FSock.SendString(s);
362
end;
363

364
{******************************************************************************}
365

366
function TRosApiClient.Query(const Request: array of AnsiString;
367
  const GetAllAfterQuery: Boolean): TROSAPIResult;
368
var
369
  i: Integer;
370
begin
371
  FLastError := '';
372

373
  //Result := nil;
374
  // if not FSock.Connected then Exit;
375

376
  Result := TRosApiResult.Create;
377
  Result.Client := Self;
378
  Result.Tag := IntToHex(FNextTag, 4);
379
  Inc(FNextTag);
380

381
  for i := 0 to High(Request) do
382
    SendWord(Request[i]);
383
  SendWord('.tag=' + Result.Tag);
384
  SendWord('');
385

386
  if GetAllAfterQuery then
387
    if not Result.GetAll then
388
      raise Exception.Create('Cannot GetAll: ' + LastError);
389
end;
390

391
{******************************************************************************}
392

393
function TRosApiClient.RecvWord(const Wait: Boolean; out w: AnsiString): Boolean;
394
var
395
  l: Cardinal;
396
  b: Byte;
397
begin
398
  Result := False;
399
  if not SockRecvByte(b, Wait) then Exit;
400
  Result := True;
401

402
  l := b;
403

404
  if l >= $f8 then
405
    raise Exception.Create('Reserved control byte received, cannot proceed') else
406
  if (l and $80) = 0 then
407
    else
408
  if (l and $c0) = $80 then begin
409
    l := (l and not $c0) shl 8;
410
    SockRecvByte(b);
411
    l := l + b; end else
412
  if (l and $e0) = $c0 then begin
413
    l := (l and not $e0) shl 8;
414
    SockRecvByte(b);
415
    l := (l + b) shl 8;
416
    SockRecvByte(b);
417
    l := l + b; end else
418
  if (l and $f0) = $e0 then begin
419
    l := (l and not $f0) shl 8;
420
    SockRecvByte(b);
421
    l := (l + b) shl 8;
422
    SockRecvByte(b);
423
    l := (l + b) shl 8;
424
    SockRecvByte(b);
425
    l := l + b; end else
426
  if (l and $f8) = $f0 then begin
427
    SockRecvByte(b);
428
    l := b shl 8;
429
    SockRecvByte(b);
430
    l := (l + b) shl 8;
431
    SockRecvByte(b);
432
    l := (l + b) shl 8;
433
    SockRecvByte(b);
434
    l := l + b;
435
  end;
436

437
  w := SockRecvBufferStr(l);
438
end;
439
      
440
{******************************************************************************}
441

442
function TRosApiClient.RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
443
var
444
  p: Integer;
445
  w: AnsiString;
446
begin
447
  repeat
448
    if RecvWord(Wait, w) then
449
    begin                        
450
      SetLength(se, 1);
451
      se[0].Name := w;
452
    end
453
    else
454
    begin
455
      Result := False;
456
      Exit;
457
    end;
458
  until w <> '';
459

460
  repeat
461
    if RecvWord(True, w) then
462
    begin
463
      if w = '' then
464
      begin
465
        Result := True;
466
        Exit;
467
      end
468
      else
469
      begin
470
        SetLength(se, High(se) + 2);
471
        p := PosEx('=', w, 2);
472
        if p = 0 then
473
          se[High(se)].Name := w
474
        else
475
        begin
476
          se[High(se)].Name := Copy(w, 1, p - 1);
477
          se[High(se)].Value := Copy(w, p + 1, Length(w) - p);
478
        end;
479
      end;
480
    end
481
    else
482
    begin
483
      Result := False;
484
      Exit;
485
    end;
486
  until False;
487
end;
488
      
489
{******************************************************************************}
490

491
function TRosApiClient.GetSentenceWithTag(const Tag: AnsiString; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
492
var
493
  i, j: Integer;
494
  se: TRosApiSentence;
495
begin
496
  Result := False;
497
  
498
  for i := 0 to High(Sentences) do
499
  begin
500
    if GetWordValueByName(Sentences[i], '.tag') = Tag then
501
    begin
502
      Sentence := Sentences[i];
503
      ClearSentenceTag(Sentence);
504
      for j := i to High(Sentences) - 1 do
505
        Sentences[j] := Sentences[j + 1];
506
      SetLength(Sentences, High(Sentences));
507
      Result := True;
508
      Exit;
509
    end;
510
  end;
511

512
  repeat
513
    if RecvSentence(Wait, se) then
514
    begin
515
      if GetWordValueByName(se, '.tag', True) = Tag then
516
      begin
517
        Sentence := se;
518
        ClearSentenceTag(Sentence);
519
        Result := True;
520
        Exit;
521
      end;
522

523
      SetLength(Sentences, High(Sentences) + 2);
524
      Sentences[High(Sentences)] := se;
525
    end
526
    else
527
      Exit;
528
  until False;
529
end;
530

531
{******************************************************************************}
532

533
procedure TRosApiClient.ClearSentenceTag(var Sentence: TRosApiSentence);
534
var
535
  i, j: Integer;
536
begin
537
  for i := High(Sentence) downto 0 do
538
    if Sentence[i].Name = '.tag' then
539
    begin
540
      for j := i to High(Sentence) - 1 do
541
        Sentence[j] := Sentence[j + 1];
542
      SetLength(Sentence, High(Sentence));
543
    end;
544
end;
545

546
{******************************************************************************}
547

548
function TRosApiClient.GetWordValueByName(Sentence: TROSAPISentence; Name: AnsiString;
549
  RaiseErrorIfNotFound: Boolean = False): AnsiString;
550
var
551
  i: Integer;
552
begin
553
  Result := '';
554
  for i := 1 to High(Sentence) do         
555
    if (Sentence[i].Name = '=' + Name) or (Sentence[i].Name = Name) then
556
    begin
557
      Result := Sentence[i].Value;
558
      Exit;
559
    end;
560

561
  if RaiseErrorIfNotFound then
562
    raise Exception.Create('API Word ''' + Name + ''' not found in sentence');
563
end;
564
        
565
{******************************************************************************}
566

567
function TRosApiResult.GetValueByName(const Name: AnsiString): AnsiString;
568
begin
569
  if High(Sentences) = -1 then
570
    raise Exception.Create('No values - use Get* first?')
571
  else
572
    Result := Client.GetWordValueByName(Sentences[0], Name);
573
end;
574

575
{******************************************************************************}
576

577
function TRosApiResult.GetValues: TRosApiSentence;
578
begin
579
  if High(Sentences) = -1 then
580
    raise Exception.Create('No values - use Get* first?')
581
  else
582
    Result := Sentences[0];
583
end;
584

585
{******************************************************************************}
586

587
function TRosApiResult.GetOne(const Wait: Boolean): Boolean;
588
begin
589
  Client.FLastError := '';
590
  FTrap := False;
591

592
  SetLength(Sentences, 1);
593

594
  Result := Client.GetSentenceWithTag(Tag, Wait, Sentences[0]);
595
  if not Result then Exit;
596

597
  if Sentences[0][0].Name = '!trap' then
598
  begin
599
    FTrap := True;
600
    Client.FLastError := Self['=message'];
601
  end;
602

603
  FDone := Sentences[0][0].Name = '!done';
604
end;
605

606
{******************************************************************************}
607

608
function TRosApiResult.GetAll: Boolean;
609
var
610
  se: TRosApiSentence;
611
begin
612
  Client.FLastError := '';
613
  FTrap := False;
614

615
  repeat
616
    Result := Client.GetSentenceWithTag(Tag, True, se);
617
    if Result then
618
    begin
619
      if se[0].Name = '!trap' then
620
      begin
621
        FTrap := True;
622
        if Client.FLastError <> '' then
623
          Client.FLastError := Client.FLastError + '; ';
624
        Client.FLastError := Client.FLastError + Client.GetWordValueByName(se, '=message');
625
      end else
626
      if se[0].Name = '!done' then
627
      begin
628
        FDone := True;
629
        if High(se) > 0 then
630
        begin
631
          SetLength(Sentences, High(Sentences) + 2);
632
          Sentences[High(Sentences)] := se;
633
        end;
634

635
        Exit;
636
      end
637
      else
638
      begin
639
        SetLength(Sentences, High(Sentences) + 2);
640
        Sentences[High(Sentences)] := se;
641
      end;
642
    end;
643
  until False;
644
end;
645

646
{******************************************************************************}
647

648
function TRosApiResult.GetEof: Boolean;
649
begin
650
  Result := High(Sentences) = -1;
651
end;
652

653
{******************************************************************************}
654

655
function TRosApiResult.GetRowsCount: Integer;
656
begin
657
  Result := Length(Sentences);
658
end;
659

660
{******************************************************************************}
661

662
procedure TRosApiResult.Next;
663
var
664
  i: Integer;
665
begin
666
  Client.FLastError := '';
667

668
  for i := 0 to High(Sentences) - 1 do
669
    Sentences[i] := Sentences[i + 1];
670
  SetLength(Sentences, High(Sentences));
671
end;
672

673
{******************************************************************************}
674

675
procedure TRosApiResult.Cancel;
676
begin
677
  if not Client.Execute(['/cancel', '=tag=' + Tag]) then
678
    raise Exception.Create('Cannot cancel: ' + Client.LastError);
679
end;
680

681
{******************************************************************************}
682

683
function TRosApiClient.Execute(const Request: array of AnsiString): Boolean;
684
var
685
  Res: TRosApiResult;
686
begin
687
  Res := Query(Request, True);
688
  Result := not Res.Trap;
689
  Res.Free;
690
end;
691

692
{******************************************************************************}
693

694
end.
695

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.