unit client_handler; {< Contains TClientHandler TThread descendent to handle client connections.} { This file is part of AMIProxyPal. AMIProxyPal is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Foobar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar. If not, see . } {$mode objfpc}{$H+} interface uses Classes, blcksock, synsock, clientconnection, SysUtils, appprops_bom, IniFiles, astevent_utils ; {$IFDEF UNIX} const cConnectionTimeout = 110; {$ELSE} const cConnectionTimeout = 10060; {$ENDIF} type {: record type to hold simple user login info. } TLoginRecord = record UserName: string; Password: string; Permissions: string; EventsOn: boolean; ByClientID: boolean; RestrictChannel: string; end; {: Handles interaction with client connection. } TClientHandler = class(TThread) private FSocket: TSocket; FSock: TBlockSocket; FClientConn: TClientConnection; FClientList: TClientConnectionList; {: Performs a login for client connection. } function TryClientLogin(var LoginRec: TLoginRecord; APacket: string): boolean; public // ---> Methods procedure Execute; override; function IsTerminated: boolean; // ---> Construction Ahead constructor Create(ASock: TSocket; AClientList: TClientConnectionList); overload; destructor Destroy; override; end; implementation uses client_decorators ; { TClientHandler } function TClientHandler.TryClientLogin(var LoginRec: TLoginRecord; APacket: string) : boolean; var lConfig: TIniFile; sPass, sUser: string; sPassFromFile: string; sPermissions: string; sRestrictChannel: string; bByClientID: boolean; bEvents: boolean; begin result := false; // Get user credentials from imcoming packet. sPass := TAstPacketUtil.GetPacketValue(APacket, 'Secret'); sUser := TAstPacketUtil.GetPacketValue(APacket, 'UserName'); bEvents := (LowerCase(TAstPacketUtil.GetPacketValue(APacket, 'Events')) = 'on'); bByClientID := (Lowercase(TAstPacketUtil.GetPacketValue(APacket, 'ByClientID')) = 'true'); sRestrictChannel := TAstPacketUtil.GetPacketValue(APacket, 'RestrictChannel'); lConfig := gAppINIConfig('users.conf'); try if not lConfig.SectionExists(sUser) then exit; // check password sPassFromFile := lConfig.ReadString(sUser, 'password',''); // get permissions sPermissions := LowerCase(Trim(lConfig.ReadString(sUser, 'permissions', ''))); result := ( (sPassFromFile = sPass) and (sPermissions <> '')); if result then begin LoginRec.Permissions := lConfig.ReadString(sUser, 'permissions', ''); LoginRec.Password := sPass; LoginRec.EventsOn := bEvents; LoginRec.UserName := sUser; LoginRec.ByClientID := bByClientID; LoginRec.RestrictChannel := sRestrictChannel; end else begin FSock.SendString('Response: Error' + #13#10 + 'Message: Authentication failed' + #13#10#13#10); end; finally lConfig.Free; end; end; procedure TClientHandler.Execute; var s, sOutgoing: string; LoginRec: TLoginRecord; iError: integer; begin // First, login client in. if not Terminated then begin s := FSock.RecvTerminated(1500, #13#10#13#10); // if not receive login attempt, terminate. if (s = '') then begin Terminate; exit; end; // try to login client. if not self.TryClientLogin(LoginRec, s) then exit; // if we got this far then successful // create new clientconnection object FClientConn := TClientConnection.Create(FSocket, TProxyStandardDecorator.Create, LoginRec.Permissions); FClientConn.RestrictByID := LoginRec.ByClientID; FClientConn.RestrictChannel := LoginRec.RestrictChannel; // now add the new client connection to the global list FClientList.Lock; try FClientList.Add(FClientConn); finally; FClientList.UnLock; end; // Send response back to the client in standard asterisk AMI format. FSock.SendString('Response: Success' + #13#10 + 'Message: Authentication accepted' + #13#10#13#10); end; repeat; if self.IsTerminated then Terminate; if not Terminated then begin s := FSock.RecvTerminated(50, #13#10#13#10); // check for error and terminate if found iError := FSock.LastError; //1054 if (iError <> 0) and (iError <> cConnectionTimeout) then Terminate; if ((s <> '') and (not Terminated)) then begin FClientConn.Lock; try // add incoming to queue FClientConn.AddIncoming(trim(s)); finally; FClientConn.Unlock; end; end; // send outgoing, if any sOutgoing := FClientConn.GetOutgoing; while (sOutgoing <> '') do begin if Terminated then break; FSock.SendString(sOutgoing + #13#10#13#10); if Terminated then break; sOutgoing := FClientConn.GetOutgoing; end; end; until (Terminated); // remove itself from the client list if FClientConn <> nil then begin FClientList.Lock; try FClientList.Remove(FClientConn); finally; FClientList.UnLock; end; end; end; function TClientHandler.IsTerminated: boolean; begin FClientConn.Lock; try result := FClientConn.Terminated; finally FClientConn.Unlock; end; end; constructor TClientHandler.Create(ASock: TSocket; AClientList: TClientConnectionList); begin FSocket := ASock; FreeOnTerminate := true; // Create block socket object FSock := TBlockSocket.create; FSock.Socket := ASock; // reference to global client list FClientList := AClientList; inherited Create(false); end; destructor TClientHandler.Destroy; begin if Assigned(FClientConn) then FClientConn.free; FSock.CloseSocket; FSock.Free; inherited Destroy; end; end.