I've created two simple helpers to make TWriter.WriteProperties() and TReader.ReadProperty() public, based on this example.
It works fine when saving the persistent things in binary object format but fails when converting to text.
Any idea about how to make this working (text format)? I don't want to rewrite the conversion routines just for this.
The problem is illustrated in this simple console program:
program tfiler_persistent_hack;
{$MODE DELPHI}
uses
classes, sysutils;
type
TReaderEx = class helper for TReader
procedure ReadPersistent(aValue: TPersistent);
end;
TWriterEx = class helper for TWriter
procedure WritePersistent(aValue: TPersistent);
end;
TTest = class(TComponent)
private
fList: TStringList;
procedure ListFromReader(aReader: TReader);
procedure ListToWriter(aWriter: TWriter);
protected
procedure defineProperties(aFiler: TFiler); override;
public
constructor create(aOwner: TComponent); override;
destructor destroy; override;
property list: TStringList read fList;
end;
procedure TReaderEx.ReadPersistent(aValue: TPersistent);
begin
ReadListBegin;
while not EndOfList do ReadProperty(aValue);
ReadListEnd;
end;
procedure TWriterEx.WritePersistent(aValue: TPersistent);
begin
WriteListBegin;
WriteProperties(aValue);
WriteListEnd;
end;
procedure TTest.ListFromReader(aReader: TReader);
begin
aReader.ReadPersistent(fList);
end;
procedure TTest.ListToWriter(aWriter: TWriter);
begin
aWriter.WritePersistent(fList);
end;
procedure TTest.defineProperties(aFiler: TFiler);
begin
aFiler.defineProperty('the_list_id_liketosave_without_publising', ListFromReader, ListToWriter, true);
end;
constructor TTest.create(aOwner: TComponent);
begin
inherited;
fList := TStringList.Create;
end;
destructor TTest.destroy;
begin
fList.Free;
inherited;
end;
var
test: TTest;
str1, str2: TMemoryStream;
const
itm1 = 'aqwzsx';
itm2 = 'edcrfv';
begin
test := TTest.create(nil);
str1 := TMemoryStream.Create;
str2 := TMemoryStream.Create;
try
// bin format passes
test.list.add(itm1);
test.list.add(itm2);
str1.WriteComponent(test);
str1.SaveToFile('bin.txt');
str1.Clear;
test.list.clear;
str1.LoadFromFile('bin.txt');
str1.ReadComponent(test);
assert( test.list.strings[0] = itm1);
assert( test.list.strings[1] = itm2);
writeln('bin: zero killed');
// text format does not
str1.Clear;
test.list.clear;
test.list.add(itm1);
test.list.add(itm2);
str1.WriteComponent(test);
str1.Position := 0;
try
ObjectBinaryToText(str1, str2);
except
writeln('ouch, it hurts (1)');
exit;
end;
str2.SaveToFile('text.txt');
str1.Clear;
str2.Clear;
test.list.clear;
str1.LoadFromFile('text.txt');
try
ObjectTextToBinary(str1, str2);
except
writeln('ouch, it hurts (2)');
exit;
end;
str2.Position := 0;
str2.ReadComponent(test);
assert( test.list.strings[0] = itm1);
assert( test.list.strings[1] = itm2);
writeln('text: zero killed');
finally
sysutils.DeleteFile('bin.txt');
sysutils.DeleteFile('text.txt');
test.Free;
str1.Free;
str2.Free;
readln;
end;
end.
When I run it, I get the following output:
bin: zero killed
ouch, it hurts (1)
If you make your list property published and remove the call to TFiler.DefineProperty(), everything works correctly, as expected:
TTest = class(TComponent)
private
fList: TStringList;
procedure SetList(Value: TStringList);
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
property list: TStringList read fList;
published
property the_list_id_liketosave_without_publising: TStringList read fList write SetList;
end;
Here is what its DFM binary data looks like:
54 50 46 30 05 54 54 65 73 74 00 30 74 68 65 5F : TPF0.TTest.0the_
6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
73 69 6E 67 2E 53 74 72 69 6E 67 73 01 06 06 61 : sing.Strings...a
71 77 7A 73 78 06 06 65 64 63 72 66 76 00 00 00 : qwzsx..edcrfv...
And here is the text output:
object TTest
the_list_id_liketosave_without_publising.Strings = (
'aqwzsx'
'edcrfv')
end
As you can see, there is a single string for the property name:
the_list_id_liketosave_without_publising.Strings
Internally, TStream.ReadComponent() reads that string and splits it on the . character, using RTTI to resolve the_list_id_liketosave_without_publising to the actual TStringList object, and then calling DefineProperties('Strings') on that object to let it stream its string list data, and all is fine.
ObjectBinaryToText() doesn't do that much work. In fact, after looking at the RTL source code, it turns out that ObjectBinaryToText() (at least in Delphi, but I'm sure FreePascal is the same way) DOES NOT support custom streaming via TComponent.DefineProperties() at all (it never calls DefineProperties())!. That is the root of your problem. ObjectBinaryToText() does not implement the full streaming system that ReadComponent() implements, only a subset of it.
However, in this case, everything is OK because TStringList writes its custom streaming data in a simple format that is easy for ObjectBinaryToText() to process.
When ObjectBinaryToText() reads the property name string, it writes it out as-is without parsing it in any way, then reads the next byte and processes it accordingly. TStringList uses this format:
vaList (TWriter.WriteListBegin())
vaString for each string (TWriter.WriteString())
vaNull (TWriter.WriteListEnd())
ObjectBinaryToText() recognizes those markers, so it knows that when it encounters vaList (hex 01) that it needs to read values in a loop until it reads a vaNull (hex 00), and it knows how to read a vaString (hex 06) value. So it has no trouble writing out the Strings data to the output text.
In the case of your TTest custom streaming, the DFM binary data it creates is a little bit different:
54 50 46 30 05 54 54 65 73 74 00 28 74 68 65 5F : TPF0.TTest.(the_
6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
73 69 6E 67 01 07 53 74 72 69 6E 67 73 01 06 06 : sing..Strings...
61 71 77 7A 73 78 06 06 65 64 63 72 66 76 00 00 : aqwzsx..edcrfv..
00 00 : ..
As you can see, there are two separate property name strings present:
the_list_id_liketosave_without_publising
Strings
When ObjectBinaryToText() reads the the_list_id_liketosave_without_publising string, it assumes it is the full property name and reads the next byte to determine the property's data type for reading. That byte (hex 01) is interpreted as vaList. The next byte (hex 07) is interpreted as vaIdent (aka not vaNull), so it assumes a non-empty list of subproperties is being read (which it really isn't). It tries to read a vaIdent "property", where the next byte (hex 53) is interpreted as the indent's byte length (which is it not), then it tries to read that many bytes (decimal 83) and fails.
In order to make your TTest custom streaming work correctly with ObjectBinaryToText(), you will have to produce a compatible DFM, by duplicating the same logic that TStrings.DefineProperties() implements (as its streaming methods are private and inaccessible), eg:
TTest = class(TComponent)
private
fList: TStringList;
procedure ListFromReader(aReader: TReader);
procedure ListToWriter(aWriter: TWriter);
protected
procedure DefineProperties(aFiler: TFiler); override;
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
property list: TStringList read fList;
end;
procedure TTest.ListFromReader(aReader: TReader);
begin
aReader.ReadListBegin;
fList.BeginUpdate;
try
fList.Clear;
while not aReader.EndOfList do fList.Add(aReader.ReadString);
finally
fList.EndUpdate;
end;
aReader.ReadListEnd;
end;
procedure TTest.ListToWriter(aWriter: TWriter);
var
I: Integer;
begin
aWriter.WriteListBegin;
for I := 0 to fList.Count - 1 do aWriter.WriteString(fList[I]);
aWriter.WriteListEnd;
end;
procedure TTest.DefineProperties(aFiler: TFiler);
begin
inherited;
aFiler.DefineProperty('the_list_id_liketosave_without_publising', ListFromReader, ListToWriter, fList.Count > 0);
end;
constructor TTest.Create(aOwner: TComponent);
begin
inherited;
fList := TStringList.Create;
end;
destructor TTest.Destroy;
begin
fList.Free;
inherited;
end;
Which produces this DFM binary data:
54 50 46 30 05 54 54 65 73 74 00 28 74 68 65 5F : TPF0.TTest.(the_
6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
73 69 6E 67 01 06 06 61 71 77 7A 73 78 06 06 65 : sing...aqwzsx..e
64 63 72 66 76 00 00 00 : dcrfv...
Which produces this output text:
object TTest
the_list_id_liketosave_without_publising = (
'aqwzsx'
'edcrfv')
end
That is simply the way ObjectBinaryToText() works, there is no getting around it. It is not designed for general purpose custom streaming like you are trying to implement. It is very specialized in what it can (and cannot) handle. Remember, it is designed primarily for the IDE editor to display DFMs to users, so it relies on published components using simple streaming formats. What you tried to implement is outside of its ability to parse.
What a difference a couple of bytes make, huh?
Related
I made application in Delphi and it is running on Windows Server 2019 per user basis. Those users connect with Remote Desktop to the user session (group policy) and run the application.
Is it possible to open a configuration file on a shared network map only with my application and not for example with Notepad?
More in general. What is the best way to store configuration data which in fact is secret for the user? I was thinking to put sensitive data just in the database but still it is nice no put for example server information somehwere in a config file instead of "baking" in.
This is my first post an I am aware it is between programming and server configuration. Else my translation scales seem no to get a hit for "only application open a file". My excuses if this post isnt perfect.
I see several possibilities:
1°
If you don't want the user to "see" your data, then you have to encrypt the file content. There are a lot of Delphi encryption/decryption libraries. I suggest you start with Delphi Encryption Compendium which is available for free on GitHub.
You can store the data in an in-memory structure such as an XML or JSON (Delphi has built-in routine to handle both XML and JSON). Before writing to disc, you encrypt it and after having reloaded the encrypted file, you decrypt it before accessing it the standard way.
2° Use a file accessible from another account and make your program impersonate that account when access to the file is required.
I wrote some code for use to ease and demo that way. I created a class TImpersonateUser having two methods Logon and Logoff which will make the program connect to a given user account and disconnect from it.
To test, first logon using another account and create a file somewhere, for example in the documents. Then logon back to your normal user code and launch the demo program (code below). Fill username, domain and password (For domain, "." will authenticate only on local computer). Fill the filename with complete path of the file you created previously. The click "file access". It should answer "file not found". Then click "Impersonate" and again "File Access". Now you should have access to the file in the other account. Click on "Revert to self" and try again "File Access" it should fail again.
In summary, for your question, the data the user cannot see must be created under another account and you application impersonate that other account when it needs to access the data. Don't forget to somehow hide username and password in your program.
Note: Once you get a handle (file or stream opened), you can RevertToSelf and still use the handle (or stream). It keeps the security context (the account used) until closed. This means you can call Logon before opening the file, call logoff right after opening (or failure of opening) and continue to access the file.
EDIT: I wrote a blog post with more code.
unit ImpersonateUser;
interface
uses
Winapi.Windows, System.Classes;
const
LOGON32_LOGON_NEW_CREDENTIALS = 9; // Missing in Delphi
type
TImpersonateUser = class(TComponent)
protected
FUserToken : THandle;
FErrorCode : DWORD;
public
destructor Destroy; override;
function Logon(const UserName : String;
const Domain : String;
const Password : String) : Boolean;
procedure Logoff();
property ErrorCode : DWORD read FErrorCode;
end;
implementation
{ TImpersonateUser }
destructor TImpersonateUser.Destroy;
begin
if FUserToken <> 0 then begin
CloseHandle(FUserToken);
FUserToken := 0;
end;
inherited Destroy;
end;
procedure TImpersonateUser.Logoff;
begin
if FUserToken <> 0 then begin
RevertToSelf(); // Revert to our user
CloseHandle(FUserToken);
FUserToken := 0;
end;
end;
function TImpersonateUser.Logon(
const UserName : String;
const Domain : String;
const Password : String): Boolean;
var
LoggedOn : Boolean;
begin
Result := FALSE;
if FUserToken <> 0 then
Logoff();
if UserName = '' then begin // Must at least provide a user name
FErrorCode := ERROR_BAD_ARGUMENTS;
Exit;
end;
if Domain <> '' then
LoggedOn := LogonUser(PChar(UserName),
PChar(Domain),
PChar(Password),
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
FUserToken)
else
LoggedOn := LogonUser(PChar(UserName),
PChar(Domain),
PChar(Password),
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_WINNT50,
FUserToken);
if not LoggedOn then begin
FErrorCode := GetLastError();
Exit;
end;
if not ImpersonateLoggedOnUser(FUserToken) then begin
FErrorCode := GetLastError();
Exit;
end;
FErrorCode := S_OK;
Result := TRUE;
end;
end.
Simple demo:
unit ImpersonateUserDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
ImpersonateUser;
type
TImpersonateUserMainForm = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
UserNameEdit: TEdit;
DomainEdit: TEdit;
PasswordEdit: TEdit;
ImpersonateButton: TButton;
Label4: TLabel;
FileNameEdit: TEdit;
RevertToSelfButton: TButton;
FileAccessButton: TButton;
procedure FileAccessButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ImpersonateButtonClick(Sender: TObject);
procedure RevertToSelfButtonClick(Sender: TObject);
private
FImpersonate : TImpersonateUser;
end;
var
ImpersonateUserMainForm: TImpersonateUserMainForm;
implementation
{$R *.dfm}
procedure TImpersonateUserMainForm.FileAccessButtonClick(Sender: TObject);
var
Stream : TFileStream;
begin
try
if not FileExists(FileNameEdit.Text) then
ShowMessage('File not found')
else begin
Stream := TFileStream.Create(FileNameEdit.Text, fmOpenRead);
try
ShowMessage('File opened');
finally
Stream.Free;
end;
end;
except
on E:Exception do
ShowMessage(E.Classname + ': ' + E.Message);
end;
end;
procedure TImpersonateUserMainForm.FormCreate(Sender: TObject);
begin
UserNameEdit.Text := 'YourUsername';
DomainEdit.Text := '.';
PasswordEdit.Text := 'YourPassword';
FilenameEdit.Text := 'C:\Users\AnotherUser\Documents\HelloWorld.txt';
FImpersonate := TImpersonateUser.Create(Self);
end;
procedure TImpersonateUserMainForm.ImpersonateButtonClick(Sender: TObject);
begin
if not FImpersonate.Logon(UserNameEdit.Text,
DomainEdit.Text,
PasswordEdit.Text) then begin
ShowMessage(Format('Failed with error 0x%X', [FImpersonate.ErrorCode]));
end
else
ShowMessage('Logon OK');
end;
procedure TImpersonateUserMainForm.RevertToSelfButtonClick(Sender: TObject);
begin
FImpersonate.Logoff;
ShowMessage('Reverted to self');
end;
end.
The DFM file:
object ImpersonateUserMainForm: TImpersonateUserMainForm
Left = 0
Top = 0
Caption = 'ImpersonateUserMainForm'
ClientHeight = 142
ClientWidth = 331
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 16
Top = 20
Width = 49
Height = 13
Caption = 'UserName'
end
object Label2: TLabel
Left = 16
Top = 48
Width = 35
Height = 13
Caption = 'Domain'
end
object Label3: TLabel
Left = 12
Top = 76
Width = 46
Height = 13
Caption = 'Password'
end
object Label4: TLabel
Left = 16
Top = 104
Width = 16
Height = 13
Caption = 'File'
end
object UserNameEdit: TEdit
Left = 80
Top = 16
Width = 121
Height = 21
TabOrder = 0
Text = 'UserNameEdit'
end
object DomainEdit: TEdit
Left = 80
Top = 44
Width = 121
Height = 21
TabOrder = 1
Text = 'DomainEdit'
end
object PasswordEdit: TEdit
Left = 80
Top = 72
Width = 121
Height = 21
TabOrder = 2
Text = 'PasswordEdit'
end
object ImpersonateButton: TButton
Left = 232
Top = 14
Width = 75
Height = 25
Caption = 'Impersonate'
TabOrder = 3
OnClick = ImpersonateButtonClick
end
object FileNameEdit: TEdit
Left = 80
Top = 99
Width = 121
Height = 21
TabOrder = 4
Text = 'FileNameEdit'
end
object RevertToSelfButton: TButton
Left = 232
Top = 45
Width = 75
Height = 25
Caption = 'Revert to self'
TabOrder = 5
OnClick = RevertToSelfButtonClick
end
object FileAccessButton: TButton
Left = 232
Top = 76
Width = 75
Height = 25
Caption = 'File access'
TabOrder = 6
OnClick = FileAccessButtonClick
end
end
I am reading UDP packets using the TIdUDPServer.OnUDPRead event. Embedded in each packet is an 8-byte QUint64, which represents a radio frequency. I am trying to read that frequency.
All attempts using BytesToInt64() are resulting in erroneous results. I have verified the data is correct by reading the hex values with Wireshark and using a web-based hex-to-decimal converter.
I know the frequency in the packet data starts at position 23 and is 8 bytes long.
procedure TWSJTxUDPClient.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
FreqInt: Int64;
begin
FreqInt := BytesToInt64(AData,23);
LabelFreq.Caption := IntToStr(FreqInt);
end;
Here is the data on the wire in hex notation, starting at position 23:
00 00 00 00 00 6b f0 d0
Which represents a freq of 7074000 hz.
My code above is resulting in a freq value of 58811137507983360.
I have also confirmed the hex data by reading each byte with the Ord() function on a string representing the frequency.
FreqStr := BytesToString(Adata,23,8);
Int8 := Ord(FreqStr[8]);
Int8 is now = 208 - the correct value for 0xd0. Did same for bytes 5, 6, & 7. Confirmed.
So I know I am missing something basic.
You are not taking endian into account.
Decimal 7074000 is hex 0x6BF0D0. Decimal 58811137507983360 is hex 0xD0F06B00000000. See the similarity? Their bytes are in opposite order.
The bytes in your packet are in network byte order (big endian) but your machine uses little endian instead. You need to swap the bytes of the Int64. You can use Indy's GStack.NetworkToHost() method for that:
procedure TWSJTxUDPClient.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
var
FreqInt: Int64;
begin
FreqInt := BytesToInt64(AData, 23);
FreqInt := Int64(GStack.NetworkToHost(UInt64(FreqInt)));
LabelFreq.Caption := IntToStr(FreqInt);
end;
I just noticed something quite interesting in a program I'm writing. I have a simple procedure that populates a TStringlist with objects of type x.
I added a breakpoint as I was tracing an issue and stumbled across this ans was hoping someone might be able to explain why it is happening, or link to a relevant document as I couldn't find anything.
My loop goes from 0 - 11. The pointer that I'm using was initialised in the loop by for nPtr := 0 but when the program was run the nPtr var was going from 12 down to 1. I then initialised the var outside the loop as shown in the code snippet but the same thing happened. The variable is used nowhere else in the unit.
I asked one of the guys I worked with who said it was due to Delphi optimisation but I'd like to know why and how it decides which loop should be affected.
Thanks for any help.
Code:
procedure TUnit.ProcedureName;
var
nPtr : Integer;
obj : TObject;
begin
nPtr:=0;//added later
for nPtr := 0 to 11 do
begin
obj := TObject.Create(Self);
slPeriodList.AddObject('X', obj);
end;
end;
The optimization is only possible if the loop body does not refer to the loop variable. In that case, if the lower bound of the loop is zero, then the compiler will reverse the loop.
If the loop variable is never referenced by the loop body then the compiler is justified in implementing the loop however it pleases. All it is required to do is execute the loop body as many times as is mandated by the loop bounds. Indeed, the compiler would be perfectly justified in optimizing away the loop variable.
Consider this program:
{$APPTYPE CONSOLE}
procedure Test1;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(0);
end;
procedure Test2;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(i);
end;
begin
Test1;
Test2;
end.
The body of Test1 is compiled to this code by XE7, 32 bit Windows compiler, with release options:
Project1.dpr.9: for i := 0 to 11 do
00405249 BB0C000000 mov ebx,$0000000c
Project1.dpr.10: Writeln(0);
0040524E A114784000 mov eax,[$00407814]
00405253 33D2 xor edx,edx
00405255 E8FAE4FFFF call #Write0Long
0040525A E8D5E7FFFF call #WriteLn
0040525F E800DBFFFF call #_IOTest
Project1.dpr.9: for i := 0 to 11 do
00405264 4B dec ebx
00405265 75E7 jnz $0040524e
The compiler is running the loop downwards, as can be seen by the use of dec. Notice that the test for loop termination is performed with jnz with no need for a cmp. That is because dec performs an implicit compare against zero.
The documentation for dec says the following:
Flags Affected
The CF flag is not affected. The OF, SF, ZF, AF, and PF flags are set
according to the result.
The ZF flag is set if and only if the result of the dec instruction is zero. And the ZF is what determines whether or not jnz branches.
The code emitted for Test2 is:
Project1.dpr.17: for i := 0 to 11 do
0040526D 33DB xor ebx,ebx
Project1.dpr.18: Writeln(i);
0040526F A114784000 mov eax,[$00407814]
00405274 8BD3 mov edx,ebx
00405276 E8D9E4FFFF call #Write0Long
0040527B E8B4E7FFFF call #WriteLn
00405280 E8DFDAFFFF call #_IOTest
00405285 43 inc ebx
Project1.dpr.17: for i := 0 to 11 do
00405286 83FB0C cmp ebx,$0c
00405289 75E4 jnz $0040526f
Note that the loop variable is increasing, and we now have an extra cmp instruction, executed on every loop iteration.
It is perhaps interesting to note that the 64 bit Windows compiler does not include this optimization. For Test1 it produces this:
Project1.dpr.9: for i := 0 to 11 do
00000000004083A5 4833DB xor rbx,rbx
Project1.dpr.10: Writeln(0);
00000000004083A8 488B0D01220000 mov rcx,[rel $00002201]
00000000004083AF 4833D2 xor rdx,rdx
00000000004083B2 E839C3FFFF call #Write0Long
00000000004083B7 4889C1 mov rcx,rax
00000000004083BA E851C7FFFF call #WriteLn
00000000004083BF E86CB4FFFF call #_IOTest
00000000004083C4 83C301 add ebx,$01
Project1.dpr.9: for i := 0 to 11 do
00000000004083C7 83FB0C cmp ebx,$0c
00000000004083CA 75DC jnz Test1 + $8
I'm not sure why this optimization has not been implemented in the 64 bit compiler. My guess would be that the optimization has negligible effect in real world cases and the designers chose not to expend effort implementing it for the 64 bit compiler.
I wrote the following lines in pascal:
procedure editor;
begin
clrscr;
frame;
levframe;
assign (level,'level.dat');
rewrite(level);
for i:=1 to 600 do write(level,'0');
for i:=1 to 30 do for j:=1 to 20 do levmem(i,j):='0';
(The variables are declarated)
The translator says:
Lp1.pas(53,43) Error: Illegal expression
Lp1.pas(53,43) Fatal: Syntax error, ";" expected but "(" found
Question: why doesn't it work? (53,43) is in the last line, at 'levmem(i,j)'. Thanks for answers.
Assuming levmem is declared as a 2D array, the syntax should be:
for i:=1 to 30 do for j:=1 to 20 do levmem[i][j]:='0';
^^^^^^
I try to connect a server though SSH using Ruby, but I have an Net::SSH::AuthenticationFailed error. The problem is that it works in PHP.
Here's my ruby code:
require 'rubygems'
require 'net/ssh'
include Net
domain = 'ks2.so.d-sites.com' # insert IP address or domain name here
begin
Net::SSH.start( domain, 'webistrano', :verbose => :debug, :keys => ['/home/webistrano/.ssh/id_rsa', '/home/webistrano/.ssh/id_rsa.pub'], :port => 22 ) do |ssh|
print "connected"
end
rescue Net::SSH::HostKeyMismatch => e
puts "remembering new key: #{e.fingerprint}"
e.remember_host!
retry
end
And I get this errors:
D, [2012-03-06T20:40:02.974862 #24165] DEBUG -- net.ssh.transport.session[3f8c5d3a06a0]: establishing connection to ks2.so.d-sites.com:22
D, [2012-03-06T20:40:02.975912 #24165] DEBUG -- net.ssh.transport.session[3f8c5d3a06a0]: connection established
I, [2012-03-06T20:40:02.976039 #24165] INFO -- net.ssh.transport.server_version[3f8c5d39edc8]: negotiating protocol version
D, [2012-03-06T20:40:02.981160 #24165] DEBUG -- net.ssh.transport.server_version[3f8c5d39edc8]: remote is `SSH-2.0-OpenSSH_5.8p1-hpn13v10'
D, [2012-03-06T20:40:02.981231 #24165] DEBUG -- net.ssh.transport.server_version[3f8c5d39edc8]: local is `SSH-2.0-Ruby/Net::SSH_2.3.0 x86_64-linux'
D, [2012-03-06T20:40:02.982272 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: read 856 bytes
D, [2012-03-06T20:40:02.982418 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: received packet nr 0 type 20 len 852
I, [2012-03-06T20:40:02.982517 #24165] INFO -- net.ssh.transport.algorithms[3f8c5d39e418]: got KEXINIT from server
I, [2012-03-06T20:40:02.982690 #24165] INFO -- net.ssh.transport.algorithms[3f8c5d39e418]: sending KEXINIT
D, [2012-03-06T20:40:02.982883 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: queueing packet nr 0 type 20 len 716
D, [2012-03-06T20:40:02.982960 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: sent 720 bytes
I, [2012-03-06T20:40:02.983003 #24165] INFO -- net.ssh.transport.algorithms[3f8c5d39e418]: negotiating algorithms
D, [2012-03-06T20:40:02.983161 #24165] DEBUG -- net.ssh.transport.algorithms[3f8c5d39e418]: negotiated:
* kex: diffie-hellman-group-exchange-sha1
* host_key: ssh-rsa
* encryption_server: aes128-cbc
* encryption_client: aes128-cbc
* hmac_client: hmac-sha1
* hmac_server: hmac-sha1
* compression_client: none
* compression_server: none
* language_client:
* language_server:
D, [2012-03-06T20:40:02.983207 #24165] DEBUG -- net.ssh.transport.algorithms[3f8c5d39e418]: exchanging keys
D, [2012-03-06T20:40:02.983416 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: queueing packet nr 1 type 34 len 20
D, [2012-03-06T20:40:02.983469 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: sent 24 bytes
D, [2012-03-06T20:40:03.024146 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: read 152 bytes
D, [2012-03-06T20:40:03.024270 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: received packet nr 1 type 31 len 148
D, [2012-03-06T20:40:03.027331 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: queueing packet nr 2 type 32 len 140
D, [2012-03-06T20:40:03.027409 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: sent 144 bytes
D, [2012-03-06T20:40:03.032446 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: read 720 bytes
D, [2012-03-06T20:40:03.032563 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: received packet nr 2 type 33 len 700
D, [2012-03-06T20:40:03.035433 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: queueing packet nr 3 type 21 len 20
D, [2012-03-06T20:40:03.035505 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: sent 24 bytes
D, [2012-03-06T20:40:03.035617 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: received packet nr 3 type 21 len 12
D, [2012-03-06T20:40:03.035954 #24165] DEBUG -- net.ssh.authentication.session[3f8c5e189fdc]: beginning authentication of `webistrano'
D, [2012-03-06T20:40:03.036081 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: queueing packet nr 4 type 5 len 28
D, [2012-03-06T20:40:03.036130 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: sent 52 bytes
D, [2012-03-06T20:40:03.074777 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: read 52 bytes
D, [2012-03-06T20:40:03.074912 #24165] DEBUG -- tcpsocket[3f8c5d39f250]: received packet nr 4 type 6 len 28
D, [2012-03-06T20:40:03.075018 #24165] DEBUG -- net.ssh.authentication.session[3f8c5e189fdc]: trying password
E, [2012-03-06T20:40:03.075077 #24165] ERROR -- net.ssh.authentication.session[3f8c5e189fdc]: all authorization methods failed (tried password)
/usr/lib64/ruby/gems/1.8/gems/net-ssh-2.3.0/lib/net/ssh.rb:200:in `start': webistrano (Net::SSH::AuthenticationFailed)
from ssh-test.rb:6
I wrote a simple PHP code, and it works:
<?php
$connection = ssh2_connect('ks2.so.d-sites.com', 22);
if (ssh2_auth_pubkey_file($connection, 'webistrano',
'/home/webistrano/.ssh/id_rsa.pub',
'/home/webistrano/.ssh/id_rsa')) {
echo "Identification réussie en utilisant une clé publique\n";
} else {
die('Echec de l\'identification en utilisant une clé publique');
}
Is there someone that can help me?
Thanks a lot in advance!
I've found how to solve that problem: adding a :auth_methods => ['publickey','password'] parameter in the Net::SSH.start function.
I have seen that error before,try uninstalling the gem 'net-ssh',try this:gem uninstall net-ssh -v 2.8.0, it works for me,
refer to: https://stackoverflow.com/a/21566548/3159604
I didn't use the Net::SSH.start solution but opted to set :ssh_options, {:forward_agent => true, :auth_methods => 'publickey'} which seems to work on my end.
Noted that Initially, I just used set :ssh_options, {:forward_agent => true} on 3 machines running on FreeBSD, CentOS and MacOSX and it worked fine - even used on production releases, however after testing on a VBoxed Vagrant-based CentOS (6.4) machine it stopped working. So, adding auth_method did the trick.
Just running ssh-add (in the terminal) and trying again (to deploy) worked for me.