Files
sakabin 070e06db00 update micropython
add m5stack.py
add m5ui.py
2018-12-04 14:06:15 +08:00

1448 lines
43 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 <ctype.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 "modnetwork.h"
#include "modmachine.h"
#include "py/mpthread.h"
#include "py/nlr.h"
#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
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] == 0x09) || (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;
}
// Converts an integer value to its hex character
//-----------------------------
static char to_hex(char code) {
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}
//------------------------------------------------------------
/* Returns a url encoded version of str
* IMPORTANT: be sure to free() the returned string after use
*/
//=================================
char *url_encode(const char *str) {
const char *pstr = str;
char *buf = malloc(strlen(str) * 3 + 1);
char *pbuf = buf;
while (*pstr) {
if (*pstr == ' ') {
*pbuf++ = '%';
*pbuf++ = to_hex(*pstr >> 4);
*pbuf++ = to_hex(*pstr & 15);
}
else {
*pbuf++ = *pstr;
}
pstr++;
}
*pbuf = '\0';
return buf;
}
//=========================================================================================================================================================================================================
int Curl_IMAP_GET(const char *opts, char *fname, char *hdr, char *body, int hdrlen, int bodylen, const char* imapserver, unsigned int imapport, const char* username, const char* password, char *cust_req)
{
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 (!curl_initialized) {
res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res) {
err = -3;
goto exit;
}
curl_initialized = 1;
}
// Create a curl curl
curl = curl_easy_init();
if (curl == NULL) {
err = -4;
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 = -5;
goto exit;
}
get_data.file = file;
get_data.tofile = 1;
curl_sim_fs = 0;
}
}
//set destination URL
char* url;
char *opts_out = url_encode(opts);
int opts_len = strlen(opts_out);
size_t len = strlen(imapserver) + opts_len + 16;
if ((url = (char*)malloc(len)) == NULL) {
free(opts_out);
ESP_LOGE(CURL_TAG, "Error allocating memory");
err = -6;
goto exit;
}
sprintf(url, "imaps://%s:%u/%s", imapserver, imapport, opts_out);
free(opts_out);
curl_easy_setopt(curl, CURLOPT_URL, url);
printf("=== URL=[%s]\n", url);
free(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);
// Try using Transport Layer Security (TLS), but continue anyway if it fails
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
//set authentication credentials if provided
if (username && *username) curl_easy_setopt(curl, CURLOPT_USERNAME, username);
if (password) curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
if (cust_req) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cust_req);
}
// Only allow IMAPS, we do not want this to work when unencrypted.
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_IMAPS);
// 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
// ==== 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)