Delphi-RouterOS-API
/
RouterOSAPI.pas
694 строки · 19.2 Кб
1{*******************************************************************************
2
3Author: Pavel Skuratovich (aka Chupaka), Minsk, Belarus
4Description: Implementation of MikroTik RouterOS API Client
5Version: 1.3
6E-Mail: chupaka@gmail.com
7Support: http://forum.mikrotik.com/viewtopic.php?t=31555
8Dependencies: Uses Ararat Synapse Library (http://synapse.ararat.cz/)
9Legal issues: Copyright © by Pavel Skuratovich
10
11This source code is provided 'as-is', without any express or
12implied warranty. In no event will the author be held liable
13for any damages arising from the use of this software.
14
15Permission is granted to anyone to use this software for any
16purpose, including commercial applications, and to alter it
17and redistribute it freely, subject to the following
18restrictions:
19
201. The origin of this software must not be misrepresented,
21you must not claim that you wrote the original software.
22If you use this software in a product, an acknowledgment
23in the product documentation would be appreciated but is
24not required.
25
262. Altered source versions must be plainly marked as such, and
27must not be misrepresented as being the original software.
28
293. This notice may not be removed or altered from any source
30distribution.
31
32********************************************************************************
33
34API over TLS notes:
35
36Added in RouterOS v6.1. Only TLS without certificate is currently supported.
37Add 'ssl_openssl' to your project uses
38(http://synapse.ararat.cz/doku.php/public:howto:sslplugin)
39and then call TRosApiClient.SSLConnect() instead of TRosApiClient.Connect()
40
41********************************************************************************
42
43Version history:
441.3 June 04, 2018
45Added support for RouterOS 6.43+ API login method
46
471.2 June 12, 2013
48Added basic support for API over TLS
49
501.1 November 5, 2009
51Delphi 2009 compatibility (thanks to Anton Ekermans for testing)
52Requires Synapse Release 39
53
541.0 May 1, 2009
55First public release
56
570.1 April 18, 2009
58Unit was rewritten to implement database-like interface
59
600.0 May 10, 2008
61The beginning
62
63*******************************************************************************}
64
65unit RouterOSAPI;
66
67interface
68
69uses
70SysUtils, Classes, StrUtils, blcksock, synautil, synsock, synacode;
71
72type
73TRosApiWord = record
74Name,
75Value: AnsiString;
76end;
77
78TRosApiSentence = array of TROSAPIWord;
79
80TRosApiClient = class;
81
82TRosApiResult = class
83private
84Client: TROSAPIClient;
85Tag: AnsiString;
86Sentences: array of TRosApiSentence;
87FTrap: Boolean;
88FTrapMessage: AnsiString;
89FDone: Boolean;
90
91constructor Create;
92
93function GetValueByName(const Name: AnsiString): AnsiString;
94function GetValues: TRosApiSentence;
95function GetEof: Boolean;
96function GetRowsCount: Integer;
97public
98property ValueByName[const Name: AnsiString]: AnsiString read GetValueByName; default;
99property Values: TRosApiSentence read GetValues;
100function GetOne(const Wait: Boolean): Boolean;
101function GetAll: Boolean;
102
103property RowsCount: Integer read GetRowsCount;
104
105property Eof: Boolean read GetEof;
106property Trap: Boolean read FTrap;
107property Done: Boolean read FDone;
108procedure Next;
109
110procedure Cancel;
111end;
112
113TRosApiClient = class
114private
115FNextTag: Cardinal;
116FSock: TTCPBlockSocket;
117FTimeout: Integer;
118
119FLastError: AnsiString;
120
121Sentences: array of TRosApiSentence;
122
123function SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
124function SockRecvBufferStr(Length: Cardinal): AnsiString;
125
126procedure SendWord(s: AnsiString);
127
128function RecvWord(const Wait: Boolean; out w: AnsiString): Boolean;
129function RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
130function GetSentenceWithTag(const Tag: AnsiString; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
131procedure ClearSentenceTag(var Sentence: TRosApiSentence);
132function DoLogin(const Username, Password: AnsiString): Boolean;
133public
134function Connect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8728'): Boolean;
135function SSLConnect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8729'): Boolean;
136function Query(const Request: array of AnsiString;
137const GetAllAfterQuery: Boolean): TROSAPIResult;
138function Execute(const Request: array of AnsiString): Boolean;
139
140property Timeout: Integer read FTimeout write FTimeout;
141property LastError: AnsiString read FLastError;
142
143constructor Create;
144destructor Destroy; override;
145
146procedure Disconnect;
147
148function GetWordValueByName(Sentence: TROSAPISentence; Name: AnsiString;
149RaiseErrorIfNotFound: Boolean = False): AnsiString;
150end;
151
152implementation
153
154{******************************************************************************}
155
156function HexToStr(hex: AnsiString): AnsiString;
157const
158Convert: 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);
163var
164i: Integer;
165begin
166Result := '';
167
168if Length(hex) mod 2 <> 0 then
169raise Exception.Create('Invalid hex value') at @HexToStr;
170
171SetLength(Result, Length(hex) div 2);
172
173for i := 1 to Length(hex) div 2 do
174begin
175if not (hex[i * 2 - 1] in ['0'..'9', 'a'..'f']) or not (hex[i * 2] in ['0'..'9', 'a'..'f']) then
176raise Exception.Create('Invalid hex value') at @HexToStr;
177Result[i] := AnsiChar((Convert[hex[i * 2 - 1]] shl 4) + Convert[hex[i * 2]]);
178end;
179end;
180
181{******************************************************************************}
182
183constructor TRosApiResult.Create;
184begin
185inherited Create;
186FTrap := False;
187FTrapMessage := '';
188FDone := False;
189SetLength(Sentences, 0);
190end;
191
192{******************************************************************************}
193
194constructor TRosApiClient.Create;
195begin
196inherited Create;
197FNextTag := 1;
198FTimeout := 30000;
199FLastError := '';
200FSock := TTCPBlockSocket.Create;
201end;
202
203{******************************************************************************}
204
205destructor TRosApiClient.Destroy;
206begin
207FSock.Free;
208inherited Destroy;
209end;
210
211{******************************************************************************}
212
213function TRosApiClient.Connect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8728'): Boolean;
214begin
215FLastError := '';
216FSock.CloseSocket;
217FSock.LineBuffer := '';
218FSock.Connect(Hostname, Port);
219Result := FSock.LastError = 0;
220FLastError := FSock.LastErrorDesc;
221if not Result then Exit;
222
223Result := DoLogin(Username, Password);
224end;
225
226{******************************************************************************}
227
228function TRosApiClient.SSLConnect(const Hostname, Username, Password: AnsiString; const Port: AnsiString = '8729'): Boolean;
229begin
230if FSock.SSL.LibName = 'ssl_none' then
231begin
232FLastError := 'No SSL/TLS support compiled';
233Result := False;
234Exit;
235end;
236
237FLastError := '';
238FSock.CloseSocket;
239FSock.LineBuffer := '';
240FSock.Connect(Hostname, Port);
241Result := FSock.LastError = 0;
242FLastError := FSock.LastErrorDesc;
243if not Result then Exit;
244
245FSock.SSL.Ciphers := 'ADH';
246FSock.SSL.SSLType := LT_TLSv1;
247FSock.SSLDoConnect;
248Result := FSock.LastError = 0;
249FLastError := FSock.LastErrorDesc;
250if not Result then Exit;
251
252Result := DoLogin(Username, Password);
253end;
254
255{******************************************************************************}
256
257function TRosApiClient.DoLogin(const Username, Password: AnsiString): Boolean;
258var
259Res, Res2: TRosApiResult;
260begin
261Result := False;
262
263// post-6.43 login method
264Res := Query(['/login', '=name=' + Username, '=password=' + Password], True);
265if Res.Trap then
266// login error
267FSock.CloseSocket
268else
269if Res.Done then
270begin
271if High(Res.Sentences) <> -1 then
272begin
273// fallback to pre-6.43 login method
274Res2 := Query(['/login', '=name=' + Username, '=response=00' +
275StrToHex(MD5(#0 + Password + HexToStr(Res['=ret'])))], True);
276if Res2.Trap then
277FSock.CloseSocket
278else
279Result := True;
280Res2.Free;
281end
282else
283Result := True;
284end
285else
286raise Exception.Create('Invalid response: ''' + Res.Values[0].Name + ''', expected ''!done''');
287
288Res.Free;
289end;
290
291{******************************************************************************}
292
293procedure TRosApiClient.Disconnect;
294begin
295FSock.CloseSocket;
296FSock.LineBuffer := '';
297end;
298
299{******************************************************************************}
300
301function TRosApiClient.SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
302begin
303Result := True;
304
305if Wait then
306b := FSock.RecvByte(FTimeout)
307else
308b := FSock.RecvByte(0);
309
310if (FSock.LastError = WSAETIMEDOUT) and (not Wait) then
311Result := False;
312if (FSock.LastError = WSAETIMEDOUT) and Wait then
313raise Exception.Create('Socket recv timeout in SockRecvByte');
314end;
315
316{******************************************************************************}
317
318function TRosApiClient.SockRecvBufferStr(Length: Cardinal): AnsiString;
319begin
320Result := FSock.RecvBufferStr(Length, FTimeout);
321
322if FSock.LastError = WSAETIMEDOUT then
323begin
324Result := '';
325raise Exception.Create('Socket recv timeout in SockRecvBufferStr');
326end;
327end;
328
329{******************************************************************************}
330
331procedure TRosApiClient.SendWord(s: AnsiString);
332var
333l: Cardinal;
334begin
335l := Length(s);
336if l < $80 then
337FSock.SendByte(l) else
338if l < $4000 then begin
339l := l or $8000;
340FSock.SendByte((l shr 8) and $ff);
341FSock.SendByte(l and $ff); end else
342if l < $200000 then begin
343l := l or $c00000;
344FSock.SendByte((l shr 16) and $ff);
345FSock.SendByte((l shr 8) and $ff);
346FSock.SendByte(l and $ff); end else
347if l < $10000000 then begin
348l := l or $e0000000;
349FSock.SendByte((l shr 24) and $ff);
350FSock.SendByte((l shr 16) and $ff);
351FSock.SendByte((l shr 8) and $ff);
352FSock.SendByte(l and $ff); end
353else begin
354FSock.SendByte($f0);
355FSock.SendByte((l shr 24) and $ff);
356FSock.SendByte((l shr 16) and $ff);
357FSock.SendByte((l shr 8) and $ff);
358FSock.SendByte(l and $ff);
359end;
360
361FSock.SendString(s);
362end;
363
364{******************************************************************************}
365
366function TRosApiClient.Query(const Request: array of AnsiString;
367const GetAllAfterQuery: Boolean): TROSAPIResult;
368var
369i: Integer;
370begin
371FLastError := '';
372
373//Result := nil;
374// if not FSock.Connected then Exit;
375
376Result := TRosApiResult.Create;
377Result.Client := Self;
378Result.Tag := IntToHex(FNextTag, 4);
379Inc(FNextTag);
380
381for i := 0 to High(Request) do
382SendWord(Request[i]);
383SendWord('.tag=' + Result.Tag);
384SendWord('');
385
386if GetAllAfterQuery then
387if not Result.GetAll then
388raise Exception.Create('Cannot GetAll: ' + LastError);
389end;
390
391{******************************************************************************}
392
393function TRosApiClient.RecvWord(const Wait: Boolean; out w: AnsiString): Boolean;
394var
395l: Cardinal;
396b: Byte;
397begin
398Result := False;
399if not SockRecvByte(b, Wait) then Exit;
400Result := True;
401
402l := b;
403
404if l >= $f8 then
405raise Exception.Create('Reserved control byte received, cannot proceed') else
406if (l and $80) = 0 then
407else
408if (l and $c0) = $80 then begin
409l := (l and not $c0) shl 8;
410SockRecvByte(b);
411l := l + b; end else
412if (l and $e0) = $c0 then begin
413l := (l and not $e0) shl 8;
414SockRecvByte(b);
415l := (l + b) shl 8;
416SockRecvByte(b);
417l := l + b; end else
418if (l and $f0) = $e0 then begin
419l := (l and not $f0) shl 8;
420SockRecvByte(b);
421l := (l + b) shl 8;
422SockRecvByte(b);
423l := (l + b) shl 8;
424SockRecvByte(b);
425l := l + b; end else
426if (l and $f8) = $f0 then begin
427SockRecvByte(b);
428l := b shl 8;
429SockRecvByte(b);
430l := (l + b) shl 8;
431SockRecvByte(b);
432l := (l + b) shl 8;
433SockRecvByte(b);
434l := l + b;
435end;
436
437w := SockRecvBufferStr(l);
438end;
439
440{******************************************************************************}
441
442function TRosApiClient.RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
443var
444p: Integer;
445w: AnsiString;
446begin
447repeat
448if RecvWord(Wait, w) then
449begin
450SetLength(se, 1);
451se[0].Name := w;
452end
453else
454begin
455Result := False;
456Exit;
457end;
458until w <> '';
459
460repeat
461if RecvWord(True, w) then
462begin
463if w = '' then
464begin
465Result := True;
466Exit;
467end
468else
469begin
470SetLength(se, High(se) + 2);
471p := PosEx('=', w, 2);
472if p = 0 then
473se[High(se)].Name := w
474else
475begin
476se[High(se)].Name := Copy(w, 1, p - 1);
477se[High(se)].Value := Copy(w, p + 1, Length(w) - p);
478end;
479end;
480end
481else
482begin
483Result := False;
484Exit;
485end;
486until False;
487end;
488
489{******************************************************************************}
490
491function TRosApiClient.GetSentenceWithTag(const Tag: AnsiString; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
492var
493i, j: Integer;
494se: TRosApiSentence;
495begin
496Result := False;
497
498for i := 0 to High(Sentences) do
499begin
500if GetWordValueByName(Sentences[i], '.tag') = Tag then
501begin
502Sentence := Sentences[i];
503ClearSentenceTag(Sentence);
504for j := i to High(Sentences) - 1 do
505Sentences[j] := Sentences[j + 1];
506SetLength(Sentences, High(Sentences));
507Result := True;
508Exit;
509end;
510end;
511
512repeat
513if RecvSentence(Wait, se) then
514begin
515if GetWordValueByName(se, '.tag', True) = Tag then
516begin
517Sentence := se;
518ClearSentenceTag(Sentence);
519Result := True;
520Exit;
521end;
522
523SetLength(Sentences, High(Sentences) + 2);
524Sentences[High(Sentences)] := se;
525end
526else
527Exit;
528until False;
529end;
530
531{******************************************************************************}
532
533procedure TRosApiClient.ClearSentenceTag(var Sentence: TRosApiSentence);
534var
535i, j: Integer;
536begin
537for i := High(Sentence) downto 0 do
538if Sentence[i].Name = '.tag' then
539begin
540for j := i to High(Sentence) - 1 do
541Sentence[j] := Sentence[j + 1];
542SetLength(Sentence, High(Sentence));
543end;
544end;
545
546{******************************************************************************}
547
548function TRosApiClient.GetWordValueByName(Sentence: TROSAPISentence; Name: AnsiString;
549RaiseErrorIfNotFound: Boolean = False): AnsiString;
550var
551i: Integer;
552begin
553Result := '';
554for i := 1 to High(Sentence) do
555if (Sentence[i].Name = '=' + Name) or (Sentence[i].Name = Name) then
556begin
557Result := Sentence[i].Value;
558Exit;
559end;
560
561if RaiseErrorIfNotFound then
562raise Exception.Create('API Word ''' + Name + ''' not found in sentence');
563end;
564
565{******************************************************************************}
566
567function TRosApiResult.GetValueByName(const Name: AnsiString): AnsiString;
568begin
569if High(Sentences) = -1 then
570raise Exception.Create('No values - use Get* first?')
571else
572Result := Client.GetWordValueByName(Sentences[0], Name);
573end;
574
575{******************************************************************************}
576
577function TRosApiResult.GetValues: TRosApiSentence;
578begin
579if High(Sentences) = -1 then
580raise Exception.Create('No values - use Get* first?')
581else
582Result := Sentences[0];
583end;
584
585{******************************************************************************}
586
587function TRosApiResult.GetOne(const Wait: Boolean): Boolean;
588begin
589Client.FLastError := '';
590FTrap := False;
591
592SetLength(Sentences, 1);
593
594Result := Client.GetSentenceWithTag(Tag, Wait, Sentences[0]);
595if not Result then Exit;
596
597if Sentences[0][0].Name = '!trap' then
598begin
599FTrap := True;
600Client.FLastError := Self['=message'];
601end;
602
603FDone := Sentences[0][0].Name = '!done';
604end;
605
606{******************************************************************************}
607
608function TRosApiResult.GetAll: Boolean;
609var
610se: TRosApiSentence;
611begin
612Client.FLastError := '';
613FTrap := False;
614
615repeat
616Result := Client.GetSentenceWithTag(Tag, True, se);
617if Result then
618begin
619if se[0].Name = '!trap' then
620begin
621FTrap := True;
622if Client.FLastError <> '' then
623Client.FLastError := Client.FLastError + '; ';
624Client.FLastError := Client.FLastError + Client.GetWordValueByName(se, '=message');
625end else
626if se[0].Name = '!done' then
627begin
628FDone := True;
629if High(se) > 0 then
630begin
631SetLength(Sentences, High(Sentences) + 2);
632Sentences[High(Sentences)] := se;
633end;
634
635Exit;
636end
637else
638begin
639SetLength(Sentences, High(Sentences) + 2);
640Sentences[High(Sentences)] := se;
641end;
642end;
643until False;
644end;
645
646{******************************************************************************}
647
648function TRosApiResult.GetEof: Boolean;
649begin
650Result := High(Sentences) = -1;
651end;
652
653{******************************************************************************}
654
655function TRosApiResult.GetRowsCount: Integer;
656begin
657Result := Length(Sentences);
658end;
659
660{******************************************************************************}
661
662procedure TRosApiResult.Next;
663var
664i: Integer;
665begin
666Client.FLastError := '';
667
668for i := 0 to High(Sentences) - 1 do
669Sentences[i] := Sentences[i + 1];
670SetLength(Sentences, High(Sentences));
671end;
672
673{******************************************************************************}
674
675procedure TRosApiResult.Cancel;
676begin
677if not Client.Execute(['/cancel', '=tag=' + Tag]) then
678raise Exception.Create('Cannot cancel: ' + Client.LastError);
679end;
680
681{******************************************************************************}
682
683function TRosApiClient.Execute(const Request: array of AnsiString): Boolean;
684var
685Res: TRosApiResult;
686begin
687Res := Query(Request, True);
688Result := not Res.Trap;
689Res.Free;
690end;
691
692{******************************************************************************}
693
694end.
695