Files
M5Stack_MicroPython/MicroPython_BUILD/components/micropython/esp32/libs/espcurl.c
T
Boris Lovosevic 0de63ef395 Changed the method of passing parameters from tasks/events/interrupts to MicroPython callbacks
Updated MicroPython scheduler functions
  Updated all affected modules

esp-idf sdspi_host driver refactored
  Using SDCard in SPI mode and display at the same time now works
  Tested on M5Stack & Adafruit 2.4" TFT Featherwing

Display module refactored
  uses (modified) esp-idf spi-master driver
  16-bit color mode added
  low level display functions added
  get TP calibration constants function added
  Backlight on/off function added
  M5Stack & GENERIC display types added
  display initialization sequence for unknown display types can now be handled from MicroPython
  tpcalib frozen module updated

I2C module refactored
  SLAVE mode added
  Low level I2C functions added
  esp-idf i2c driver modified

SPI module updated

UART module updated

network module updated

machine module: added method for reading internal ESP32 temperature sensor

_thread module: status of the system tasks is now available in _thread.list()

os module: SDCard mode can now be configured from MicroPython

time.ticks_xx() functions now returns correct tick count after time update
rtc module updated

Added MPU9250 frozen module (available on M5Stack)

Experimental Bluetooth support, not yet finished
Experimental support for eve display modules, not yet finished

License file added
License information in many source files updated/added
2018-02-28 14:46:02 +01:00

1317 lines
39 KiB
C

/*
* This file is part of the MicroPython ESP32 project, https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo
*
* The MIT License (MIT)
*
* Copyright (c) 2018 LoBo (https://github.com/loboris)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "sdkconfig.h"
#if defined(CONFIG_MICROPY_USE_CURL) || defined(CONFIG_MICROPY_USE_SSH)
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "libs/espcurl.h"
#include "libs/libGSM.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
#include "esp_log.h"
#include "esp_task_wdt.h"
#include "mphalport.h"
#include "esp_wifi_types.h"
#include "tcpip_adapter.h"
#include "py/mpthread.h"
#include "py/nlr.h"
//--------------------
void checkConnection()
{
tcpip_adapter_ip_info_t info;
tcpip_adapter_get_ip_info(WIFI_IF_STA, &info);
if (info.ip.addr == 0) {
#ifdef CONFIG_MICROPY_USE_GSM
if (ppposStatus() != GSM_STATE_CONNECTED) {
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "No Internet connection"));
}
#else
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "No Internet connection"));
#endif
}
}
#ifdef CONFIG_MICROPY_USE_CURL
const char *CURL_TAG = "[Curl]";
// Set default values for configuration variables
uint8_t curl_verbose = 0; // show detailed info of what curl functions are doing
uint8_t curl_progress = 0; // show progress during curl transfers
uint16_t curl_timeout = 60; // curl operations timeout in seconds
uint32_t curl_maxbytes = 300000; // limit download length
uint8_t curl_initialized = 0;
uint8_t curl_nodecode = 0; // if set to 1, do not use compression in http transfers
#if CONFIG_SPIRAM_SUPPORT
int hdr_maxlen = 1024;
int body_maxlen = 4096;
#else
int hdr_maxlen = 512;
int body_maxlen = 1024;
#endif
static uint8_t curl_sim_fs = 0;
struct curl_Transfer {
char *ptr;
uint32_t len;
uint32_t size;
int status;
uint8_t tofile;
uint32_t maxlen;
double lastruntime;
FILE *file;
CURL *curl;
};
struct curl_httppost *formpost = NULL;
struct curl_httppost *lastptr = NULL;
// Initialize the structure used in curlWrite callback
//-----------------------------------------------------------------------------------------------------------
static void init_curl_Transfer(CURL *curl, struct curl_Transfer *s, char *buf, uint16_t maxlen, FILE* file) {
s->len = 0;
s->size = 0;
s->status = 0;
s->maxlen = maxlen;
s->lastruntime = 0;
s->tofile = 0;
s->file = file;
s->ptr = buf;
s->curl = curl;
if (s->ptr) s->ptr[0] = '\0';
}
// Callback: Get response header or body to buffer or file
//--------------------------------------------------------------------------------
static size_t curlWrite(void *buffer, size_t size, size_t nmemb, void *userdata) {
struct curl_Transfer *s = (struct curl_Transfer *)userdata;
CURL *curl = s->curl;
double curtime = 0;
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime);
mp_hal_reset_wdt();
if (!s->tofile) {
// === Downloading to buffer ===
char *buf = (char *)buffer;
if (s->ptr) {
if ((s->status == 0) && ((size*nmemb) > 0)) {
for (int i=0; i<(size*nmemb); i++) {
if (s->len < (s->maxlen-2)) {
if (((buf[i] == 0x0a) || (buf[i] == 0x0d)) || ((buf[i] >= 0x20) && (buf[i] >= 0x20))) s->ptr[s->len] = buf[i];
else s->ptr[s->len] = '.';
s->len++;
s->ptr[s->len] = '\0';
}
else {
s->status = 1;
break;
}
}
if ((curl_progress) && ((curtime - s->lastruntime) > curl_progress)) {
s->lastruntime = curtime;
ESP_LOGI(CURL_TAG, "* Download: received %d", s->len);
}
}
}
return size*nmemb;
}
else {
// === Downloading to file ===
size_t nwrite;
if (curl_sim_fs) nwrite = size*nmemb;
else {
nwrite = fwrite(buffer, 1, size*nmemb, s->file);
if (nwrite <= 0) {
ESP_LOGE(CURL_TAG, "* Download: Error writing to file %d", nwrite);
return 0;
}
}
s->len += nwrite;
if ((curl_progress) && ((curtime - s->lastruntime) > curl_progress)) {
s->lastruntime = curtime;
ESP_LOGI(CURL_TAG, "* Download: received %d", s->len);
}
return nwrite;
}
}
// Callback: ftp PUT file
//--------------------------------------------------------------------------
static size_t curlRead(void *ptr, size_t size, size_t nmemb, void *userdata)
{
struct curl_Transfer *s = (struct curl_Transfer *)userdata;
CURL *curl = s->curl;
double curtime = 0;
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime);
size_t nread;
mp_hal_reset_wdt();
if (curl_sim_fs) {
if (s->len < 1024) {
size_t reqsize = size*nmemb;
nread = 0;
while ((nread+23) < reqsize) {
sprintf((char *)(ptr+nread), "%s", "Simulate upload data\r\n");
nread += 22;
}
}
else nread = 0;
}
else {
nread = fread(ptr, 1, size*nmemb, s->file);
}
s->len += nread;
if ((curl_progress) && ((curtime - s->lastruntime) > curl_progress)) {
s->lastruntime = curtime;
ESP_LOGI(CURL_TAG, "* Upload: sent %d", s->len);
}
//ESP_LOGI(CURL_TAG, "**Upload, read %d (%d,%d)", nread,size,nmemb);
if (nread <= 0) return 0;
return nread;
}
/*
//------------------------------------------------------------------
static int closesocket_callback(void *clientp, curl_socket_t item) {
int ret = lwip_close(item);
return ret;
}
//------------------------------------------------------------------------------------------------------------
static curl_socket_t opensocket_callback(void *clientp, curlsocktype purpose, struct curl_sockaddr *address) {
int s = lwip_socket(address->family, address->socktype, address->protocol);
return s;
}
*/
// Set some common curl options
//----------------------------------------------
static void _set_default_options(CURL *handle) {
curl_easy_setopt(handle, CURLOPT_VERBOSE, curl_verbose);
// ** Set SSL Options
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(handle, CURLOPT_PROXY_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_PROXY_SSL_VERIFYHOST, 0L);
//curl_easy_setopt(handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
// ==== Provide CA Certs from different file than default ====
//curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
// ===========================================================
/*
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER , 1L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST , 1L);
*/
/* If the server is redirected, we tell libcurl if we want to follow redirection
* There are some problems when redirecting ssl requests, so for now we disable redirection
*/
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 0L); // set to 1 to enable redirection
curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 3L);
curl_easy_setopt(handle, CURLOPT_TIMEOUT, (long)curl_timeout);
curl_easy_setopt(handle, CURLOPT_MAXFILESIZE, (long)curl_maxbytes);
curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
if (curl_nodecode) {
// Do not decode/decompress received content
curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "identity");
curl_easy_setopt(handle, CURLOPT_HTTP_CONTENT_DECODING, 0L);
}
else {
curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "");
curl_easy_setopt(handle, CURLOPT_HTTP_CONTENT_DECODING, 1L);
}
//curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, 1024L); //bytes/sec
//curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, 4); //seconds
// Open&Close socket callbacks can be set if special handling is needed
/*
curl_easy_setopt(handle, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
curl_easy_setopt(handle, CURLOPT_CLOSESOCKETDATA, NULL);
curl_easy_setopt(handle, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
curl_easy_setopt(handle, CURLOPT_OPENSOCKETDATA, NULL);
*/
}
//==================================================================================
int Curl_GET(char *url, char *fname, char *hdr, char *body, int hdrlen, int bodylen)
{
CURL *curl = NULL;
CURLcode res = 0;
FILE* file = NULL;
int err = 0;
if ((hdr) && (hdrlen < MIN_HDR_BUF_LEN)) {
err = -1;
goto exit;
}
if ((body) && (bodylen < MIN_BODY_BUF_LEN)) {
err = -2;
goto exit;
}
struct curl_Transfer get_data;
struct curl_Transfer get_header;
if (!url) {
err = -3;
goto exit;
}
if (!curl_initialized) {
res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res) {
err = -4;
goto exit;
}
curl_initialized = 1;
}
// Create a curl curl
curl = curl_easy_init();
if (curl == NULL) {
err = -5;
goto exit;
}
init_curl_Transfer(curl, &get_data, body, bodylen, NULL);
init_curl_Transfer(curl, &get_header, hdr, hdrlen, NULL);
if (fname) {
if (strcmp(fname, "simulate") == 0) {
get_data.tofile = 1;
curl_sim_fs = 1;
}
else {
file = fopen(fname, "wb");
if (file == NULL) {
err = -6;
goto exit;
}
get_data.file = file;
get_data.tofile = 1;
curl_sim_fs = 0;
}
}
curl_easy_setopt(curl, CURLOPT_URL, url);
_set_default_options(curl);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &get_header);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &get_data);
// Perform the request, res will get the return code
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if (curl_verbose) {
ESP_LOGE(CURL_TAG, "curl_easy_perform failed: %s", curl_easy_strerror(res));
}
if (body) snprintf(body, bodylen, "%s", curl_easy_strerror(res));
err = -7;
goto exit;
}
if (get_data.tofile) {
if (curl_progress) {
double curtime = 0;
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime);
ESP_LOGI(CURL_TAG, "* Download: received %d B; time=%0.1f s; speed=%0.1f KB/sec", get_data.len, curtime, (float)(((get_data.len*10)/curtime) / 10240.0));
}
if (body) {
if (strcmp(fname, "simulate") == 0) snprintf(body, bodylen, "SIMULATED save to file; size=%d", get_data.len);
else snprintf(body, bodylen, "Saved to file %s, size=%d", fname, get_data.len);
}
}
exit:
// Cleanup
if (file) fclose(file);
if (curl) curl_easy_cleanup(curl);
return err;
}
//=======================================================================
int Curl_POST(char *url , char *hdr, char *body, int hdrlen, int bodylen)
{
CURL *curl = NULL;
CURLcode res = 0;
int err = 0;
struct curl_slist *headerlist=NULL;
const char hl_buf[] = "Expect:";
if ((hdr) && (hdrlen < MIN_HDR_BUF_LEN)) {
err = -1;
goto exit;
}
if ((body) && (bodylen < MIN_BODY_BUF_LEN)) {
err = -2;
goto exit;
}
struct curl_Transfer get_data;
struct curl_Transfer get_header;
if (!url) {
err = -3;
goto exit;
}
if (!curl_initialized) {
res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res) {
err = -4;
goto exit;
}
curl_initialized = 1;
}
// Create a curl curl
curl = curl_easy_init();
if (curl == NULL) {
err = -5;
goto exit;
}
init_curl_Transfer(curl, &get_data, body, bodylen, NULL);
init_curl_Transfer(curl, &get_header, hdr, hdrlen, NULL);
// initialize custom header list (stating that Expect: 100-continue is not wanted
headerlist = curl_slist_append(headerlist, hl_buf);
// set URL that receives this POST
curl_easy_setopt(curl, CURLOPT_URL, url);
_set_default_options(curl);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &get_header);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &get_data);
if (formpost) curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
// Perform the request, res will get the return code
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if (curl_verbose) {
ESP_LOGE(CURL_TAG, "curl_easy_perform failed: %s", curl_easy_strerror(res));
}
if (body) snprintf(body, bodylen, "%s", curl_easy_strerror(res));
err = -7;
goto exit;
}
exit:
// Cleanup
if (curl) curl_easy_cleanup(curl);
if (formpost) {
curl_formfree(formpost);
formpost = NULL;
}
curl_slist_free_all(headerlist);
return err;
}
#ifdef CONFIG_MICROPY_USE_CURLFTP
//===================================================================================================================
int Curl_FTP(uint8_t upload, char *url, char *user_pass, char *fname, char *hdr, char *body, int hdrlen, int bodylen)
{
#undef DISABLE_SSH_AGENT
CURL *curl = NULL;
CURLcode res = 0;
int err = 0;
FILE* file = NULL;
int fsize = 0;
if ((hdr) && (hdrlen < MIN_HDR_BUF_LEN)) {
err = -1;
goto exit;
}
if ((body) && (bodylen < MIN_BODY_BUF_LEN)) {
err = -2;
goto exit;
}
struct curl_Transfer get_data;
struct curl_Transfer get_header;
if ((!url) || (!user_pass)) {
err = -3;
goto exit;
}
if (!curl_initialized) {
res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res) {
err = -4;
goto exit;
}
curl_initialized = 1;
}
// Create a curl curl
curl = curl_easy_init();
if (curl == NULL) {
err = -5;
goto exit;
}
init_curl_Transfer(curl, &get_data, body, bodylen, NULL);
init_curl_Transfer(curl, &get_header, hdr, hdrlen, NULL);
if (fname) {
if (strcmp(fname, "simulate") == 0) {
get_data.tofile = 1;
curl_sim_fs = 1;
}
else {
if (upload) {
// Uploading the file
struct stat sb;
if ((stat(fname, &sb) == 0) && (sb.st_size > 0)) {
fsize = sb.st_size;
file = fopen(fname, "rb");
}
}
else {
// Downloading to file (LIST or Get file)
file = fopen(fname, "wb");
}
if (file == NULL) {
err = -6;
goto exit;
}
get_data.file = file;
get_data.tofile = 1;
get_data.size = fsize;
curl_sim_fs = 0;
}
}
curl_easy_setopt(curl, CURLOPT_URL, url);
_set_default_options(curl);
/// build a list of commands to pass to libcurl
//headerlist = curl_slist_append(headerlist, "QUIT");
curl_easy_setopt(curl, CURLOPT_USERPWD, user_pass);
//curl_easy_setopt(curl, CURLOPT_POSTQUOTE, headerlist);
curl_easy_setopt(curl, CURLOPT_FTP_USE_EPSV, 0L);
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &get_header);
if (upload) {
curl_easy_setopt(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR_RETRY);
// we want to use our own read function
curl_easy_setopt(curl, CURLOPT_READFUNCTION, curlRead);
// specify which file to upload
curl_easy_setopt(curl, CURLOPT_READDATA, &get_data);
// enable uploading
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
if (fsize > 0) curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long)fsize);
}
else {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &get_data);
}
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)curl_timeout);
// Perform the request, res will get the return code
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if (curl_verbose) {
ESP_LOGE(CURL_TAG, "curl_easy_perform failed: %s", curl_easy_strerror(res));
}
if (body) snprintf(body, bodylen, "%s", curl_easy_strerror(res));
err = -7;
goto exit;
}
if (get_data.tofile) {
if (curl_progress) {
double curtime = 0;
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime);
if (upload) {
ESP_LOGI(CURL_TAG, "* Upload: sent %d B; time=%0.1f s; speed=%0.1f KB/sec", get_data.len, curtime, (float)(((get_data.len*10)/curtime) / 10240.0));
}
else {
ESP_LOGI(CURL_TAG, "* Download: received %d B; time=%0.1f s; speed=%0.1f KB/sec", get_data.len, curtime, (float)(((get_data.len*10)/curtime) / 10240.0));
}
}
if (body) {
if (upload) {
if (strcmp(fname, "simulate") == 0) snprintf(body, bodylen, "SIMULATED upload from file; size=%d", get_data.len);
else snprintf(body, bodylen, "Uploaded file %s, size=%d", fname, fsize);
}
else {
if (strcmp(fname, "simulate") == 0) snprintf(body, bodylen, "SIMULATED download to file; size=%d", get_data.len);
else snprintf(body, bodylen, "Downloaded to file %s, size=%d", fname, get_data.len);
}
}
}
exit:
// Cleanup
if (file) fclose(file);
if (curl) curl_easy_cleanup(curl);
return err;
}
#endif
//-------------------
void Curl_cleanup() {
if (curl_initialized) {
curl_global_cleanup();
curl_initialized = 0;
}
}
#endif // CONFIG_MICROPY_USE_CURL
#ifdef CONFIG_MICROPY_USE_SSH
#include "libssh2.h"
#include "libssh2_sftp.h"
const char *SSH_TAG = "[SSH]";
uint8_t ssh2_verbose = 0; // show detailed info of what ssh2 functions are doing
uint8_t ssh2_progress = 0; // show progress during ssh2 transfers
uint16_t ssh2_session_trace = 0;
uint8_t ssh2_session_timeout = 8;
uint32_t ssh2_maxbytes = 300000; // limit download length
#if CONFIG_SPIRAM_SUPPORT
int ssh2_hdr_maxlen = 1024;
int ssh2_body_maxlen = 4096;
#else
int ssh2_hdr_maxlen = 512;
int ssh2_body_maxlen = 1024;
#endif
// ==== LIBSSH2 functions ====
//------------------------------------------------------------
static int waitsocket(int socket_fd, LIBSSH2_SESSION *session)
{
struct timeval timeout;
int rc;
fd_set fd;
fd_set *writefd = NULL;
fd_set *readfd = NULL;
int dir;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO(&fd);
FD_SET(socket_fd, &fd);
// now make sure we wait in the correct direction
dir = libssh2_session_block_directions(session);
if (dir & LIBSSH2_SESSION_BLOCK_INBOUND) readfd = &fd;
if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) writefd = &fd;
rc = select(socket_fd + 1, readfd, writefd, NULL, &timeout);
return rc;
}
//--------------------------------------------------------------------------
static void _append_msg(char *messages, char *msg, int msglen, uint8_t type)
{
if ((messages) && ((strlen(messages) + strlen(msg)+1 < msglen))) {
strcat(messages, msg);
strcat(messages, "\n");
}
if (ssh2_verbose) {
if (type == ESP_LOG_ERROR) {
ESP_LOGE(SSH_TAG, "%s", msg);
}
else if (type == ESP_LOG_WARN) {
ESP_LOGW(SSH_TAG, "%s", msg);
}
else if (type == ESP_LOG_INFO) {
ESP_LOGI(SSH_TAG, "%s", msg);
}
}
}
//---------------------------------------------------------------------------
static int sock_connect(char *server, char *port, char *messages, int msglen)
{
int sock=-1;
int rc;
char msg[80];
struct addrinfo *res;
struct in_addr *addr;
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
// Resolve IP address
rc = getaddrinfo(server, port, &hints, &res);
if (rc != 0 || res == NULL) {
sprintf(msg, "* DNS lookup failed err=%d res=%p", rc, res);
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
return -1;
}
// Print the resolved IP. Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
sprintf(msg, "* DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
// Create the socket
sock = socket(res->ai_family, res->ai_socktype, 0);
if (sock < 0) {
sprintf(msg, "* Failed to allocate socket.");
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
freeaddrinfo(res);
return sock;
}
// Connect
if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) {
sprintf(msg, "* Failed to connect!");
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
freeaddrinfo(res);
close(sock);
return -1;
}
sprintf(msg, "* Connected");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
freeaddrinfo(res);
return sock;
}
//---------------------------------------------------------------------------------------------------------------------------------------------------
static LIBSSH2_SESSION *getSSHSession(int sock, char *username, char *password, char* privkey, char *pubkey, int auth_pw, char *messages, int msglen)
{
int rc;
const char *fingerprint;
LIBSSH2_SESSION *session;
char msg[96];
// Create a session instance
session = libssh2_session_init();
if(!session) {
sprintf(msg, "* Failed to create session!");
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
return NULL;
}
sprintf(msg, "* SSH session created");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
// Set trace level
libssh2_trace(session, ssh2_session_trace);
// Set timeout, reset wdt first
mp_hal_set_wdt_tmo();
#ifdef CONFIG_MICROPY_USE_TASK_WDT
esp_task_wdt_reset();
#endif
#if CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 || CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
vTaskDelay(1); // allow other core idle task to reset the watchdog
#endif
libssh2_session_set_timeout(session, ssh2_session_timeout*1000);
// Set preferred key exchange methods
//libssh2_session_method_pref(session, LIBSSH2_METHOD_KEX, "diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1");
// ... start it up. This will trade welcome banners, exchange keys, and setup crypto, compression, and MAC layers
rc = libssh2_session_handshake(session, sock);
if (rc) {
libssh2_session_disconnect(session, "Error Shutdown.");
libssh2_session_free(session);
sprintf(msg, "* Failure establishing SSH session: %d", rc);
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
return NULL;
}
sprintf(msg, "* SSH session established");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts. Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
int fplen = 0, fpofst = 0;
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
if (fingerprint) {
fplen = 32;
fpofst = 25;
sprintf(msg, "* Fingerprint (SHA256): [");
}
else {
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
if (fingerprint) {
fplen = 20;
fpofst = 23;
sprintf(msg, "* Fingerprint (SHA1): [");
}
else {
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
if (fingerprint) {
fplen = 16;
fpofst = 22;
sprintf(msg, "* Fingerprint (MD5): [");
}
}
}
if (fplen > 0) {
for(int i = 0; i < fplen; i++) {
sprintf(msg+fpofst+(i*2), "%02X", (unsigned char)fingerprint[i]);
}
strcat(msg, "]");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
}
if (auth_pw) {
// We could authenticate via password
if (libssh2_userauth_password(session, username, password)) {
libssh2_session_disconnect(session, "Error Shutdown.");
libssh2_session_free(session);
sprintf(msg, "* Authentication by password failed.");
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
return NULL;
}
sprintf(msg,"* Authentication by password succeed.");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
}
else {
// Or by public key
if (libssh2_userauth_publickey_fromfile(session, username, pubkey, privkey, password)) {
libssh2_session_disconnect(session, "Error Shutdown.");
libssh2_session_free(session);
sprintf(msg, "* Authentication by public key failed");
_append_msg(messages, msg, msglen, ESP_LOG_ERROR);
return NULL;
}
sprintf(msg, "* Authentication by public key succeed.");
_append_msg(messages, msg, msglen, ESP_LOG_INFO);
}
return session;
}
//------------------------------------------------------------------------------------------------------------------------------------------
static int sshDownload(LIBSSH2_CHANNEL *channel, libssh2_struct_stat *fileinfo, FILE *fdd, char *hdr, char *body, int hdrlen, int bodylen) {
libssh2_struct_stat_size got = 0;
uint64_t tstart = mp_hal_ticks_ms(), tlatest=0;
int rc, err = 0;
uint32_t bodypos = 0;
char msg[80];
char mem[1024];
while(got < fileinfo->st_size) {
int amount = sizeof(mem);
if ((fileinfo->st_size -got) < amount) {
amount = (int)(fileinfo->st_size -got);
}
rc = libssh2_channel_read(channel, mem, amount);
if (rc > 0) {
if (fdd != NULL) {
// === Downloading to file ===
if (err == 0) {
if ((got + amount) < ssh2_maxbytes) {
size_t nwrite = fwrite(mem, 1, amount, fdd);
if (nwrite <= 0) {
sprintf(msg, "* Download: Error writing to file at %u", (uint32_t)got);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
err = -10;
}
}
else {
sprintf(msg, "* Max file size exceeded at %u", (uint32_t)got);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
err = -11;
}
}
}
else {
// === Download to buffer ===
if (err == 0) {
if ((bodypos + amount) < bodylen) {
memcpy(body+bodypos, mem, amount);
bodypos += amount;
body[bodypos] = 0;
}
else {
sprintf(msg, "* Buffer full at %u", (uint32_t)got);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
err = -12;
}
}
}
}
else if( rc < 0) {
sprintf(msg, "\n* libssh2_channel_read() failed: %d", rc);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
err = -13;
break;
}
got += rc;
if (ssh2_progress) {
if (tlatest == 0) {
printf("\n");
tlatest = mp_hal_ticks_ms();
}
if ((tlatest + (ssh2_progress*1000)) >= mp_hal_ticks_ms()) {
tlatest = mp_hal_ticks_ms();
printf("Download: %u\r", (uint32_t)got);
}
}
}
tstart = mp_hal_ticks_ms() - tstart;
if (ssh2_progress) {
printf(" \r");
}
sprintf(msg, "* Received: %u bytes in %0.1f sec (%0.3f KB/s)", (uint32_t)got, (float)(tstart / 1000.0), (float)((float)(got)/1024.0/((float)(tstart) / 1000.0)));
_append_msg(hdr, msg, hdrlen, ESP_LOG_INFO);
return err;
}
//--------------------------------------------------------------------------------
static int sshUpload(LIBSSH2_CHANNEL *channel, FILE *fdd, char *hdr, int hdrlen) {
uint64_t tstart = mp_hal_ticks_ms(), tlatest=0;
int rc, err = 0;
char msg[80];
char mem[1024];
size_t nread;
char *ptr;
uint32_t sent = 0;
do {
nread = fread(mem, 1, sizeof(mem), fdd);
if (nread <= 0) {
// end of file
break;
}
ptr = mem;
do {
// write the same data over and over, until error or completion
rc = libssh2_channel_write(channel, ptr, nread);
if (rc < 0) {
sprintf(msg, "* Upload: Error sending: %d at %u", rc, sent);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
err = -10;
break;
}
else {
// rc indicates how many bytes were written this time
ptr += rc;
nread -= rc;
sent += rc;
}
if (ssh2_progress) {
if (tlatest == 0) {
printf("\n");
tlatest = mp_hal_ticks_ms();
}
if ((tlatest + (ssh2_progress*1000)) >= mp_hal_ticks_ms()) {
tlatest = mp_hal_ticks_ms();
printf("Upload: %u\r", sent);
}
}
} while (nread);
} while (1);
tstart = mp_hal_ticks_ms() - tstart;
if (ssh2_progress) {
printf(" \r");
}
sprintf(msg, "* Sent: %u bytes in %0.1f sec (%0.3f KB/s)", sent, (float)(tstart / 1000.0), (float)((float)(sent)/1024.0/((float)(tstart) / 1000.0)));
_append_msg(hdr, msg, hdrlen, ESP_LOG_INFO);
libssh2_channel_send_eof(channel);
libssh2_channel_wait_eof(channel);
libssh2_channel_wait_closed(channel);
return err;
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
int ssh_SCP(uint8_t type, char *server, char *port, char * scppath, char *user, char *pass, char *key, char *fname, char *hdr, char *body, int hdrlen, int bodylen)
{
char msg[80];
LIBSSH2_SESSION *session;
libssh2_struct_stat fileinfo;
FILE *fdd = NULL;
int fsize = 0;
hdrlen -=1;
bodylen -=1;
int auth = 1;
char pub_key[128];
char *privkey = NULL;
char *pubkey = NULL;
if (key) {
// Check the authentication keys
struct stat sb_key;
if ((stat(key, &sb_key) != 0) || (sb_key.st_size = 0)) {
sprintf(msg, "* Error opening private key file");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
return -5;
}
sprintf(pub_key, key);
strcat(pub_key, ".pub");
if ((stat(pub_key, &sb_key) != 0) || (sb_key.st_size = 0)) {
sprintf(msg, "* Error opening public key file");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
return -5;
}
privkey = key;
pubkey = pub_key;
auth = 0;
}
if ((fname) && ((type == 0) || (type == 1))){
if (strcmp(fname, "simulate") != 0) {
if (type == 1) {
// Uploading the file
struct stat sb_local;
if ((stat(fname, &sb_local) == 0) && (sb_local.st_size > 0)) {
fsize = sb_local.st_size;
fdd = fopen(fname, "rb");
}
}
else {
// Downloading to file (LIST or Get file)
fdd = fopen(fname, "wb");
}
if (fdd == NULL) {
sprintf(msg, "* Error opening file");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
return -5;
}
}
}
// ** Initialize libssh2
int rc = libssh2_init (0);
if (rc != 0) {
sprintf(msg, "* libssh2 initialization failed (%d)", rc);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
if (fdd) fclose(fdd);
return -1;
}
int sock = sock_connect(server, port, hdr, hdrlen);
if (sock < 0) {
if (fdd) fclose(fdd);
libssh2_exit();
return -2;
}
vTaskDelay(50 / portTICK_RATE_MS);
// ** Create session
session = getSSHSession(sock, user, pass, privkey, pubkey, auth, hdr, hdrlen);
if (session == NULL) {
if (fdd) fclose(fdd);
close(sock);
libssh2_exit();
return -3;
}
vTaskDelay(100 / portTICK_RATE_MS);
LIBSSH2_CHANNEL *channel = NULL;
// ** Open session
if (type == 1) {
// === SCP File upload ===
channel = libssh2_scp_send(session, scppath, 0555, (unsigned long)fsize);
if (!channel) {
char *errmsg;
int errlen;
int err = libssh2_session_last_error(session, &errmsg, &errlen, 0);
sprintf(msg, "* Unable to open a session: (%d) %s", err, errmsg);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
rc = -4;
goto shutdown;
}
// ** Upload a file via SCP
rc = sshUpload(channel, fdd, hdr, hdrlen);
if (fdd != NULL) {
if (rc == 0) sprintf(body, "Uploaded file %s", fname);
else sprintf(body, "Error uploading file %s", fname);
}
}
else if (type == 0) {
// === SCP File download ===
channel = libssh2_scp_recv2(session, scppath, &fileinfo);
if (!channel) {
sprintf(msg, "* Unable to open a session: %d", libssh2_session_last_errno(session));
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
rc = -4;
goto shutdown;
}
if (fileinfo.st_size > ssh2_maxbytes) {
sprintf(msg, "* Warning: file size (%u) > max allowed (%u) !", (uint32_t)fileinfo.st_size, ssh2_maxbytes);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
}
// ** Download a file via SCP
rc = sshDownload(channel, &fileinfo, fdd, hdr, body, hdrlen, bodylen);
if (fdd != NULL) {
if (rc == 0) sprintf(body, "Downloaded file %s", fname);
else sprintf(body, "Error downloading file %s", fname);
}
}
else if (type == 5) {
// === SSH exec, Execute non-blocking on the remove host ===
int bytecount = 0;
char *exitsignal=(char *)"none";
while (((channel = libssh2_channel_open_session(session)) == NULL) && (libssh2_session_last_error(session,NULL,NULL,0) == LIBSSH2_ERROR_EAGAIN)) {
waitsocket(sock, session);
}
if (channel == NULL) {
sprintf(msg, "* Channel Error");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
rc = -1;
goto endchannel;
}
sprintf(msg, "* Exec: '%s'", scppath);
_append_msg(hdr, msg, hdrlen, ESP_LOG_INFO);
while( (rc = libssh2_channel_exec(channel, scppath)) == LIBSSH2_ERROR_EAGAIN ) {
vTaskDelay(2 / portTICK_RATE_MS);
waitsocket(sock, session);
}
if ( rc != 0 ) {
sprintf(msg, "* Channel Exec Error %d", rc);
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
goto endchannel;
}
char buffer[1024];
int body_pos = 0;
for( ;; ) {
// loop until we block
int rc;
do {
rc = libssh2_channel_read( channel, buffer, sizeof(buffer) );
if (rc > 0) {
if ((body_pos+rc) < bodylen) {
memcpy(body+body_pos, buffer, rc);
body_pos += rc;
}
bytecount += rc;
}
}
while( rc > 0 );
// this is due to blocking that would occur otherwise so we loop on this condition
if ( rc == LIBSSH2_ERROR_EAGAIN ) waitsocket(sock, session);
else break;
}
int exitcode = 127;
while ( (rc = libssh2_channel_close(channel)) == LIBSSH2_ERROR_EAGAIN ) waitsocket(sock, session);
if ( rc == 0 ) {
exitcode = libssh2_channel_get_exit_status( channel );
libssh2_channel_get_exit_signal(channel, &exitsignal, NULL, NULL, NULL, NULL, NULL);
}
if (exitsignal) {
sprintf(msg, "* Got signal '%s'", exitsignal);
_append_msg(hdr, msg, hdrlen, ESP_LOG_INFO);
rc = -1;
}
else {
sprintf(msg, "* Exit: %d bytecount: %d", exitcode, bytecount);
_append_msg(hdr, msg, hdrlen, ESP_LOG_INFO);
rc = exitcode;
}
}
else if (type == 4) {
// === SFTP mkdir ===
LIBSSH2_SFTP *sftp_session;
sftp_session = libssh2_sftp_init(session);
if (!sftp_session) {
sprintf(msg, "Unable to init SFTP session");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
goto shutdown;
}
vTaskDelay(20 / portTICK_RATE_MS);
// Since we have not set non-blocking, tell libssh2 we are blocking
libssh2_session_set_blocking(session, 1);
// Make a directory via SFTP
rc = libssh2_sftp_mkdir(sftp_session, scppath,
LIBSSH2_SFTP_S_IRWXU|
LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IXGRP|
LIBSSH2_SFTP_S_IROTH|LIBSSH2_SFTP_S_IXOTH);
if (rc) {
sprintf(msg, "SFTP mkdir failed\n");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
}
libssh2_sftp_shutdown(sftp_session);
}
else if ((type == 2) || (type == 3)) {
// === SFTP List ===
LIBSSH2_SFTP *sftp_session;
LIBSSH2_SFTP_HANDLE *sftp_handle;
sftp_session = libssh2_sftp_init(session);
vTaskDelay(20 / portTICK_RATE_MS);
if (!sftp_session) {
sprintf(msg, "Unable to init SFTP session\n");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
goto shutdown;
}
// Since we have not set non-blocking, tell libssh2 we are blocking
libssh2_session_set_blocking(session, 1);
// Request a dir listing via SFTP
sftp_handle = libssh2_sftp_opendir(sftp_session, scppath);
if (!sftp_handle) {
sprintf(msg, "Unable to open dir with SFTP\n");
_append_msg(hdr, msg, hdrlen, ESP_LOG_ERROR);
goto shutdown;
}
int body_pos = 0;
do {
char mem[128];
char longentry[256];
LIBSSH2_SFTP_ATTRIBUTES attrs;
attrs.flags = LIBSSH2_SFTP_ATTR_SIZE | LIBSSH2_SFTP_ATTR_ACMODTIME;
/* loop until we fail */
rc = libssh2_sftp_readdir_ex(sftp_handle, mem, sizeof(mem), longentry, sizeof(longentry), &attrs);
if (rc > 0) {
// rc is the length of the file name in the mem buffer
if ((longentry[0] != '\0') && (type == 3)) {
if ((body_pos+strlen(longentry)) < bodylen) {
sprintf(body+body_pos, "%s\n", longentry);
body_pos += strlen(longentry) + 1;
}
} else {
if (attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
uint32_t prm = attrs.permissions >> 12;
if (prm == 4) sprintf(msg, "D\t");
else if (prm == 8) sprintf(msg, "F\t");
else if (prm == 10) sprintf(msg, "L\t");
else sprintf(msg, "?\t");
}
else sprintf(msg, "?\t");
if (attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) sprintf(msg+strlen(msg), "%llu\t" , (uint64_t)attrs.filesize);
else (sprintf(msg, "0\t"));
if ((body_pos+strlen(msg)+strlen(mem)+1) < bodylen) {
sprintf(body+body_pos, "%s%s\n", msg, mem);
body_pos += strlen(msg) + strlen(mem) + 1;
}
}
}
else
break;
} while (1);
libssh2_sftp_closedir(sftp_handle);
libssh2_sftp_shutdown(sftp_session);
}
endchannel:
if (channel) {
libssh2_channel_free(channel);
channel = NULL;
}
shutdown:
if (fdd) fclose(fdd);
libssh2_session_disconnect(session, "Normal Shutdown.");
libssh2_session_free(session);
close(sock);
libssh2_exit();
if (ssh2_verbose) {
ESP_LOGI(SSH_TAG, "All done");
}
return rc;
}
#endif // CONFIG_MICROPY_USE_SSH
#endif // defined(CONFIG_MICROPY_USE_CURL) || defined(CONFIG_MICROPY_USE_SSH)