Discussion:
libcurl SFTP file upload
Mueller, Alexander
2008-04-04 11:10:59 UTC
Permalink
Hello,

after 2 days of experimenting with curl, hoping to be able to upload a file via SFTP, today I gave up and just wanted to post my experiences for others trying the same. Hopefully no one else uses 2 days time for this again and also fails.

First of all, simple FTP Uploading works. I use MS Windows XP and compile on MS Visual Studio 6. Used versions: curl 7.18.0, libssh2 V 0.18, OpenSSL 0.9.8g

In order to get SFTP going, you'll have to download OpenSSL package separately (this is not contained in libcurl, don't ask me why), compile, then download libssh2 package (also not contained).

When compiling libssh2, you'll have to do some tweaking. Go into win32 and open the .dsw file. Go into comp.c and search for

#ifdef LIBSSH2_HAVE_ZLIB
# include <zlib.h>
#endif

and put *before* this passage this line:

#undef LIBSSH2_HAVE_ZLIB

in order to remove zlib stuff (which is, of course, also not contained and yields compiler and linker errors if you don't include this line). Then in VS, go to "Project / Settings / Link" and remove zlib.lib, therefore include there "user32.lib advapi32.lib gdi32.lib"

Then, create a new .c file with this content, and include it to the project:

#include "stdio.h"
#include "windows.h"

#if _MSC_VER < 1300
// ist für VC 6.0 noch nötig, als workaround
long _ftol(double);
long _ftol2( double dblSource )
{
return _ftol(dblSource);
}
long _aulldiv(unsigned long, unsigned long);
long _aulldvrm(unsigned long divisor, unsigned long dividend)
{
return _aulldiv(divisor, dividend); //
}
#endif

After that, you'll be able to build the libssh2.lib and libssh2.dll

When you're done, tell libcurl that SSH shall be enabled by setting the additional compiler options "USE_LIBSSH2,HAVE_LIBSSH2,HAVE_LIBSSH2_H,LIBSSH2_WIN32"

Rebuild libcurl. If you don't do this, you'll get errors out of libcurl in the moment you use "sftp://" in the URL.

Comment this out: curl_easy_setopt(hCurl, CURLOPT_UPLOAD, 1) ;
otherwise you'll also get an error after connecting to the server

Next, you'll find out that connecting to an URL does not work with subfolders.

You can use

curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://mydomain.de/");

successfully, but this won't work (of cource the subfolder exists):

curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://mydomain.de/subfolder"); -> CURLE_REMOTE_FILE_NOT_FOUND
curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://mydomain.de/subfolder/"); -> CURLE_REMOTE_FILE_NOT_FOUND

Then, when trying to use the "cd" command in libcurl, libcurl goes into an endless loop, this seems to be the case with all SFTP commands that are not supported in libcurl. The same goes for "put".

I don't want to be inpolite or not thankful, I think libcurl does a good job, and it is free. But the documentation lists SFTP in the features list, and there is no hint that SFTP uploads aren't possible. There is also no code example for uploading per SFTP, so now I know why. But there should be somewhere a statment about this, and I hope that my post may help others in deciding if they want to try this or not

Alex
Daniel Stenberg
2008-04-04 11:31:09 UTC
Permalink
Post by Mueller, Alexander
after 2 days of experimenting with curl, hoping to be able to upload a file
via SFTP, today I gave up and just wanted to post my experiences for others
trying the same. Hopefully no one else uses 2 days time for this again and
also fails.
You're of course free to do as you please, but in my view you seem to have
tripped just a few inches from your goal and that's a weird point to give up -
in my view.
Post by Mueller, Alexander
In order to get SFTP going, you'll have to download OpenSSL package
separately (this is not contained in libcurl, don't ask me why)
It is separate because it is a separate project with a separate license.
Post by Mueller, Alexander
then download libssh2 package (also not contained).
Same goes for that.
Post by Mueller, Alexander
Comment this out: curl_easy_setopt(hCurl, CURLOPT_UPLOAD, 1) ;
otherwise you'll also get an error after connecting to the server
Can you elaborate on this? Comment this out from what? Ie can you show us a
full example where this causes problems?
Post by Mueller, Alexander
curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://mydomain.de/subfolder");
While you may be tricked into believing that's a fine URL, it isn't. SFTP and
SCP URLs are crafted differently and then no, you probably did not have those
directories. An SFTP URL for the local directory "subfolder" would look like:

"sftp://mydomain.de/~/subfolder/"
Post by Mueller, Alexander
Then, when trying to use the "cd" command in libcurl, libcurl goes into an
endless loop, this seems to be the case with all SFTP commands that are not
supported in libcurl. The same goes for "put".
The infinite loop thing for unknown commands is a bug that we recently fixed.

The recognized and working commands are listed in the manual: chgrp, chmod,
chown, ln, mkdir, pwd, rename, rm, rmdir, symlink.

put as in upload is not supposed to be done with a "quote" command, but with a
regular libcurl CURLOPT_UPLOAD approach.
Post by Mueller, Alexander
But the documentation lists SFTP in the features list, and there is no hint
that SFTP uploads aren't possible.
That's because they are most certainly possible and even working on most
platforms. We even have several such test cases in the test suite that prove
this.
Post by Mueller, Alexander
There is also no code example for uploading per SFTP, so now I know why.
?
Post by Mueller, Alexander
But there should be somewhere a statment about this, and I hope that my post
may help others in deciding if they want to try this or not
Yes indeed. And I think my feedback on this can help too.
--
Commercial curl and libcurl Technical Support: http://haxx.se/curl.html
Mueller, Alexander
2008-04-04 11:46:56 UTC
Permalink
Thanks, Daniel, for the hints.
Post by Daniel Stenberg
Post by Mueller, Alexander
Comment this out: curl_easy_setopt(hCurl, CURLOPT_UPLOAD, 1) ;
otherwise you'll also get an error after connecting to the server
Can you elaborate on this? Comment this out from what? Ie can you show
us a full example where this causes problems?

OK, when I leave this line and perform no command, I receive this:

Connected to treuehandy.de (80.67.26.55) port 22 (#0)
SSH authentication methods available:
publickey,password,keyboard-interactive
Using ssh public key file id_dsa.pub
Using ssh private key file id_dsa
Initialized password authentication
Authentication complete
Sending quote commands
Syntax error in SFTP command. Supply parameter(s)!
Connection #0 to host treuehandy.de left intact

After including the line, I get this:

Connected to treuehandy.de (80.67.26.55) port 22 (#0)
SSH authentication methods available:
publickey,password,keyboard-interactive
Using ssh public key file id_dsa.pub
Using ssh private key file id_dsa
Initialized password authentication
Authentication complete
Upload failed: Operation failed
Connection #0 to host treuehandy.de left intact
Closing connection #0

Like I said, without any upload command, just this Option set.

As I said, I cannot input an Upload command, because this leads to the
endless loop:

headerlist = curl_slist_append(headerlist, "put
C:/test.txt test.txt");
Post by Daniel Stenberg
While you may be tricked into believing that's a fine URL, it isn't.
SFTP and SCP URLs are crafted differently and then no, you probably did
Post by Daniel Stenberg
Post by Mueller, Alexander
not have those directories. An SFTP URL for the local directory
"sftp://mydomain.de/~/subfolder/"
Oh, I did not know this, will fiddle around with this new information
The recognized and working commands are listed in the manual: chgrp,
chmod, chown, ln, mkdir, pwd, rename, rm, rmdir, symlink.
put as in upload is not supposed to be done with a "quote" command, but
with a regular libcurl CURLOPT_UPLOAD approach.
<<<

I did not yet find something about 'quote' commands, do you have a link
for me to clarify ?

Thank you in advance,

Alex
Dan Fandrich
2008-04-04 18:18:51 UTC
Permalink
Post by Mueller, Alexander
As I said, I cannot input an Upload command, because this leads to the
headerlist = curl_slist_append(headerlist, "put
C:/test.txt test.txt");
As Daniel said, "put" isn't a valid quote command, it's not how to do an
upload, and this bug was fixed in 7.18.1. Uploads are done in the
normal curl way, e.g.
curl -u user -T file-to-load sftp://host/absolute/path/new-file
Post by Mueller, Alexander
Oh, I did not know this, will fiddle around with this new information
The SFTP URL scheme is documented at
http://curl.haxx.se/rfc/draft-ietf-secsh-scp-sftp-ssh-uri-04.txt
Post by Mueller, Alexander
I did not yet find something about 'quote' commands, do you have a link
for me to clarify ?
They are documented in the curl man page, the curl_easy_setopt man page and
curl --manual.
Post by Mueller, Alexander
Dan
--
http://www.MoveAnnouncer.com The web change of address service
Let webmasters know that your web site has moved
Mueller, Alexander
2008-04-05 08:44:46 UTC
Permalink
At first thanks to Daniel and Dan for responding, I continued researching.

Dan wrote:
As Daniel said, "put" isn't a valid quote command, it's not how to do an upload, and this bug was fixed in 7.18.1. Uploads are done in the normal curl way, e.g.
curl -u user -T file-to-load sftp://host/absolute/path/new-file
<<<<

This looks for me rather like a command-line call ... I use the C-API and search for a way to do the SFTP Upload with this.
I tried this call (see code below) as quoted command, but also got errors.
The SFTP URL scheme is documented at
http://curl.haxx.se/rfc/draft-ietf-secsh-scp-sftp-ssh-uri-04.txt
<<<

I really appreciate your help, but this document didn't clear the scheme for me. When using WinSCP, I can access our host (treuehandy.de) via SFTP, and there I descend down to a subfolder "treuehandy.de" and then to folder "csv", this is the structure how it should work. I tried using the "~" symbol as proposed by Daniel, but this seems to symbolize a "user home directory", it didn't work, ans in WinSCP this was also not necessary so I wonder if this leads to success.
They are documented in the curl man page, the curl_easy_setopt man page and curl --manual.
<<<<

I have read the documentation of "curl_easy_setopt(CURLOPT_PREQOUTE / CURLOPT_QOUTE / CURLOPT_POSTQUOTE)" several times, but I still don't get it running. My code example (see below) ran with POSTQUOTE, like in the official FTP upload example. When I run it with "CURLOPT_QOUTE" I get the described infinite loop.

What irritates me most: I cannot find out the "PUT" command. The documentation of curl_easy_setopt(CURLOPT_QOUTE) says: "The valid SFTP commands are: chgrp, chmod, chown, ln, mkdir, pwd, rename, rm, rmdir, symlink. (SFTP support added in 7.16.3)". But these are all no upload commands ! So I tried the documented SFTP-Upload command, "put", with the described infinite loop. So what is the upload command ? The one from Dan (see above) also doesn't work, I tried it in my code which follows.

So, here comes the full code as my experimental state is at this moment (only user name was x-ed out):

STDMETHODIMP CFtpTransfer::Upload(/*[in]*/ BSTR bsFilename, /*[in]*/ BSTR bsSourcePath, /*[in]*/ BSTR bsFtpSiteWithPath,
/*[in]*/ BSTR bsUser, /*[in]*/ BSTR bsPassword, /*[out]*/ BSTR * pbsFehlermeldung)
{
USES_CONVERSION;

*pbsFehlermeldung = NULL;

HANDLE hFile = NULL;
char * lpszCurlErrorBuffer[CURL_ERROR_SIZE];
CURLcode nCurlResult = CURL_LAST;

curl_global_init(CURL_GLOBAL_ALL);

CURL * hCurl = curl_easy_init();

if (!hCurl)
{
curl_global_cleanup();
*pbsFehlermeldung = SysAllocString(L"Fehler bei der Initialisierung der CURL-Library");
return S_FALSE;
}

DWORD dwLastError;

// Prüfung, ob die zu transferierende Datei existiert, und ich Schreibrecht habe
{
wchar_t lpszSourceFileWithPath[256];
swprintf(lpszSourceFileWithPath, L"%s\\%s", bsSourcePath, bsFilename);
hFile = CreateFileW(lpszSourceFileWithPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

dwLastError = GetLastError();

if (hFile == INVALID_HANDLE_VALUE)
{
wchar_t lpszFehlermeldung[512];
swprintf(lpszFehlermeldung, L"Fehler %ld beim Lesen der Datei:\n%s", dwLastError, lpszSourceFileWithPath);
*pbsFehlermeldung = SysAllocString(lpszFehlermeldung);
curl_global_cleanup();
return S_FALSE;
}
}

{
struct curl_slist * headerlist = NULL;

wchar_t lpszCommandBuffer[512], lpszTarget[512], lpszUserPassword[256];

if (SysStringLen(bsFtpSiteWithPath) >= 4)
{
if (_wcsnicmp(bsFtpSiteWithPath, L"SFTP", 4) == 0)
{
wchar_t lpszSourcePathUnixFormat[512];
memset(lpszSourcePathUnixFormat, 0, sizeof(lpszSourcePathUnixFormat));

for (unsigned int i = 0; i < SysStringLen(bsSourcePath); i++)
if (bsSourcePath[i] == L'\\')
lpszSourcePathUnixFormat[i] = L'/';
else
lpszSourcePathUnixFormat[i] = bsSourcePath[i];

swprintf(lpszCommandBuffer, L"curl -u xxxxxxxx -T C:/test.txt sftp://treuehandy.de/treuehandy.de/csv/test.txt");
//swprintf(lpszCommandBuffer, L"put C:/test.txt test.txt");
//swprintf(lpszCommandBuffer, L"put %s %s", lpszSourcePathUnixFormat, bsFilename);
}
else
swprintf(lpszCommandBuffer, L"RNFR %s", bsFilename);
}
else
swprintf(lpszCommandBuffer, L"RNFR %s", bsFilename);

swprintf(lpszTarget, L"%s/%s", bsFtpSiteWithPath, bsFilename);

/* enable error buffer */
curl_easy_setopt(hCurl, CURLOPT_ERRORBUFFER, lpszCurlErrorBuffer) ;
curl_easy_setopt(hCurl, CURLOPT_VERBOSE , 1);
curl_easy_setopt(hCurl, CURLOPT_DEBUGFUNCTION, my_curl_debug_callback);

/* enable uploading */
/* Im Fall von SFTP auskommentieren !*/ curl_easy_setopt(hCurl, CURLOPT_UPLOAD, TRUE) ;

/* specify target */
//curl_easy_setopt(hCurl, CURLOPT_URL, W2CA(lpszTarget));
curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://treuehandy.de/treuehandy.de/csv/");
//curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://treuehandy.de/");

/* build a list of commands to pass to libcurl */
headerlist = curl_slist_append(headerlist, W2CA(lpszCommandBuffer));

/* we want to use our own read function */
curl_easy_setopt(hCurl, CURLOPT_READFUNCTION, read_callback);

/* pass in that last of FTP commands to run after the transfer */
curl_easy_setopt(hCurl, CURLOPT_POSTQUOTE, headerlist);

/* now specify which file to upload */
curl_easy_setopt(hCurl, CURLOPT_READDATA, hFile);

swprintf(lpszUserPassword, L"%s:%s", bsUser, bsPassword);
curl_easy_setopt(hCurl, CURLOPT_USERPWD, W2CA(lpszUserPassword)); // user : password

// SSL verpflichtend machen
//curl_easy_setopt(hCurl, CURLOPT_USE_SSL, CURLUSESSL_ALL);

/* Now run off and do what you've been told! */
nCurlResult = curl_easy_perform(hCurl);

/* clean up the FTP commands list */
curl_slist_free_all (headerlist);

/* always cleanup */
curl_easy_cleanup(hCurl);
}

CloseHandle(hFile);

curl_global_cleanup();

if (nCurlResult == CURLE_OK)
return S_OK;
else
{
wchar_t lpszFehlermeldung[64];
swprintf(lpszFehlermeldung, L"FTP-Operation ergab Fehler Nr. %ld", nCurlResult);
*pbsFehlermeldung = SysAllocString(lpszFehlermeldung);
return S_FALSE;
}
}

nCurlResult yields "CULRE_SSH", and this is the debug result:

Trying 80.67.26.55... connected
Connected to treuehandy.de (80.67.26.55) port 22 (#0)
SSH authentication methods available: publickey,password,keyboard-interactive
Using ssh public key file id_dsa.pub
Using ssh private key file id_dsa
Initialized password authentication
Authentication complete
Upload failed: Operation failed
Connection #0 to host treuehandy.de left intact
Closing connection #0

Thanks for any hint!

Alex
Dan Fandrich
2008-04-05 16:12:06 UTC
Permalink
Post by Mueller, Alexander
This looks for me rather like a command-line call ... I use the C-API and search for a way to do the SFTP Upload with this.
I tried this call (see code below) as quoted command, but also got errors.
I was trying to show with that command that quote commands are not involved
in uploads. You can use the --libcurl command-line option to turn that
command line into a C program if you want.
Post by Mueller, Alexander
I really appreciate your help, but this document didn't clear the scheme for me. When using WinSCP, I can access our host (treuehandy.de) via SFTP, and there I descend down to a subfolder "treuehandy.de" and then to folder "csv", this is the structure how it should work. I tried using the "~" symbol as proposed by Daniel, but this seems to symbolize a "user home directory", it didn't work, ans in WinSCP this was also not necessary so I wonder if this leads to success.
Does WinSCP use SFTP URLs? If not, then that URL spec isn't relevant. I
don't know what WinSCP does, but what you describe is likely represented
by a URL of either sftp://treuehandy.de/treuehandy.de/csv/file-to-upload or
sftp://treuehandy.de/~/treuehandy.de/csv/file-to-upload
Post by Mueller, Alexander
I have read the documentation of "curl_easy_setopt(CURLOPT_PREQOUTE / CURLOPT_QOUTE / CURLOPT_POSTQUOTE)" several times, but I still don't get it running. My code example (see below) ran with POSTQUOTE, like in the official FTP upload example. When I run it with "CURLOPT_QOUTE" I get the described infinite loop.
What irritates me most: I cannot find out the "PUT" command. The documentation of curl_easy_setopt(CURLOPT_QOUTE) says: "The valid SFTP commands are: chgrp, chmod, chown, ln, mkdir, pwd, rename, rm, rmdir, symlink. (SFTP support added in 7.16.3)". But these are all no upload commands ! So I tried the documented SFTP-Upload command, "put", with the described infinite loop. So what is the upload command ? The one from Dan (see above) also doesn't work, I tried it in my code which follows.
This is the fourth time someone has written this: you can't do uploads with
quote commands! So, please, please, stop trying!
Post by Mueller, Alexander
curl_easy_setopt(hCurl, CURLOPT_URL, "sftp://treuehandy.de/treuehandy.de/csv/");
URL stands for Uniform Resource Locator and the resource this URL is
pointing to is a directory; you can't upload directories. Since you're
uploading a file, you need a URL that locates a file, such as
sftp://treuehandy.de/treuehandy.de/csv/the-file Try using that and
eliminating any mention of QUOTE commands.
Post by Mueller, Alexander
Dan
--
http://www.MoveAnnouncer.com The web change of address service
Let webmasters know that your web site has moved
Mueller, Alexander
2008-04-06 08:15:57 UTC
Permalink
It works now, thanks a lot.

The key to success was commenting the CURLOPT_QUOTE out, and indeed the URL had to be "sftp://treuehandy.de/~/treuehandy.de/csv"

So thanks to everyone, and to give something back to the community, here comes the full function which can do FTP Upload and SFTP Upload as an example. The function is made for MS Visual Studio 6, the class CFtpTransfer ist usable for example by Visual Basic 6 as an ActiveX class.


static size_t read_callback(void * pBuffer, size_t size, size_t nmemb, void * hFile)
{
DWORD dwNumberOfBytesRead = 0;

BOOL bResult = ReadFile( (HANDLE) hFile, pBuffer, size * nmemb, &dwNumberOfBytesRead, NULL);

return dwNumberOfBytesRead;
}

int my_curl_debug_callback (CURL * objCurl, curl_infotype objT, char * lpszText, size_t uTextSize, void * pPointer)
{
if (objT == CURLINFO_TEXT)
{
USES_CONVERSION;
char * lpszDebugMessage = (char *) alloca(uTextSize + 2);
sprintf(lpszDebugMessage, "%s\0", lpszText);
OutputDebugString(A2CT(lpszDebugMessage));
}
return 0;
}

STDMETHODIMP CFtpTransfer::Upload(/*[in]*/ BSTR bsFilename, /*[in]*/ BSTR bsSourcePath, /*[in]*/ BSTR bsFtpSiteWithPath,
/*[in]*/ BSTR bsUser, /*[in]*/ BSTR bsPassword, /*[out]*/ BSTR * pbsFehlermeldung)
{
USES_CONVERSION;

*pbsFehlermeldung = NULL;

HANDLE hFile = NULL;
char * lpszCurlErrorBuffer[CURL_ERROR_SIZE];
CURLcode nCurlResult = CURL_LAST;
bool bSftp = false;

curl_global_init(CURL_GLOBAL_ALL);

CURL * hCurl = curl_easy_init();

if (!hCurl)
{
curl_global_cleanup();
*pbsFehlermeldung = SysAllocString(L"Fehler bei der Initialisierung der CURL-Library");
return S_FALSE;
}

DWORD dwLastError;

// Prüfung, ob die zu transferierende Datei existiert, und ich Schreibrecht habe
{
wchar_t lpszSourceFileWithPath[256];
swprintf(lpszSourceFileWithPath, L"%s\\%s", bsSourcePath, bsFilename);
hFile = CreateFileW(lpszSourceFileWithPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

dwLastError = GetLastError();

if (hFile == INVALID_HANDLE_VALUE)
{
wchar_t lpszFehlermeldung[512];
swprintf(lpszFehlermeldung, L"Fehler %ld beim Lesen der Datei:\n%s", dwLastError, lpszSourceFileWithPath);
*pbsFehlermeldung = SysAllocString(lpszFehlermeldung);
curl_global_cleanup();
return S_FALSE;
}
}

{
struct curl_slist * headerlist = NULL;

wchar_t lpszCommandBuffer[512], lpszTarget[512], lpszUserPassword[256];

if (SysStringLen(bsFtpSiteWithPath) >= 4)
{
if (_wcsnicmp(bsFtpSiteWithPath, L"SFTP", 4) == 0)
{
bSftp = true;
}
else
swprintf(lpszCommandBuffer, L"RNFR %s", bsFilename);
}
else
swprintf(lpszCommandBuffer, L"RNFR %s", bsFilename);

swprintf(lpszTarget, L"%s/%s", bsFtpSiteWithPath, bsFilename);

/* enable error buffer */
curl_easy_setopt(hCurl, CURLOPT_ERRORBUFFER, lpszCurlErrorBuffer) ;
curl_easy_setopt(hCurl, CURLOPT_VERBOSE , 1);
curl_easy_setopt(hCurl, CURLOPT_DEBUGFUNCTION, my_curl_debug_callback);

/* enable uploading */
curl_easy_setopt(hCurl, CURLOPT_UPLOAD, TRUE) ;

/* specify target */
curl_easy_setopt(hCurl, CURLOPT_URL, W2CA(lpszTarget));

/* build a list of commands to pass to libcurl */
headerlist = curl_slist_append(headerlist, W2CA(lpszCommandBuffer));

/* we want to use our own read function */
curl_easy_setopt(hCurl, CURLOPT_READFUNCTION, read_callback);

/* pass in that last of FTP commands to run after the transfer */
if (!bSftp)
curl_easy_setopt(hCurl, CURLOPT_POSTQUOTE, headerlist);

/* now specify which file to upload */
curl_easy_setopt(hCurl, CURLOPT_READDATA, hFile);

swprintf(lpszUserPassword, L"%s:%s", bsUser, bsPassword);
curl_easy_setopt(hCurl, CURLOPT_USERPWD, W2CA(lpszUserPassword)); // user : password

/* Now run off and do what you've been told! */
nCurlResult = curl_easy_perform(hCurl);

/* clean up the FTP commands list */
curl_slist_free_all (headerlist);

/* always cleanup */
curl_easy_cleanup(hCurl);
}

CloseHandle(hFile);

curl_global_cleanup();

if (nCurlResult == CURLE_OK)
return S_OK;
else
{
wchar_t lpszFehlermeldung[64];
swprintf(lpszFehlermeldung, L"FTP-Operation ergab Fehler Nr. %ld", nCurlResult);
*pbsFehlermeldung = SysAllocString(lpszFehlermeldung);
return S_FALSE;
}
}

Loading...