In the previous parts (part 1 part 2) i’ve described the theoretical part and implementation problems. So, now we can write the code:

  1. In case we login the user, we just call LsaLogonUser to get the token:
objectpascal Download
function CopyString(const Source : WideString; const Buffer : Pointer; var CurrentPosition : Pointer; LengthInBytes: Cardinal = $FFFFFFFF) : Pointer;
begin
  if (Source <> '' ) then
  begin
    Cardinal(Result) := Cardinal(CurrentPosition) - Cardinal(Buffer);
    if (LengthInBytes = $FFFFFFFF) then
      LengthInBytes := (Length(Source) + 1) * SizeOf(Source[1]);

    CopyMemory(CurrentPosition, PWideChar(Source), LengthInBytes);
    Cardinal(CurrentPosition) := Cardinal(CurrentPosition) + LengthInBytes;
  end
  else
    Result := nil;
end;

procedure CopyUnicodeString(const Target : PUnicodeString; const Source : PUnicodeString; const Buffer : Pointer; var CurrentPosition : Pointer);
begin
  Target^ := Source^;
  Target^.Buffer := CopyString(Target^.Buffer, Buffer, CurrentPosition, Target^.MaximumLength);
end;

procedure CreateLsaLogonPackageBuffer(const UserName, Domain, Password : WideString;
  out Result : PMSV1_0_INTERACTIVE_LOGON; out ResultLength : Cardinal);
var
  LogonCurrentPointer : Pointer;
begin
  ResultLength := SizeOf(Result^) + (Length(UserName) + Length(Domain) + Length(Password) + 3) * SizeOf(UserName[1]);
  Result := GetMemory(ResultLength);
  try
    ZeroMemory(Result, ResultLength);

    Cardinal(LogonCurrentPointer) := Cardinal(Result) + SizeOf(Result^);

    Result^.MessageType := MsV1_0InteractiveLogon;

    RtlInitUnicodeString(@Result^.UserName, PWideChar(UserName));
    CopyUnicodeString(@Result^.UserName, @Result^.UserName, nil, LogonCurrentPointer);

    RtlInitUnicodeString(@Result^.LogonDomainName, PWideChar(Domain));
    CopyUnicodeString(@Result^.LogonDomainName, @Result^.LogonDomainName, nil, LogonCurrentPointer);

    RtlInitUnicodeString(@Result^.Password, PWideChar(Password));
    CopyUnicodeString(@Result^.Password, @Result^.Password, nil, LogonCurrentPointer);
  except
    FreeMemory(Result);
    Raise;
  end;
end;

procedure LoginUser;
var
  LsaHandle : THandle;
  LsaName : LSA_STRING;
  PackageName : LSA_STRING;
  AuthenticationPackage : ULONG;
  LogonPackageBuffer : PMSV1_0_INTERACTIVE_LOGON ;
  LogonPackageBufferLength : Cardinal;
  TokenSource : JwaWindows.TTokenSource;
  SubStatus : Integer;
  SecurityMode : LSA_OPERATIONAL_MODE;
begin
  RtlInitUnicodeString(@LsaName, 'AutoLogonXP login');
  NtCheck(LsaRegisterLogonProcess(LsaName, LsaHandle, @SecurityMode));
  try
    RtlInitString(@PackageName, PCSZ(PChar(MSV1_0_PACKAGE_NAME)));
    LsaLookupAuthenticationPackage(LsaHandle, PackageName, AuthenticationPackage);

    JwaWindows.AllocateLocallyUniqueId(TokenSource.SourceIdentifier);
    TokenSource.SourceName := 'AUTOLGON';

    CreateLsaLogonPackageBuffer(UserName, Domain, Password, LogonPackageBuffer, LogonPackageBufferLength);
    try
      NtCheck(
        LsaLogonUser(
          LsaHandle,
          LsaName,
          Interactive,
          AuthenticationPackage,
          LogonPackageBuffer,
          LogonPackageBufferLength,
          TokenGroups,
          @TokenSource,
          Pointer(Profile),
          ProfileLength,
          JwaWindows.LUID(Result^.LogonId),
          Result^.UserToken,
          JwaWindows.QUOTA_LIMITS(Result^.Quotas),
          SubStatus
        ));
    finally
      FreeLsaLogonPackageBuffer(LogonPackageBuffer);
    end;
  finally
    LsaDeregisterLogonProcess(LsaHandle);
  end;
end;
  1. In case we need to create a token, we call NtCreateToken. If there are no groups, we create a single group with the Primary SID in this group :
objectpascal Download
procedure CreateToken;
var
  ExpirationTime : LARGE_INTEGER;
  User : JwaWindows.TOKEN_USER;
  TokenSource : TOKEN_SOURCE;
  PrimaryGroup : JwaWindows.TOKEN_PRIMARY_GROUP;
  EmptyTokenGroups : JwaWindows.TTokenGroups;
begin
  ExpirationTime.QuadPart := 0;

  User.User.Sid := PrimarySID;
  User.User.Attributes := 0;

  TokenSource.SourceName := 'AUTOLGON';
  Win32Check(AllocateLocallyUniqueId(TokenSource.SourceIdentifier));

  Result^.LogonId := SYSTEM_LUID;

  PrimaryGroup.PrimaryGroup := PrimarySID;

  if (nil = TokenGroups) then
  begin
    EmptyTokenGroups.GroupCount := 1;

    EmptyTokenGroups.Groups[0].Sid := PrimarySID;
    EmptyTokenGroups.Groups[0].Attributes := SE_GROUP_MANDATORY or SE_GROUP_ENABLED or SE_GROUP_ENABLED_BY_DEFAULT or SE_GROUP_LOGON_ID;

    TokenGroups := @EmptyTokenGroups;
  end;

  EnableToken(SE_CREATE_TOKEN_NAME);
  NtCheck(
    NtCreateToken(
      @Result^.UserToken,
      TOKEN_ALL_ACCESS,
      nil,
      TokenPrimary,
      @Result^.LogonId,
      @ExpirationTime,
      @User,
      TokenGroups,
      Privs,
      nil,
      @PrimaryGroup,
      nil,
      @TokenSource
    ));

  try
    Profile := AllocEmptyProfile(ProfileLength);
  except
    CloseHandle(Result^.UserToken);
    Raise;
  end;

end;
  1. In case we want to use a “current” system token, we need to do some adjustments with it - we need to add the group with SE_GROUP_LOGON_ID flag to it:
objectpascal Download
function GetFromToken(TokenHandle : THandle; Info : TOKEN_INFORMATION_CLASS) : Pointer;
var
  Res : DWORD;
begin
  Result := nil;
  GetTokenInformation(TokenHandle, info, nil, 0, Res);
  if GetLastError = ERROR_INSUFFICIENT_BUFFER then
  begin
     Result := GetMemory(Res);
     Win32Check(GetTokenInformation(TokenHandle, info, Result, Res, Res));
  end else
    RaiseLastOSError;
end;

procedure AsSystem;
var
  SystemToken : THandle;

  Statistics : PTokenStatistics;
  User : JwaWinNT.PTokenUser;
  Priviledges : JwaWindows.PTokenPrivileges;
  Owner : PTokenOwner;
  Groups : JwaWindows.PTokenGroups;
  PrimaryGroup : PTokenPrimaryGroup;
  DefaultDacl : PTokenDefaultDacl;
  TokenSrc : PTokenSource;

  TokenNewGroups : JwaWindows.PTokenGroups;
  I : Integer;
begin
  Win32Check(OpenProcessToken(GetCurrentProcess, TOKEN_ALL_ACCESS, SystemToken));

  EnableToken(SE_CREATE_TOKEN_NAME);

  Statistics := nil;
  User := nil;
  Priviledges := nil;
  Owner := nil;
  PrimaryGroup := nil;
  DefaultDacl := nil;
  TokenSrc := nil;
  Groups := nil;
  TokenNewGroups := nil;

  try
    Statistics := GetFromToken(SystemToken, TokenStatistics);
    User := GetFromToken(SystemToken, TokenUser);
    Priviledges := GetFromToken(SystemToken, TokenPrivileges);
    Owner := GetFromToken(SystemToken, TokenOwner);
    PrimaryGroup := GetFromToken(SystemToken, TokenPrimaryGroup);
    DefaultDacl := GetFromToken(SystemToken, TokenDefaultDacl);
    TokenSrc := GetFromToken(SystemToken, TokenSource);
    Groups := GetFromToken(SystemToken, JwaWinNT.TokenGroups);

    TokenNewGroups := GetMemory(SizeOf(TokenNewGroups^) + SizeOf(TokenNewGroups^.Groups) * Groups^.GroupCount);

    TokenNewGroups^.GroupCount := Groups^.GroupCount + 1;

    for I := 0 to Groups^.GroupCount - 1 do
      TokenNewGroups^.Groups[I] := Groups^.Groups[I];

    TokenNewGroups^.Groups[Groups^.GroupCount].Sid := Pointer(User^.User.Sid);
    TokenNewGroups^.Groups[Groups^.GroupCount].Attributes := SE_GROUP_MANDATORY or SE_GROUP_ENABLED or SE_GROUP_ENABLED_BY_DEFAULT or SE_GROUP_LOGON_ID;

    NtCheck(
      NtCreateToken(
        @Result^.UserToken,
        TOKEN_ALL_ACCESS,
        nil,
        Statistics^.TokenType,
        @Statistics^.AuthenticationId,
        @Statistics^.ExpirationTime,
        User,
        TokenNewGroups,
        Priviledges,
        Owner,
        PrimaryGroup,
        DefaultDacl,
        TokenSrc
      ));

  finally
    if (Statistics <> nil) then
      FreeMemory(Statistics);

    if (User <> nil) then
      FreeMemory(User);

    if (Priviledges <> nil) then
      FreeMemory(Priviledges);

    if (Owner <> nil) then
      FreeMemory(Owner);

    If (PrimaryGroup <> nil) then
      FreeMemory(PrimaryGroup);

    if (DefaultDacl <> nil) then
      FreeMemory(DefaultDacl);

    if (TokenSrc <> nil) then
      FreeMemory(TokenSrc);

    if (Groups <> nil) then
      FreeMemory(Groups);

    if (TokenNewGroups <> nil) then
      FreeMemory(TokenNewGroups);

  end;

  try
    try
      Profile := AllocEmptyProfile(ProfileLength);
    except
      CloseHandle(Result^.UserToken);
      Raise;
    end;
  finally
    CloseHandle(SystemToken);
  end;
end;
  1. Now we can create a buffer:
objectpascal Download
function CreateAutoLogonBuffer32(
  const UserName, Domain, Password : WideString;
  WorkingMode : TWorkingMode;
  const PrimarySID : JwaWindows.PSID;  TokenGroups : JWaWindows.PTokenGroups;
  const Privs : JwaWindows.PTokenPrivileges
  ) : PAutoLogonBuffer32;
var
  Profile : PMSV1_0_INTERACTIVE_PROFILE;
  ProfileLength : DWORD;

  CurrentPosition : Pointer;
  bDoFreeTokenHandle : Boolean;

begin
  Result := GetMemory(SizeOf(Result^));
  try
    ZeroMemory(Result, SizeOf(Result^));

    case WorkingMode of
      wmNormalLogin : LoginUser;
      wmCreateToken : CreateToken;
      wmAsSystem    : AsSystem;
    end;

  //  PrintTokenSIDs(Result^.UserToken);

    bDoFreeTokenHandle := True;
    try

      if Profile^.MessageType <> MsV1_0InteractiveProfile then
      begin
        Writeln('Profile message type is not  MsV1_0InteractiveProfile, but is ', Ord(Profile^.MessageType));
        Abort;
      end;

      Result^.SourceProcessId := GetCurrentProcessId;

      CurrentPosition := @Result^.DynamicData[0];

      if Is2003 then
      begin
        Result^.Credentials.WIN2K_MODE.UserName := CopyString(UserName, Result, CurrentPosition);
        Result^.Credentials.WIN2K_MODE.Domain := CopyString(Domain, Result, CurrentPosition);
        Result^.Credentials.WIN2K_MODE.DomainLength := Length(Domain);
        Result^.Credentials.WIN2K_MODE.ProfileLength := ProfileLength;
      end else
      begin
        Result^.Credentials.WINXP_MODE.UserName := CopyString(UserName, Result, CurrentPosition);
        Result^.Credentials.WINXP_MODE.Domain := CopyString(Domain, Result, CurrentPosition);
        Result^.Credentials.WINXP_MODE.ProfileLength := ProfileLength;
      end;

      Result^.MessageType := Ord(MsV1_0InteractiveProfile);

      Result^.LogonCount := Profile^.LogonCount;
      Result^.BadPasswordCount := Profile^.BadPasswordCount;

      Result^.ProfileLogonTime := Profile^.LogonTime;
      Result^.LogoffTime := Profile^.LogoffTime;
      Result^.KickOffTime := Profile^.KickOffTime;
      Result^.PasswordLastSet := Profile^.PasswordLastSet;
      Result^.PasswordCanChange := Profile^.PasswordCanChange;
      Result^.PasswordMustChange := Profile^.PasswordMustChange;

      CopyUnicodeString(@Result^.LogonScript, @Profile^.LogonScript, Result, CurrentPosition);
      CopyUnicodeString(@Result^.HomeDirectory, @Profile^.HomeDirectory, Result, CurrentPosition);
      CopyUnicodeString(@Result^.FullName, @Profile^.FullName, Result, CurrentPosition);
      CopyUnicodeString(@Result^.ProfilePath, @Profile^.ProfilePath, Result, CurrentPosition);
      CopyUnicodeString(@Result^.HomeDirectoryDrive, @Profile^.HomeDirectoryDrive, Result, CurrentPosition);
      CopyUnicodeString(@Result^.LogonServer, @Profile^.LogonServer, Result, CurrentPosition);

      Result^.UserFlags := Profile^.UserFlags;

      Result^.LogonTime := GetCurrentTime();

      Result^.SmartCardLogon := False;

      Result^.TotalLength.Length := Cardinal(CurrentPosition) - Cardinal(Result);

      bDoFreeTokenHandle := False;
    finally
      if (bDoFreeTokenHandle) then
        CloseHandle(Result^.UserToken);

      LsaFreeReturnBuffer(Profile);
    end;
  except
    FreeMemory(Result);
    Raise;
  end;
end;
  1. In case of x64 systems we need to copy the whole structure into 64 bit structure:
objectpascal Download
type
  PWSTR_64 = record
    Buffer : PWSTR;
    Alignment : DWORD;
  end;

  THANDLE_64 = record
    Handle : THandle;
    Alignment : DWORD;
  end;

  PUNICODE_STRING_64 = ^UNICODE_STRING_64;
  UNICODE_STRING_64 = record
    Length: USHORT;
    MaximumLength: USHORT;
    Alignment : DWORD;
    Buffer: PWSTR_64;
  end;

  ULONG_64 = record
    Value : DWORD;
    Alignment : DWORD;
  end;

  QUOTA_LIMITS_64 = record
    PagedPoolLimit: ULONG_64;
    NonPagedPoolLimit: ULONG_64;
    MinimumWorkingSetSize: ULONG_64;
    MaximumWorkingSetSize: ULONG_64;
    PagefileLimit: ULONG_64;
    TimeLimit: LARGE_INTEGER;
  end;

  PAutoLogonBuffer64 = ^TAutoLogonBuffer64;
  TAutoLogonBuffer64 = record
    TotalLength     : TLengthBuffer;
    SourceProcessId : DWORD;
    UserToken       : THANDLE_64;
    LogonId         : LUID;

    Quotas          : QUOTA_LIMITS_64;

    Credentials : record
      case Boolean of
        True : (WINXP_MODE : record
                 UserName        : PWSTR_64;
                 Domain          : PWSTR_64;

                 ProfileLength   : ULONG;
                 Unknown1        : DWORD;
               end);
        False : (WIN2K_MODE : record
                 UserName        : PWSTR_64;
                 DomainLength    : ULONG_64;
                 Domain          : PWSTR_64;
                 ProfileLength   : ULONG_64;
              end);
    end;

    MessageType     : DWORD;
    LogonCount      : USHORT;
    BadPasswordCount: USHORT;

    ProfileLogonTime: LARGE_INTEGER;
    LogoffTime      : LARGE_INTEGER;
    KickOffTime     : LARGE_INTEGER;
    PasswordLastSet : LARGE_INTEGER;
    PasswordCanChange: LARGE_INTEGER;
    PasswordMustChange: LARGE_INTEGER;

    LogonScript     : UNICODE_STRING_64;
    HomeDirectory   : UNICODE_STRING_64;
    FullName        : UNICODE_STRING_64;
    ProfilePath     : UNICODE_STRING_64;
    HomeDirectoryDrive : UNICODE_STRING_64;
    LogonServer     : UNICODE_STRING_64;

    UserFlags       : ULONG_64;
    LogonTime       : LARGE_INTEGER;
    SmartCardLogon  : BOOL;
    PrivateDataLen  : ULONG;
    PrivateData     : Pointer;
    Unknown2        : DWORD;

    DynamicData     : array [0..7981] of BYTE;
  end;

function CreateAutoLogonBuffer64(const Source : PAutoLogonBuffer32) : PAutoLogonBuffer64;
var
  DynamicBufferDelta : Integer;
begin
  Result := GetMemory(SizeOf(Result^));

  try
    ZeroMemory(Result, SizeOf(Result^));

    DynamicBufferDelta := ((Cardinal(@Result^.DynamicData) - Cardinal(Result) - (Cardinal(@Source^.DynamicData) - Cardinal(Source))));
    Result^.TotalLength.Length := Source^.TotalLength.Length + Cardinal(DynamicBufferDelta);

    Result^.SourceProcessId := Source^.SourceProcessId;

    Result^.UserToken.Handle := Source^.UserToken;
    Result^.LogonId := Source^.LogonId;

    Result^.Quotas.PagedPoolLimit.Value         := Source^.Quotas.PagedPoolLimit;
    Result^.Quotas.NonPagedPoolLimit.Value      := Source^.Quotas.NonPagedPoolLimit;
    Result^.Quotas.MinimumWorkingSetSize.Value  := Source^.Quotas.MinimumWorkingSetSize;
    Result^.Quotas.MaximumWorkingSetSize.Value  := Source^.Quotas.MaximumWorkingSetSize;
    Result^.Quotas.PagefileLimit.Value          := Source^.Quotas.PagefileLimit;
    Result^.Quotas.TimeLimit                    := Source^.Quotas.TimeLimit;

    // In theory we need to do a check if it's windows 2003/xp x64, but actually there is no public version of XP (5.1) x64, so
    // we can just skip it.
    Result^.Credentials.WIN2K_MODE.UserName.Buffer    := AdjustBuffer(Source^.Credentials.WIN2K_MODE.UserName, DynamicBufferDelta);
    Result^.Credentials.WIN2K_MODE.DomainLength.Value := Source^.Credentials.WIN2K_MODE.DomainLength;
    Result^.Credentials.WIN2K_MODE.Domain.Buffer      := AdjustBuffer(Source^.Credentials.WIN2K_MODE.Domain, DynamicBufferDelta);
    Result^.Credentials.WIN2K_MODE.ProfileLength.Value:= Source^.Credentials.WIN2K_MODE.ProfileLength;

    Result^.MessageType       := Source^.MessageType;
    Result^.LogonCount        := Source^.LogonCount;
    Result^.BadPasswordCount  := Source^.BadPasswordCount;

    Result^.ProfileLogonTime   := Source^.ProfileLogonTime;
    Result^.LogoffTime         := Source^.LogoffTime;
    Result^.KickOffTime        := Source^.KickOffTime;
    Result^.PasswordLastSet    := Source^.PasswordLastSet;
    Result^.PasswordCanChange  := Source^.PasswordCanChange;
    Result^.PasswordMustChange := Source^.PasswordMustChange;

    Result^.LogonScript       := AdjustUnicodeString(Source^.LogonScript, DynamicBufferDelta);
    Result^.HomeDirectory     := AdjustUnicodeString(Source^.HomeDirectory, DynamicBufferDelta);
    Result^.FullName          := AdjustUnicodeString(Source^.FullName, DynamicBufferDelta);
    Result^.ProfilePath       := AdjustUnicodeString(Source^.ProfilePath, DynamicBufferDelta);
    Result^.HomeDirectoryDrive:= AdjustUnicodeString(Source^.HomeDirectoryDrive, DynamicBufferDelta);
    Result^.LogonServer       := AdjustUnicodeString(Source^.LogonServer, DynamicBufferDelta);

    Result^.UserFlags.Value   := Source^.UserFlags;
    Result^.LogonTime         := Source^.LogonTime;
    Result^.SmartCardLogon    := Source^.SmartCardLogon;
    Result^.PrivateDataLen    := Source^.PrivateDataLen;
    Result^.PrivateData       := AdjustBuffer(Source^.PrivateData, DynamicBufferDelta);
    Result^.Unknown2          := Source^.Unknown2;

    CopyMemory(@Result^.DynamicData, @Source^.DynamicData, Source^.TotalLength.Length - (SizeOf(Source^)  - SizeOf(Source^.DynamicData)));
  except
    FreeMemory(Result);
    Raise;
  end;
end;

function CreateAutoLogonBuffer(
  const UserName, Domain, Password : WideString;
  WorkingMode : TWorkingMode;
  const PrimarySID : JwaWindows.PSID;  TokenGroups : JWaWindows.PTokenGroups;
  const Privs : JwaWindows.PTokenPrivileges
 ) : PLengthBuffer;
begin
  Result := @CreateAutoLogonBuffer32(UserName, Domain, Password, WorkingMode, PrimarySID, TokenGroups, Privs)^.TotalLength;
  if (IsWow64) then
  try
    Result := @CreateAutoLogonBuffer64(PAutoLogonBuffer32(Result))^.TotalLength;
  except
    FreeMemory(Result);
    Raise;
  end;
end;

procedure FreeAutoLogonBuffer(var Buffer : PLengthBuffer);
begin
  if (Buffer <> nil) then
  begin
    if (IsWow64) then
      CloseHandle(PAutoLogonBuffer64(Buffer)^.UserToken.Handle)
    else
      CloseHandle(PAutoLogonBuffer32(Buffer)^.UserToken);

    FreeMemory(Buffer);
    Buffer := nil;
  end;
end;
  1. Now we need to open the pipe, send the WLX_SAS_TYPE_AUTHENTICATED SAS to Winlogon and write the data to the pipe:
objectpascal Download
function SendCredentialThreadProc(Parameter: PLengthBuffer): Integer;
var
  PipeHandle : THandle;
begin
  Result := 0;

  try
    PipeHandle := OpenPipe;
    try
      DoLogonNotifyMessage(WPARAM_AUTHENTICATED, 0);
      ProcessPipe(PipeHandle, Parameter);
    finally
      CloseHandle(PipeHandle);
    end;
  except
    on E : EOSError do
      Result := EOsError(E).ErrorCode;
    on E : Exception do
      Result := ERROR_GEN_FAILURE;
  end;

  EndThread(Result);
end;
  1. So, the main procedure will look like this:
objectpascal Download
procedure DoLogon(const UserName, Domain, Password : WideString;
    WorkingMode : TWorkingMode;
    const PrimarySIDStr : String; const AdditionalSIDS : TStringDynArray;
    const PriviledgesList : TStringDynArray;
    Timeout : Cardinal);
var
  Buffer : PLengthBuffer;
  TokenGroups : JWAWindows.PTokenGroups;
  PrimarySID : JWaWindows.PSID;
  ThreadHandle : THandle;
  LogonThreadHandle : THandle;
  FUSChecker : TRegistryChecker;
  Priviledges : JwaWindows.PTokenPrivileges;
begin
  CheckLoggedOnUser;
  CheckScreenSaver;
  TryToConnectToPipe;

  FUSChecker := TRegistryChecker.Create(FUSKey, False);
  try
    PrimarySID := CreateSID(PrimarySIDStr);
    try
      TokenGroups := GetAdditionalSIDS(AdditionalSIDS);
      try
        Priviledges := CreatePriviledges(PriviledgesList);
        try
          Buffer := CreateAutoLogonBuffer(UserName, Domain, Password, WorkingMode, PrimarySID, TokenGroups, Priviledges);
          try
            LogonThreadHandle := WaitForLogonAsync;
            try
              ThreadHandle := SendCredentialsAsync(Buffer);
              try
                WaitForThreads(ThreadHandle, LogonThreadHandle, Timeout);
              finally
                CloseHandle(ThreadHandle);
              end;
            finally
              CloseHandle(LogonThreadHandle);
            end;
          finally
            FreeAutoLogonBuffer(Buffer);
          end;
        finally
          FreePriviledges(Priviledges);
        end;
      finally
        FreeAdditionalSIDs(TokenGroups);
      end
    finally
      _FreeSID(PrimarySID);
    end;
  finally
    FreeAndNil(FUSChecker);
  end;

end;

AutoLogonXP-1.0.zip

Please note that this sample is free for non-commercial use only. If you require to use it, or it’s source code in your business projects, please send a request using contact form.