commit 1092c739861b0fbbb6e8d4a053f030640c687577 Author: Kyle Isom Date: Tue Oct 3 18:20:30 2023 -0700 initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/connSettings.h b/include/connSettings.h new file mode 100644 index 0000000..4e922b4 --- /dev/null +++ b/include/connSettings.h @@ -0,0 +1,47 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +enum ConnFlag +{ + FLAG_DISCONNECT_ON_EXIT=1, + FLAG_PETSCII=2, + FLAG_TELNET=4, + FLAG_ECHO=8, + FLAG_XONXOFF=16, + FLAG_SECURE=32, + FLAG_RTSCTS=64 +}; + +class ConnSettings +{ + public: + boolean petscii = false; + boolean telnet = false; + boolean echo = false; + boolean xonxoff = false; + boolean rtscts = false; + boolean secure = false; + + ConnSettings(int flagBitmap); + ConnSettings(const char *dmodifiers); + ConnSettings(String modifiers); + int getBitmap(); + int getBitmap(FlowControlType forceCheck); + void setFlag(ConnFlag flagMask, boolean newVal); + String getFlagString(); + + static void IPtoStr(IPAddress *ip, String &str); + static IPAddress *parseIP(const char *ipStr); +}; diff --git a/include/filelog.h b/include/filelog.h new file mode 100644 index 0000000..0706c31 --- /dev/null +++ b/include/filelog.h @@ -0,0 +1,48 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +enum LogOutputState +{ + LOS_NADA=0, + LOS_SocketIn=1, + LOS_SocketOut=2, + LOS_SerialIn=3, + LOS_SerialOut=4 +}; + +static unsigned long expectedSerialTime = 1000; + +static boolean logFileOpen = false; +static bool logFileDebug= false; +static File logFile; + +static void logSerialOut(const uint8_t c); +static void logSocketOut(const uint8_t c); +static void logSerialIn(const uint8_t c); +static void logSocketIn(const uint8_t c); +static void logPrint(const char* msg); +static void logPrintln(const char* msg); +static void logPrintf(const char* format, ...); +static void logPrintfln(const char* format, ...); +static char *TOHEX(const char *s, char *hex, const size_t len); +static char *TOHEX(long a); +static char *TOHEX(int a); +static char *TOHEX(unsigned int a); +static char *TOHEX(unsigned long a); +static char *tohex(uint8_t a); +static char *TOHEX(uint8_t a); +static uint8_t FROMHEX(uint8_t a1, uint8_t a2); +static char *FROMHEX(const char *hex, char *s, const size_t len); diff --git a/include/pet2asc.h b/include/pet2asc.h new file mode 100644 index 0000000..9b53fcf --- /dev/null +++ b/include/pet2asc.h @@ -0,0 +1,75 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifdef ZIMODEM_ESP32 +# include +# define ENC_TYPE_NONE WIFI_AUTH_OPEN +# include +# include +# include +# include "SD.h" +# include "SPI.h" +# include "driver/uart.h" + static HardwareSerial HWSerial(UART_NUM_2); +#else +# include "ESP8266WiFi.h" +# define HWSerial Serial +#endif + +#include + +char petToAsc(char c); +bool ascToPet(char *c, Stream *stream); +char ascToPetcii(char c); +bool handleAsciiIAC(char *c, Stream *stream); + +static void setCharArray(char **target, const char *src) +{ + if(src == NULL) + return; + if(*target != NULL) + free(*target); + *target = (char *)malloc(strlen(src)+1); + strcpy(*target,src); +} + +static void freeCharArray(char **arr) +{ + if(*arr == NULL) + return; + free(*arr); + *arr = NULL; +} + +static int modifierCompare(const char *match1, const char *match2) +{ + if(strlen(match1) != strlen(match2)) + return -1; + + for(int i1=0;i1 + +# define HCM_BUFSIZ 104 +# define HCM_SENDBUF (208/2 - 6) +# define HCM_FNSIZ 32 +# define HCM_MAXFN 16 + +typedef struct _HCMFile +{ + char descriptor; + File f; + uint8_t mode; + uint8_t format; + uint8_t type; + int reclen; + char filename[HCM_FNSIZ+1]; + struct _HCMFile *nxt; +} HCMFile; + +class HostCM +{ +private: + ZSerial hserial; + const struct _HCOpts + { + unsigned int speed = 15; //B9600 + unsigned int parity = 0;// ?! + unsigned int stopb=0; + unsigned char lineend=0xd; + unsigned char prompt=0x11; + unsigned char response=0x13; + unsigned char ext=0; + } opt PROGMEM; + + uint8_t outbuf[HCM_BUFSIZ]; + int odex = 0; + + uint8_t inbuf[HCM_BUFSIZ+1]; + int idex = 0; + + bool aborted = false; + unsigned long lastNonPlusTm = 0; + unsigned int plussesInARow = 0; + unsigned long plusTimeExpire = 0; + HCMFile files[HCM_MAXFN]; + FS *hFS = &SD; + File openDirF = (File)0; + File renameF = (File)0; + + char checksum(uint8_t *b, int n); + void checkDoPlusPlusPlus(const int c, const unsigned long tm); + bool checkPlusPlusPlusExpire(const unsigned long tm); + void sendNAK(); + void sendACK(); + void sendError(const char* format, ...); + bool closeAllFiles(); + HCMFile *addNewFileEntry(); + void delFileEntry(HCMFile *e); + HCMFile *getFileByDescriptor(char c); + int numOpenFiles(); + + void protoOpenFile(); + void protoCloseFile(); + void protoPutToFile(); + void protoGetFileBytes(); + void protoOpenDir(); + void protoNextDirFile(); + void protoCloseDir(); + void protoSetRenameFile(); + void protoFinRenameFile(); + void protoEraseFile(); + void protoSeekFile(); + +public: + void receiveLoop(); + bool isAborted(); + HostCM(FS *fs); + ~HostCM(); +}; +#endif +#endif diff --git a/include/proto_http.h b/include/proto_http.h new file mode 100644 index 0000000..7f52622 --- /dev/null +++ b/include/proto_http.h @@ -0,0 +1,22 @@ +#ifndef ZIMODEM_PROTO_HTTP +#define ZIMODEM_PROTO_HTTP +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +bool parseWebUrl(uint8_t *vbuf, char **hostIp, char **req, int *port, bool *doSSL); +bool doWebGetBytes(const char *hostIp, int port, const char *req, const bool doSSL, uint8_t *buf, int *bufSize); +WiFiClient *doWebGetStream(const char *hostIp, int port, const char *req, bool doSSL, uint32_t *responseSize); +bool doWebGet(const char *hostIp, int port, FS *fs, const char *filename, const char *req, const bool doSSL); +#endif diff --git a/include/proto_kermit.h b/include/proto_kermit.h new file mode 100644 index 0000000..6dd2bab --- /dev/null +++ b/include/proto_kermit.h @@ -0,0 +1,155 @@ +#include + +class KModem +{ +private: + static const int MAXPACKSIZ = 94; /* Maximum packet size */ + static const int MAXTRY = 20; /* Times to retry a packet */ + static const int MYTIME = 10; /* (10) Seconds after which I should be timed out */ + static const int MAXTIM = 60; /* (60) Maximum timeout interval */ + static const int MINTIM = 2; /* (2) Minumum timeout interval */ + static const char MYQUOTE ='#'; /* Quote character I will use */ + static const int MYPAD = 0; /* Number of padding characters I will need */ + static const int MYPCHAR = 0; /* Padding character I need (NULL) */ + static const char MYEOL = '\n'; /* End-Of-Line character I need */ + static const char SOH = 1; /* Start of header */ + static const char CR = 13; /* ASCII Carriage Return */ + static const char SP = 32; /* ASCII space */ + static const char DEL = 127; /* Delete (rubout) */ + static const char ESCCHR = '^'; /* Default escape character for CONNECT */ + static const char NUL = '\0'; /* Null character */ + static const char FALSE = 0; + static const char TRUE = -1; + + int size=0, /* Size of present data */ + rpsiz=0, /* Maximum receive packet size */ + spsiz=0, /* Maximum send packet size */ + pad=0, /* How much padding to send */ + timint=0, /* Timeout for foreign host on sends */ + n=0, /* Packet number */ + numtry=0, /* Times this packet retried */ + oldtry=0, /* Times previous packet retried */ + image=1, /* -1 means 8-bit mode */ + debug=99, /* indicates level of debugging output (0=none) */ + filecount=0, /* Number of files left to send */ + filenum=0, + mflg=0, /* Flag for MacKermit mode */ + xflg=0; /* flag for xmit directory structure */ + char state, /* Present state of the automaton */ + padchar, /* Padding character to send */ + eol, /* End-Of-Line character to send */ + escchr, /* Connect command escape character */ + quote, /* Quote character in incoming data */ + *filnam, /* Current file name */ + *filnamo, /* File name sent */ + *ttyline, /* Pointer to tty line */ + recpkt[MAXPACKSIZ], /* Receive packet buffer */ + packet[MAXPACKSIZ], /* Packet buffer */ + ldata[1024]; /* First line of data to send over connection */ + String **filelist = 0; + int (*recvChar)(ZSerial *ser, int); + void (*sendChar)(ZSerial *ser, char); + bool (*dataHandler)(File *kfp, unsigned long number, char *buffer, int len); + + void flushinput(); + void rpar(char data[]); + void spar(char data[]); + int gnxtfl(); + void bufemp(char buffer[], int len); + void prerrpkt(char *msg); + int bufill(char buffer[]); + int rpack(int *len, int *num, char *data); + int spack(char type, int num, int len, char *data); + char rdata(); + char rinit(); + char rfile(); + char sfile(); + char sinit(); + char sdata(); + char sbreak(); + char seof(); + + bool kfpClosed = true; + String *errStr = 0; +public: + String rootpath = ""; + FS *kfileSystem = &SD; + File kfp; + ZSerial kserial; + + KModem(FlowControlType commandFlow, + int (*recvChar)(ZSerial *ser, int), + void (*sendChar)(ZSerial *ser, char), + bool (*dataHandler)(File *kfp, unsigned long, char*, int), + String &errors); + void setTransmitList(String **fileList, int numFiles); + bool receive(); + bool transmit(); +}; + +static int kReceiveSerial(ZSerial *ser, int del) +{ + unsigned long end=millis() + (del * 1000L); + while(millis() < end) + { + serialOutDeque(); + if(ser->available() > 0) + { + int c=ser->read(); + logSerialIn(c); + return c; + } + yield(); + } + return -1; +} + +static void kSendSerial(ZSerial *ser, char c) +{ + ser->write((uint8_t)c); + ser->flush(); +} + +static bool kUDataHandler(File *kfp, unsigned long number, char *buf, int sz) +{ + for(int i=0;iwrite((uint8_t)buf[i]); + return true; +} + +static bool kDDataHandler(File *kfp, unsigned long number, char *buf, int sz) +{ + for(int i=0;iread(); + if(c<0) + { + if(i==0) + return false; + buf[i] = (char)26; + } + else + buf[i] = (char)c; + } + return true; +} + +static boolean kDownload(FlowControlType commandFlow, FS &fs, String **fileList, int fileCount, String &errors) +{ + KModem kmo(commandFlow, kReceiveSerial, kSendSerial, kDDataHandler, errors); + kmo.kfileSystem = &fs; + kmo.setTransmitList(fileList,fileCount); + bool result = kmo.transmit(); + return result; +} + +static boolean kUpload(FlowControlType commandFlow, FS &fs, String rootPath, String &errors) +{ + KModem kmo(commandFlow, kReceiveSerial, kSendSerial, kUDataHandler, errors); + kmo.kfileSystem = &fs; + kmo.rootpath = rootPath; + bool result = kmo.receive(); + return result; +} + + diff --git a/include/proto_ping.h b/include/proto_ping.h new file mode 100644 index 0000000..5d9a87d --- /dev/null +++ b/include/proto_ping.h @@ -0,0 +1,21 @@ +#ifndef ZIMODEM_PROTO_PING +#define ZIMODEM_PROTO_PING +#ifdef INCLUDE_PING +/* + Copyright 2023-2023 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +int ping(char *host); +#endif +#endif diff --git a/include/proto_xmodem.h b/include/proto_xmodem.h new file mode 100644 index 0000000..3529828 --- /dev/null +++ b/include/proto_xmodem.h @@ -0,0 +1,140 @@ +/* + Copyright 2018-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#include + +class XModem +{ + typedef enum + { + Crc, + ChkSum + } transfer_t; + + private: + //holds readed byte (due to dataAvail()) + int byte; + //expected block number + unsigned char blockNo; + //extended block number, send to dataHandler() + unsigned long blockNoExt; + //retry counter for NACK + int retries; + //buffer + char buffer[128]; + //repeated block flag + bool repeatedBlock; + File *xfile = null; + ZSerial xserial; + + int (*recvChar)(ZSerial *ser, int); + void (*sendChar)(ZSerial *ser, char); + bool (*dataHandler)(File *xfile, unsigned long number, char *buffer, int len); + unsigned short crc16_ccitt(char *buf, int size); + bool dataAvail(int delay); + int dataRead(int delay); + void dataWrite(char symbol); + bool receiveFrameNo(void); + bool receiveData(void); + bool checkCrc(void); + bool checkChkSum(void); + bool receiveFrames(transfer_t transfer); + bool sendNack(void); + void init(void); + + bool transmitFrames(transfer_t); + unsigned char generateChkSum(void); + + public: + static const unsigned char XMO_NACK = 21; + static const unsigned char XMO_ACK = 6; + + static const unsigned char XMO_SOH = 1; + static const unsigned char XMO_EOT = 4; + static const unsigned char XMO_CAN = 0x18; + + static const int receiveDelay=7000; + static const int rcvRetryLimit = 10; + + + XModem(File &f, + FlowControlType commandFlow, + int (*recvChar)(ZSerial *ser, int), + void (*sendChar)(ZSerial *ser, char), + bool (*dataHandler)(File *xfile, unsigned long, char*, int)); + bool receive(); + bool transmit(); +}; + +static int xReceiveSerial(ZSerial *ser, int del) +{ + unsigned long end=micros() + (del * 1000L); + while(micros() < end) + { + serialOutDeque(); + if(ser->available() > 0) + { + int c=ser->read(); + logSerialIn(c); + return c; + } + yield(); + } + return -1; +} + +static void xSendSerial(ZSerial *ser, char c) +{ + ser->write((uint8_t)c); + ser->flush(); +} + +static bool xUDataHandler(File *xfile, unsigned long number, char *buf, int sz) +{ + for(int i=0;iwrite((uint8_t)buf[i]); + return true; +} + +static bool xDDataHandler(File *xfile, unsigned long number, char *buf, int sz) +{ + for(int i=0;iread(); + if(c<0) + { + if(i==0) + return false; + buf[i] = (char)26; + } + else + buf[i] = (char)c; + } + return true; +} + +static boolean xDownload(FlowControlType commandFlow, File &f, String &errors) +{ + XModem xmo(f,commandFlow, xReceiveSerial, xSendSerial, xDDataHandler); + bool result = xmo.transmit(); + return result; +} + +static boolean xUpload(FlowControlType commandFlow, File &f, String &errors) +{ + XModem xmo(f,commandFlow, xReceiveSerial, xSendSerial, xUDataHandler); + bool result = xmo.receive(); + return result; +} diff --git a/include/proto_zmodem.h b/include/proto_zmodem.h new file mode 100644 index 0000000..f99d519 --- /dev/null +++ b/include/proto_zmodem.h @@ -0,0 +1,504 @@ +/* + * zmodem.h + * zmodem constants + * (C) Mattheij Computer Service 1994 + * + * Date: Thu, 19 Nov 2015 10:10:02 +0100 + * From: Jacques Mattheij + * Subject: Re: zmodem license + * To: Stephen Hurd, Fernando Toledo + * CC: Rob Swindell + * + * Hello there to all of you, + * + * So, this email will then signify as the transfer of any and all rights I + * held up to this point with relation to the copyright of the zmodem + * package as released by me many years ago and all associated files to + * Stephen Hurd. Fernando Toledo and Rob Swindell are named as + * witnesses to this transfer. + * + * ... + * + * best regards, + * + * Jacques Mattheij + */ + +/* $Id: zmodem.h,v 1.55 2018/02/01 08:20:19 deuce Exp $ */ + +#ifndef _ZMODEM_H +#define _ZMODEM_H + +#define ZMODEM_FILE_SIZE_MAX 0xffffffff /* 32-bits, blame Chuck */ + +/* + * ascii constants + */ + +#define BOOL bool +#define BYTE uint8_t +#define uchar uint8_t +#define MAX_PATH 253 +#define NOINP -1 /* input buffer empty (incom only) */ +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define ZMO_DLE 0x10 +#define ZMO_XON 0x11 +#define ZMO_XOFF 0x13 +#define ZMO_CAN 0x18 + +#ifndef INT_TO_BOOL +#define INT_TO_BOOL(x) ((x)?ZTRUE:ZFALSE) +#endif + +#define ZFALSE 0 +#define ZTRUE 1 + +#define TERMINATE(str) str[sizeof(str)-1]=0 + +/* This is a bound-safe version of strcpy basically - only works with fixed-length arrays */ +#ifdef SAFECOPY_USES_SPRINTF +#define SAFECOPY(dst,src) sprintf(dst,"%.*s",(int)sizeof(dst)-1,src) +#else /* strncpy is faster */ +#define SAFECOPY(dst,src) (strncpy(dst,src,sizeof(dst)), TERMINATE(dst)) +#endif + +/* + * zmodem constants + */ + +#define ZBLOCKLEN 1024 /* "true" Zmodem max subpacket length */ + +#define ZMAXHLEN 0x10 /* maximum header information length */ +#define ZMAXSPLEN 0x400 /* maximum subpacket length */ + + +#define ZPAD 0x2a /* pad character; begins frames */ +#define ZDLE 0x18 /* ctrl-x zmodem escape */ +#define ZDLEE 0x58 /* escaped ZDLE */ + +#define ZBIN 0x41 /* binary frame indicator (CRC16) */ +#define ZHEX 0x42 /* hex frame indicator */ +#define ZBIN32 0x43 /* binary frame indicator (CRC32) */ +#define ZBINR32 0x44 /* run length encoded binary frame (CRC32) */ + +#define ZVBIN 0x61 /* binary frame indicator (CRC16) */ +#define ZVHEX 0x62 /* hex frame indicator */ +#define ZVBIN32 0x63 /* binary frame indicator (CRC32) */ +#define ZVBINR32 0x64 /* run length encoded binary frame (CRC32) */ + +#define ZRESC 0x7e /* run length encoding flag / escape character */ + +/* + * zmodem frame types + */ + +#define ZRQINIT 0x00 /* request receive init (s->r) */ +#define ZRINIT 0x01 /* receive init (r->s) */ +#define ZSINIT 0x02 /* send init sequence (optional) (s->r) */ +#define ZACK 0x03 /* ack to ZRQINIT ZRINIT or ZSINIT (s<->r) */ +#define ZFILE 0x04 /* file name (s->r) */ +#define ZSKIP 0x05 /* skip this file (r->s) */ +#define ZNAK 0x06 /* last packet was corrupted (?) */ +#define ZABORT 0x07 /* abort batch transfers (?) */ +#define ZFIN 0x08 /* finish session (s<->r) */ +#define ZRPOS 0x09 /* resume data transmission here (r->s) */ +#define ZDATA 0x0a /* data packet(s) follow (s->r) */ +#define ZEOF 0x0b /* end of file reached (s->r) */ +#define ZFERR 0x0c /* fatal read or write error detected (?) */ +#define ZCRC 0x0d /* request for file CRC and response (?) */ +#define ZCHALLENGE 0x0e /* security challenge (r->s) */ +#define ZCOMPL 0x0f /* request is complete (?) */ +#define ZCAN 0x10 /* pseudo frame; + other end cancelled session with 5* CAN */ +#define ZFREECNT 0x11 /* request free bytes on file system (s->r) */ +#define ZCOMMAND 0x12 /* issue command (s->r) */ +#define ZSTDERR 0x13 /* output data to stderr (??) */ + +/* + * ZDLE sequences + */ + +#define ZCRCE 0x68 /* CRC next, frame ends, header packet follows */ +#define ZCRCG 0x69 /* CRC next, frame continues nonstop */ +#define ZCRCQ 0x6a /* CRC next, frame continuous, ZACK expected */ +#define ZCRCW 0x6b /* CRC next, frame ends, ZACK expected */ +#define ZRUB0 0x6c /* translate to rubout 0x7f */ +#define ZRUB1 0x6d /* translate to rubout 0xff */ + +/* + * frame specific data. + * entries are prefixed with their location in the header array. + */ + +/* + * Byte positions within header array + */ + +#define FTYPE 0 /* frame type */ + +#define ZF0 4 /* First flags byte */ +#define ZF1 3 +#define ZF2 2 +#define ZF3 1 + +#define ZP0 1 /* Low order 8 bits of position */ +#define ZP1 2 +#define ZP2 3 +#define ZP3 4 /* High order 8 bits of file position */ + +/* + * ZRINIT frame + * zmodem receiver capability flags + */ + +#define ZF0_CANFDX 0x01 /* Receiver can send and receive true full duplex */ +#define ZF0_CANOVIO 0x02 /* receiver can receive data during disk I/O */ +#define ZF0_CANBRK 0x04 /* receiver can send a break signal */ +#define ZF0_CANCRY 0x08 /* Receiver can decrypt DONT USE */ +#define ZF0_CANLZW 0x10 /* Receiver can uncompress DONT USE */ +#define ZF0_CANFC32 0x20 /* Receiver can use 32 bit Frame Check */ +#define ZF0_ESCCTL 0x40 /* Receiver expects ctl chars to be escaped */ +#define ZF0_ESC8 0x80 /* Receiver expects 8th bit to be escaped */ + +#define ZF1_CANVHDR 0x01 /* Variable headers OK */ + +/* + * ZSINIT frame + * zmodem sender capability + */ + +#define ZF0_TESCCTL 0x40 /* Transmitter expects ctl chars to be escaped */ +#define ZF0_TESC8 0x80 /* Transmitter expects 8th bit to be escaped */ + +#define ZATTNLEN 0x20 /* Max length of attention string */ +#define ALTCOFF ZF1 /* Offset to alternate canit string, 0 if not used */ + +/* + * ZFILE frame + */ + +/* + * Conversion options one of these in ZF0 + */ + +#define ZF0_ZCBIN 1 /* Binary transfer - inhibit conversion */ +#define ZF0_ZCNL 2 /* Convert NL to local end of line convention */ +#define ZF0_ZCRESUM 3 /* Resume interrupted file transfer */ + +/* + * Management include options, one of these ored in ZF1 + */ + +#define ZF1_ZMSKNOLOC 0x80 /* Skip file if not present at rx */ +#define ZF1_ZMMASK 0x1f /* Mask for the choices below */ +#define ZF1_ZMNEWL 1 /* Transfer if source newer or longer */ +#define ZF1_ZMCRC 2 /* Transfer if different file CRC or length */ +#define ZF1_ZMAPND 3 /* Append contents to existing file (if any) */ +#define ZF1_ZMCLOB 4 /* Replace existing file */ +#define ZF1_ZMNEW 5 /* Transfer if source newer */ +#define ZF1_ZMDIFF 6 /* Transfer if dates or lengths different */ +#define ZF1_ZMPROT 7 /* Protect destination file */ +#define ZF1_ZMCHNG 8 /* Change filename if destination exists */ + +/* + * Transport options, one of these in ZF2 + */ + +#define ZF2_ZTNOR 0 /* no compression */ +#define ZF2_ZTLZW 1 /* Lempel-Ziv compression */ +#define ZF2_ZTRLE 3 /* Run Length encoding */ + +/* + * Extended options for ZF3, bit encoded + */ + +#define ZF3_ZCANVHDR 0x01 /* Variable headers OK */ + /* Receiver window size override */ +#define ZF3_ZRWOVR 0x04 /* byte position for receive window override/256 */ +#define ZF3_ZXSPARS 0x40 /* encoding for sparse file operations */ + +/* + * ZCOMMAND frame + */ + +#define ZF0_ZCACK1 0x01 /* Acknowledge, then do command */ + +typedef struct { + + BYTE rxd_header[ZMAXHLEN]; /* last received header */ + int rxd_header_len; /* last received header size */ + uint32_t rxd_header_pos; /* last received header position value */ + + /* + * receiver capability flags + * extracted from the ZRINIT frame as received + */ + + BOOL can_full_duplex; + BOOL can_overlap_io; + BOOL can_break; + BOOL can_fcs_32; + BOOL want_fcs_16; + BOOL escape_ctrl_chars; + BOOL escape_8th_bit; + + /* + * file management options. + * only one should be on + */ + + int management_newer; + int management_clobber; + int management_protect; + + /* from zmtx.c */ + + BYTE tx_data_subpacket[8192]; + BYTE rx_data_subpacket[8192]; /* zzap = 8192 */ + + char current_file_name[MAX_PATH+1]; + int64_t current_file_size; + int64_t current_file_pos; + time_t current_file_time; + unsigned current_file_num; + unsigned total_files; + int64_t total_bytes; + unsigned files_remaining; + int64_t bytes_remaining; + int64_t transfer_start_pos; + time_t transfer_start_time; + + int receive_32bit_data; + int use_crc16; + int32_t ack_file_pos; /* file position used in acknowledgement of correctly */ + /* received data subpackets */ + + int last_sent; + + int n_cans; + + /* Stuff added by RRS */ + + /* Status */ + BOOL cancelled; + BOOL local_abort; + BOOL file_skipped; + BOOL no_streaming; + BOOL frame_in_transit; + unsigned recv_bufsize; /* Receiver specified buffer size */ + int32_t crc_request; + unsigned errors; + unsigned consecutive_errors; + + /* Configuration */ + BOOL escape_telnet_iac; + unsigned init_timeout; + unsigned send_timeout; + unsigned recv_timeout; + unsigned crc_timeout; + unsigned max_errors; + unsigned block_size; + unsigned max_block_size; + int64_t max_file_size; /* 0 = unlimited */ + int *log_level; + /* error C2520: conversion from unsigned __int64 to double not implemented, use signed __int64 */ + void* cbdata; +} zmodem_t; + +class ZModem +{ +public: + ZModem(FS *zfs, void* cbdata); + ~ZModem(); + BOOL send_file( char* name, File* fp, BOOL request_init, time_t* start, uint64_t* bytes_sent); + int get_zfin(); + int recv_init(); + int lputs(void* unused, int level, const char* str); + int lprintf(int level, const char *fmt, ...); + int send_zabort(); + int send_zfin(); + int recv_files(const char* download_dir, uint64_t* bytes_received); + unsigned recv_file_data( File*, int64_t offset); + zmodem_t *zm=0; + ZSerial zserial; + FS *zfileSystem=0; +private: + char* ver(char *buf); + const char* source(void); + int rx(); + int tx(BYTE ch); + int send_ack( int32_t pos); + int send_nak(); + int send_zskip(); + int send_zrinit(); + int send_pos_header(int type, int32_t pos, BOOL hex); + int get_zrinit(); + BOOL get_crc( int32_t length, uint32_t* crc); + void parse_zrinit(); + void parse_zfile_subpacket(); + int recv_file_frame(File* fp); + int recv_header_and_check(); + int send_hex(uchar val); + int send_padded_zdle(); + int send_hex_header(unsigned char * p); + int send_bin32_header(unsigned char * p); + int send_bin16_header(unsigned char * p); + int send_bin_header(unsigned char * p); + int send_data32(uchar subpkt_type, unsigned char * p, size_t l); + int send_data16(uchar subpkt_type,unsigned char * p, size_t l); + int send_data(uchar subpkt_type, unsigned char * p, size_t l); + int send_data_subpkt(uchar subpkt_type, unsigned char * p, size_t l); + int data_waiting(unsigned timeout); + void recv_purge(); + void flush(); + int send_raw(unsigned char ch); + int send_esc(unsigned char c); + int recv_data32(unsigned char * p, unsigned maxlen, unsigned* l); + int recv_data16(register unsigned char* p, unsigned maxlen, unsigned* l); + int recv_data(unsigned char* p, size_t maxlen, unsigned* l, BOOL ack); + BOOL recv_subpacket(BOOL ack); + int recv_nibble(); + int recv_hex(); + int recv_raw(); + BOOL recv_bin16_header(); + BOOL recv_hex_header(); + BOOL recv_bin32_header(); + int recv_header_raw(int errors); + int recv_header(); + BOOL request_crc(int32_t length); + BOOL recv_crc(uint32_t* crc); + BOOL handle_zrpos(uint64_t* pos); + BOOL handle_zack(); + BOOL is_connected(); + BOOL is_cancelled(); + int send_from(File* fp, uint64_t pos, uint64_t* sent); + int send_znak(); + int send_zeof(uint32_t pos); + void progress(void* cbdata, int64_t current_pos); + int send_byte(void* unused, uchar ch, unsigned timeout); + int recv_byte(void* unused, unsigned timeout); /* seconds */ + ulong frame_pos(int type); + char* getfname(const char* path); +}; + +static ZModem *initZSerial(FS &fs, FlowControlType commandFlow) +{ + ZModem *modem = new ZModem(&SD, NULL); + modem->zserial.setFlowControlType(FCT_DISABLED); + if(commandFlow==FCT_RTSCTS) + modem->zserial.setFlowControlType(FCT_RTSCTS); + else + modem->zserial.setFlowControlType(FCT_NORMAL); + modem->zserial.setPetsciiMode(false); + modem->zserial.setXON(true); + return modem; +} + +static boolean zDownload(FlowControlType flow, FS &fs, String filePath, String &errors) +{ + time_t starttime = 0; + uint64_t bytes_sent=0; + BOOL success=ZFALSE; + char filePathC[MAX_PATH]; + File F; + + ZModem *modem = initZSerial(fs, flow); + + //static int send_files(char** fname, uint fnames) + F=modem->zfileSystem->open(filePath); + modem->zm->files_remaining = 1; + modem->zm->bytes_remaining = F.size(); + strcpy(filePathC,filePath.c_str()); + success=modem->send_file(filePathC, &F, ZTRUE, &starttime, &bytes_sent); + if(success) + modem->get_zfin(); + F.close(); + + modem->zserial.flushAlways(); + delete modem; + return (success==ZTRUE) && (modem->zm->cancelled==ZFALSE); +} + +static boolean zUpload(FlowControlType flow, FS &fs, String dirPath, String &errors) +{ + BOOL success=ZFALSE; + int i; + char str[MAX_PATH]; + File fp; + int err; + + ZModem *modem = initZSerial(fs,flow); + + //static int receive_files(char** fname_list, int fnames) + //TODO: loop might be necc around here, for multiple files? + i=modem->recv_init(); + if(modem->zm->cancelled || (i<0)) + { + delete modem; + return ZFALSE; + } + switch(i) { + case ZFILE: + //SAFECOPY(fname,zm.current_file_name); + //file_bytes = zm.current_file_size; + //ftime = zm.current_file_time; + //total_files = zm.files_remaining; + //total_bytes = zm.bytes_remaining; + break; + case ZFIN: + case ZCOMPL: + delete modem; + return ZTRUE; // was (!success) + default: + delete modem; + return ZFALSE; + } + + strcpy(str,dirPath.c_str()); + if(str[strlen(str)-1]!='/') + { + str[strlen(str)]='/'; + str[strlen(str)+1]=0; + } + strcpy(str+strlen(str),modem->zm->current_file_name); + + fp = modem->zfileSystem->open(str,FILE_WRITE); + if(!fp) + { + modem->lprintf(LOG_ERR,"Error %d creating %s",errno,str); + modem->send_zabort(); + //zmodem_send_zskip(); //TODO: for when we move to multiple files + //continue; + delete modem; + return ZFALSE; + } + err=modem->recv_file_data(&fp,0); + + if(err<=modem->zm->max_errors && !modem->zm->cancelled) + success=ZTRUE; + + if(success) + modem->send_zfin(); + + fp.close(); + if(modem->zm->local_abort) + { + modem->lprintf(LOG_ERR,"Locally aborted, sending cancel to remote"); + modem->send_zabort(); + delete modem; + return ZFALSE; + } + + modem->zserial.flushAlways(); + delete modem; + return (success == ZTRUE); +} +#endif diff --git a/include/rt_clock.h b/include/rt_clock.h new file mode 100644 index 0000000..e612bb5 --- /dev/null +++ b/include/rt_clock.h @@ -0,0 +1,174 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +static const char *TimeZones[243][2] PROGMEM = { {"UTC","0"}, + {"A","1"},{"CDT","10:30"},{"ACST","9:30"},{"ACT","-5"}, + {"ACT","9:30/10:30"},{"ACWST","8:45"},{"ADT","3"},{"ADT","-3"}, + {"AEDT","11"},{"AEST","10"},{"AET","11"},{"AFT","4:30"}, + {"AKDT","-8"},{"AKST","-9"},{"ALMT","6"},{"AMST","-3"},{"AMST","5"}, + {"AMT","-4"},{"AMT","4"},{"ANAST","12"},{"ANAT","12"},{"AQTT","5"}, + {"ART","-3"},{"AST","2"},{"AST","-4"},{"AT","-4/-3"},{"AWDT","9"}, + {"AWST","8"},{"AZOST","0"},{"AZOT","-1"},{"AZST","5"},{"AZT","4"}, + {"AOE","-12"},{"B","2"},{"BNT","8"},{"BOT","-4"},{"BRST","-2"}, + {"BRT","-3"},{"BST","6"},{"BST","11"},{"BST","1"},{"BTT","6"}, + {"C","3"},{"CAST","8"},{"CAT","2"},{"CCT","6:30"},{"CDT","-5"}, + {"CDT","-4"},{"CEST","2"},{"CET","1"},{"CHADT","13:45"}, + {"CHAST","12:45"},{"CHOST","9"},{"CHOT","8"},{"CHUT","10"}, + {"CIDST","-4"},{"CIST","-5"},{"CKT","-10"},{"CLST","-3"}, + {"CLT","-4"},{"COT","-5"},{"CST","-6"},{"CST","8"},{"CST","-5"}, + {"CT","-6/-5"},{"CVT","-1"},{"CXT","7"},{"ChST","10"},{"D","4"}, + {"DAVT","7"},{"DDUT","10"},{"E","5"},{"EASST","-5"},{"EAST","-6"}, + {"EAT","3"},{"ECT","-5"},{"EDT","-4"},{"EEST","3"},{"EET","2"}, + {"EGST","0"},{"EGT","-1"},{"EST","-5"},{"ET","-5/-4"},{"F","6"}, + {"FET","3"},{"FJST","13"},{"FJT","12"},{"FKST","-3"},{"FKT","-4"}, + {"FNT","-2"},{"G","7"},{"GALT","-6"},{"GAMT","-9"},{"GET","4"}, + {"GFT","-3"},{"GILT","12"},{"GMT","0"},{"GST","4"},{"GST","-2"}, + {"GYT","-4"},{"H","8"},{"HADT","-9"},{"HAST","-10"},{"HKT","8"}, + {"HOVST","8"},{"HOVT","7"},{"I","9"},{"ICT","7"},{"IDT","3"}, + {"IOT","6"},{"IRDT","4:30"},{"IRKST","9"},{"IRKT","8"}, + {"IRST","3:30"},{"IST","5:30"},{"IST","1"},{"IST","2"},{"JST","9"}, + {"K","10"},{"KGT","6"},{"KOST","11"},{"KRAST","8"},{"KRAT","7"}, + {"KST","9"},{"KUYT","4"},{"L","11"},{"LHDT","11"},{"LHST","10:30"}, + {"LINT","14"},{"M","12"},{"MAGST","12"},{"MAGT","11"},{"MART","-9:30"}, + {"MAWT","5"},{"MDT","-6"},{"MHT","12"},{"MMT","6:30"},{"MSD","4"}, + {"MSK","3"},{"MST","-7"},{"MT","-7/-6"},{"MUT","4"},{"MVT","5"}, + {"MYT","8"},{"N","-1"},{"NCT","11"},{"NDT","-2:30"},{"NFT","11"}, + {"NOVST","7"},{"NOVT","6"},{"NPT","5:45"},{"NRT","12"},{"NST","-3:30"}, + {"NUT","-11"},{"NZDT","13"},{"NZST","12"},{"O","-2"},{"OMSST","7"}, + {"OMST","6"},{"ORAT","5"},{"P","-3"},{"PDT","-7"},{"PET","-5"}, + {"PETST","12"},{"PETT","12"},{"PGT","10"},{"PHOT","13"},{"PHT","8"}, + {"PKT","5"},{"PMDT","-2"},{"PMST","-3"},{"PONT","11"},{"PST","-8"}, + {"PST","-8"},{"PT","-8/-7"},{"PWT","9"},{"PYST","-3"},{"PYT","-4"}, + {"PYT","8:30"},{"Q","-4"},{"QYZT","6"},{"R","-5"},{"RET","4"}, + {"ROTT","-3"},{"S","-6"},{"SAKT","11"},{"SAMT","4"},{"SAST","2"}, + {"SBT","11"},{"SCT","4"},{"SGT","8"},{"SRET","11"},{"SRT","-3"}, + {"SST","-11"},{"SYOT","3"},{"T","-7"},{"TAHT","-10"},{"TFT","5"}, + {"TJT","5"},{"TKT","13"},{"TLT","9"},{"TMT","5"},{"TOST","14"}, + {"TOT","13"},{"TRT","3"},{"TVT","12"},{"U","-8"},{"ULAST","9"}, + {"ULAT","8"},{"UYST","-2"},{"UYT","-3"},{"UZT","5"}, + {"V","-9"},{"VET","-4"},{"VLAST","11"},{"VLAT","10"},{"VOST","6"}, + {"VUT","11"},{"W","-10"},{"WAKT","12"},{"WARST","-3"},{"WAST","2"}, + {"WAT","1"},{"WEST","1"},{"WET","0"},{"WFT","12"},{"WGST","-2"}, + {"WGT","-3"},{"WIB","7"},{"WIT","9"},{"WITA","8"},{"WST","14"}, + {"WST","1"},{"WT","0"},{"X","-11"},{"Y","-12"},{"YAKST","10"}, + {"YAKT","9"},{"YAPT","10"},{"YEKST","6"},{"YEKT","5"},{"Z","0"} +}; + +class DateTimeClock +{ +public: + DateTimeClock(uint32_t epochSecs); + DateTimeClock(); + DateTimeClock(int year, int month, int day, int hour, int min, int sec, int millis); +protected: + uint16_t year=0; + uint8_t month=0; + uint8_t day=0; + uint8_t hour=0; + uint8_t min=0; + uint8_t sec=0; + uint16_t milsec=0; + char str[55]; + + bool isLeapYear(); + uint8_t getDaysInThisMonth(); +public: + int getYear(); + void setYear(int y); + void addYear(uint32_t y); + int getMonth(); + void setMonth(int m); + void addMonth(uint32_t m); + int getDay(); + void setDay(int d); + void addDay(uint32_t d); + int getHour(); + void setHour(int h); + void addHour(uint32_t h); + int getMinute(); + void setMinute(int mm); + void addMinute(uint32_t mm); + int getSecond(); + void setSecond(int s); + void addSecond(uint32_t s); + int getMillis();; + void setMillis(int s); + void addMillis(uint64_t s); + + void setByUnixEpoch(uint32_t unisex); + uint32_t getUnixEpoch(); + + int getDoWNumber(); + const char *getDoW(); + + bool isInStandardTime(); + bool isInDaylightSavingsTime(); + + void setTime(DateTimeClock &clock); +}; + +class RealTimeClock : DateTimeClock +{ +public: + RealTimeClock(uint32_t epochSecs); + RealTimeClock(); + RealTimeClock(int year, int month, int day, int hour, int min, int sec, int millis); + + void tick(); + + bool isTimeSet(); + + bool reset(); + + int getTimeZoneCode(); + void setTimeZoneCode(int val); + bool setTimeZone(String str); + + String getFormat(); + void setFormat(String fmt); + + String getNtpServerHost(); + void setNtpServerHost(String newHost); + + bool isDisabled(); + void setDisabled(bool tf); + + void forceUpdate(); + + DateTimeClock &getCurrentTime(); + String getCurrentTimeFormatted(); + + // should be private +private: + bool disabled = false; + DateTimeClock adjClock; + WiFiUDP udp; + bool udpStarted = false; + uint32_t lastMillis = 0; + uint32_t nextNTPMillis = 0; + int32_t ntpPeriodMillis = 60 * 1000; // every minute + int32_t ntpPeriodLongMillis = 5 * 60 * 60 * 1000; // every 5 hours + uint8_t tzCode = 0; + String format="%M/%d/%yyyy %h:%mm:%ss%aa %z"; + String ntpServerName = "time.nist.gov"; + + void startUdp(); + bool sendTimeRequest(); +}; + + diff --git a/include/serout.h b/include/serout.h new file mode 100644 index 0000000..56d65cd --- /dev/null +++ b/include/serout.h @@ -0,0 +1,91 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifndef ZHEADER_SEROUT_H +#define ZHEADER_SEROUT_H + +#define DBG_BYT_CTR 20 + +#define SER_WRITE_BUFSIZE 4096 + +enum FlowControlType +{ + FCT_RTSCTS=0, + FCT_NORMAL=1, + FCT_AUTOOFF=2, + FCT_MANUAL=3, + FCT_DISABLED=4, + FCT_INVALID=5 +}; + +static bool enableRtsCts = true; +#ifdef ZIMODEM_ESP32 +# define SER_BUFSIZE 0x7F +#else +# define SER_BUFSIZE 128 +#endif +static uint8_t TBUF[SER_WRITE_BUFSIZE]; +static char FBUF[256]; +static int TBUFhead=0; +static int TBUFtail=0; +static int serialDelayMs = 0; + +static void serialDirectWrite(uint8_t c); +static void serialOutDeque(); +static int serialOutBufferBytesRemaining(); +static void clearSerialOutBuffer(); + +class ZSerial : public Stream +{ + private: + bool petsciiMode = false; + FlowControlType flowControlType=DEFAULT_FCT; + bool XON_STATE=true; + void enqueByte(uint8_t c); + public: + ZSerial(); + void setPetsciiMode(bool petscii); + bool isPetsciiMode(); + void setFlowControlType(FlowControlType type); + FlowControlType getFlowControlType(); + void setXON(bool isXON); + bool isXON(); + bool isSerialOut(); + bool isSerialHalted(); + bool isSerialCancelled(); + bool isPacketOut(); + int getConfigFlagBitmap(); + + void prints(String str); + void prints(const char *expr); + void printc(const char c); + void printc(uint8_t c); + virtual size_t write(uint8_t c); + size_t write(uint8_t *buf, int bufSz); + void printb(uint8_t c); + void printd(double f); + void printi(int i); + void printf(const char* format, ...); + void flush(); + void flushAlways(); + int availableForWrite(); + char drainForXonXoff(); + + virtual int available(); + virtual int read(); + virtual int peek(); +}; + +#endif diff --git a/include/stringstream.h b/include/stringstream.h new file mode 100644 index 0000000..ecdf342 --- /dev/null +++ b/include/stringstream.h @@ -0,0 +1,27 @@ +#ifndef _STRING_STREAM_H_INCLUDED_ +#define _STRING_STREAM_H_INCLUDED_ + +class StringStream : public Stream +{ +public: + StringStream(const String &s) + { + str = s; + position = 0; + } + + // Stream methods + virtual int available() { return str.length() - position; } + virtual int read() { return position < str.length() ? str[position++] : -1; } + virtual int peek() { return position < str.length() ? str[position] : -1; } + virtual void flush() { }; + // Print methods + virtual size_t write(uint8_t c) { str += (char)c; return 1;}; + +private: + String str; + int length; + int position; +}; + +#endif // _STRING_STREAM_H_INCLUDED_ diff --git a/include/wificlientnode.h b/include/wificlientnode.h new file mode 100644 index 0000000..4283b3f --- /dev/null +++ b/include/wificlientnode.h @@ -0,0 +1,123 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define PACKET_BUF_SIZE 256 + +#ifdef ZIMODEM_ESP32 +# include +#endif +#ifdef INCLUDE_SSH +# include "wifisshclient.h" +#endif + +static WiFiClient *createWiFiClient(bool SSL) +{ +#ifdef ZIMODEM_ESP32 + if(SSL) + { + WiFiClientSecure *c = new WiFiClientSecure(); + c->setInsecure(); + return c; + } + else +#else + //WiFiClientSecure *c = new WiFiClientSecure(); + //c->setInsecure(); +#endif + return new WiFiClient(); +} + +typedef struct Packet +{ + uint8_t num = 0; + uint16_t len = 0; + uint8_t buf[PACKET_BUF_SIZE] = {0}; +}; + +class WiFiClientNode : public Stream +{ + private: + void finishConnectionLink(); + int flushOverflowBuffer(); + void fillUnderflowBuf(); + WiFiClient client; + WiFiClient *clientPtr; + bool answered=true; + int ringsRemain=0; + unsigned long nextRingMillis = 0; + unsigned long nextDisconnect = 0; + void constructNode(); + void constructNode(char *hostIp, int newport, int flagsBitmap, int ringDelay); + void constructNode(char *hostIp, int newport, char *username, char *password, int flagsBitmap, int ringDelay); + + public: + int id=0; + char *host; + int port; + bool wasConnected=false; + bool serverClient=false; + int flagsBitmap = 0; + char *delimiters = NULL; + char *maskOuts = NULL; + char *stateMachine = NULL; + char *machineState = NULL; + String machineQue = ""; + + uint8_t nextPacketNum=1; + uint8_t blankPackets=0; + struct Packet lastPacket[3]; // 0 = current buf, 1&2 are back-cache bufs + //struct Packet underflowBuf; // underflows no longer handled this way + WiFiClientNode *next = null; + + WiFiClientNode(char *hostIp, int newport, int flagsBitmap); + WiFiClientNode(char *hostIp, int newport, char *username, char *password, int flagsBitmap); + WiFiClientNode(WiFiClient newClient, int flagsBitmap, int ringDelay); + ~WiFiClientNode(); + bool isConnected(); + + FlowControlType getFlowControl(); + bool isPETSCII(); + bool isEcho(); + bool isTelnet(); + + bool isAnswered(); + void answer(); + int ringsRemaining(int delta); + unsigned long nextRingTime(long delta); + void markForDisconnect(); + bool isMarkedForDisconnect(); + + bool isDisconnectedOnStreamExit(); + void setDisconnectOnStreamExit(bool tf); + + void setNoDelay(bool tf); + + size_t write(uint8_t c); + size_t write(const uint8_t *buf, size_t size); + void print(String s); + int read(); + int peek(); + void flush(); + void flushAlways(); + int available(); + int read(uint8_t *buf, size_t size); + String readLine(unsigned int timeout); + + static int getNumOpenWiFiConnections(); + static void checkForAutoDisconnections(); +}; + + diff --git a/include/wifiservernode.h b/include/wifiservernode.h new file mode 100644 index 0000000..b332e91 --- /dev/null +++ b/include/wifiservernode.h @@ -0,0 +1,52 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +static int WiFiNextClientId = 0; + +class WiFiServerSpec +{ + public: + int port; + int id; + int flagsBitmap = 0; + char *delimiters = NULL; + char *maskOuts = NULL; + char *stateMachine = NULL; + + WiFiServerSpec(); + WiFiServerSpec(WiFiServerSpec ©); + ~WiFiServerSpec(); + + WiFiServerSpec& operator=(const WiFiServerSpec&); +}; + +class WiFiServerNode : public WiFiServerSpec +{ + public: + WiFiServer *server; + WiFiServerNode *next = null; + + WiFiServerNode(int port, int flagsBitmap); + bool hasClient(); + ~WiFiServerNode(); + + static WiFiServerNode *FindServer(int port); + static void DestroyAllServers(); + static bool ReadWiFiServer(File &f, WiFiServerSpec &node); + static void SaveWiFiServers(); + static void RestoreWiFiServers(); +}; + diff --git a/include/wifisshclient.h b/include/wifisshclient.h new file mode 100644 index 0000000..e448882 --- /dev/null +++ b/include/wifisshclient.h @@ -0,0 +1,79 @@ +#ifdef INCLUDE_SSH +/* + Copyright 2023-2023 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#include "Arduino.h" +#include "IPAddress.h" +#include +#include "src/libssh2/libssh2_config.h" +#include "src/libssh2/libssh2.h" + +class WiFiSSHClient : public WiFiClient +{ +protected: + String _username = ""; + String _password = ""; + libssh2_socket_t sock = null; + LIBSSH2_SESSION *session = null; + LIBSSH2_CHANNEL *channel = null; + static const size_t INTERNAL_BUF_SIZE = 1024; + size_t ibufSz = 0; + char ibuf[INTERNAL_BUF_SIZE]; + + bool finishLogin(); + void closeSSH(); + void intern_buffer_fill(); + +public: + WiFiSSHClient(); + ~WiFiSSHClient(); + int connect(IPAddress ip, uint16_t port) override; + int connect(IPAddress ip, uint16_t port, int32_t timeout_ms) override; + int connect(const char *host, uint16_t port) override; + int connect(const char *host, uint16_t port, int32_t timeout_ms) override; + void setLogin(String username, String password); + int peek() override; + size_t write(uint8_t data) override; + size_t write(const uint8_t *buf, size_t size) override; + int available() override; + int read() override; + int read(uint8_t *buf, size_t size) override; + void flush() {} + void stop() override; + uint8_t connected() override; + int fd() const override; + + operator bool() + { + return connected(); + } + WiFiSSHClient &operator=(const WiFiSSHClient &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const WiFiSSHClient &); + bool operator!=(const WiFiSSHClient &rhs) + { + return !this->operator==(rhs); + }; + +private: +}; +#endif diff --git a/include/zbrowser.h b/include/zbrowser.h new file mode 100644 index 0000000..e512014 --- /dev/null +++ b/include/zbrowser.h @@ -0,0 +1,61 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef INCLUDE_SD_SHELL +class ZBrowser : public ZMode +{ + private: + enum ZBrowseState + { + ZBROW_MAIN=0, + } currState; + + ZSerial serial; + + void switchBackToCommandMode(); + String makePath(String addendum); + String fixPathNoSlash(String path); + String stripDir(String path); + String stripFilename(String path); + String stripArgs(String line, String &argLetters); + String cleanOneArg(String line); + String cleanFirstArg(String line); + String cleanRemainArg(String line); + bool isMask(String mask); + bool matches(String fname, String mask); + void makeFileList(String ***l, int *n, String p, String mask, bool recurse); + void deleteFile(String fname, String mask, bool recurse); + void showDirectory(String path, String mask, String prefix, bool recurse); + void copyFiles(String source, String mask, String target, bool recurse, bool overwrite); + + FTPHost *ftpHost = 0; + bool showMenu; + bool savedEcho; + String path="/"; + String EOLN; + char EOLNC[5]; + unsigned long lastNumber; + String lastString; + + public: + ~ZBrowser(); + void switchTo(); + void serialIncoming(); + void loop(); + void init(); + void doModeCommand(String &line); +}; +#endif diff --git a/include/zcommand.h b/include/zcommand.h new file mode 100644 index 0000000..920dfff --- /dev/null +++ b/include/zcommand.h @@ -0,0 +1,202 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +const int MAX_COMMAND_SIZE=256; +static const char *CONFIG_FILE_OLD = "/zconfig.txt"; +static const char *CONFIG_FILE = "/zconfig_v2.txt"; +#define ZI_STATE_MACHINE_LEN 7 +#define DEFAULT_TERMTYPE "Zimodem" +#define DEFAULT_BUSYMSG "\r\nBUSY\r\n7\r\n" + +static void parseHostInfo(uint8_t *vbuf, char **hostIp, int *port, char **username, char **password); +static bool validateHostInfo(uint8_t *vbuf); + +enum ZResult +{ + ZOK, + ZERROR, + ZCONNECT, + ZNOCARRIER, + ZNOANSWER, + ZIGNORE, + ZIGNORE_SPECIAL +}; + +enum ConfigOptions +{ + CFG_WIFISSI=0, + CFG_WIFIPW=1, + CFG_BAUDRATE=2, + CFG_EOLN=3, + CFG_FLOWCONTROL=4, + CFG_ECHO=5, + CFG_RESP_SUPP=6, + CFG_RESP_NUM=7, + CFG_RESP_LONG=8, + CFG_PETSCIIMODE=9, + CFG_DCDMODE=10, + CFG_UART=11, + CFG_CTSMODE=12, + CFG_RTSMODE=13, + CFG_DCDPIN=14, + CFG_CTSPIN=15, + CFG_RTSPIN=16, + CFG_S0_RINGS=17, + CFG_S41_STREAM=18, + CFG_S60_LISTEN=19, + CFG_RIMODE=20, + CFG_DTRMODE=21, + CFG_DSRMODE=22, + CFG_RIPIN=23, + CFG_DTRPIN=24, + CFG_DSRPIN=25, + CFG_TIMEZONE=26, + CFG_TIMEFMT=27, + CFG_TIMEURL=28, + CFG_HOSTNAME=29, + CFG_PRINTDELAYMS=30, + CFG_PRINTSPEC=31, + CFG_TERMTYPE=32, + CFG_STATIC_IP=33, + CFG_STATIC_DNS=34, + CFG_STATIC_GW=35, + CFG_STATIC_SN=36, + CFG_BUSYMSG=37, + CFG_S62_TELNET=38, + CFG_LAST=38 +}; + +const ConfigOptions v2HexCfgs[] = { CFG_WIFISSI, CFG_WIFIPW, CFG_TIMEZONE, CFG_TIMEFMT, CFG_TIMEURL, + CFG_PRINTSPEC, CFG_BUSYMSG, CFG_HOSTNAME, CFG_TERMTYPE, (ConfigOptions)255 }; + +enum BinType +{ + BTYPE_NORMAL=0, + BTYPE_HEX=1, + BTYPE_DEC=2, + BTYPE_NORMAL_NOCHK=3, + BTYPE_NORMAL_PLUS=4, + BTYPE_HEX_PLUS=5, + BTYPE_DEC_PLUS=6, + BTYPE_INVALID=7 +}; + +class ZCommand : public ZMode +{ + friend class WiFiClientNode; + friend class ZConfig; + friend class ZBrowser; +#ifdef INCLUDE_IRCC + friend class ZIRCMode; +#endif + + private: + char CRLF[4]; + char LFCR[4]; + char LF[2]; + char CR[2]; + char BS=8; + char ringCounter = 1; + + ZSerial serial; + bool packetXOn = true; + BinType binType = BTYPE_NORMAL; + uint8_t nbuf[MAX_COMMAND_SIZE]; + char hbuf[MAX_COMMAND_SIZE]; + int eon=0; + int lastServerClientId = 0; + WiFiClientNode *current = null; + bool autoStreamMode=false; + bool telnetSupport=true; + bool preserveListeners=false; + unsigned long lastNonPlusTimeMs = 0; + unsigned long currentExpiresTimeMs = 0; + char *tempDelimiters = NULL; + char *tempMaskOuts = NULL; + char *tempStateMachine = NULL; + char *delimiters = NULL; + char *maskOuts = NULL; + char *stateMachine = NULL; + char *machineState = NULL; + String machineQue = ""; + String previousCommand = ""; + WiFiClientNode *nextConn=null; + int lastPacketId = -1; + + byte CRC8(const byte *data, byte len); + + void showInitMessage(); + bool readSerialStream(); + void clearPlusProgress(); + bool checkPlusEscape(); + String getNextSerialCommand(); + ZResult doSerialCommand(); + void setConfigDefaults(); + void parseConfigOptions(String configArguments[]); + void setOptionsFromSavedConfig(String configArguments[]); + void reSaveConfig(); + void reSendLastPacket(WiFiClientNode *conn, uint8_t which); + void acceptNewConnection(); + void headerOut(const int channel, const int num, const int sz, const int crc8); + void sendConnectionNotice(int nodeId); + void sendNextPacket(); + void connectionArgs(WiFiClientNode *c); + void updateAutoAnswer(); + uint8_t *doStateMachine(uint8_t *buf, uint16_t *bufLen, char **machineState, String *machineQue, char *stateMachine); + uint8_t *doMaskOuts(uint8_t *buf, uint16_t *bufLen, char *maskOuts); + ZResult doWebDump(Stream *in, int len, const bool cacheFlag); + ZResult doWebDump(const char *filename, const bool cache); + + ZResult doResetCommand(); + ZResult doNoListenCommand(); + ZResult doBaudCommand(int vval, uint8_t *vbuf, int vlen); + ZResult doTransmitCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers, int *crc8); + ZResult doLastPacket(int vval, uint8_t *vbuf, int vlen, bool isNumber); + ZResult doConnectCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers); + ZResult doWiFiCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers); + ZResult doDialStreamCommand(unsigned long vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers); + ZResult doPhonebookCommand(unsigned long vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers); + ZResult doAnswerCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers); + ZResult doHangupCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber); + ZResult doEOLNCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber); + ZResult doInfoCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber); + ZResult doWebStream(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *filename, bool cache); + ZResult doUpdateFirmware(int vval, uint8_t *vbuf, int vlen, bool isNumber); + ZResult doTimeZoneSetupCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber); + + public: + int packetSize = 127; + bool suppressResponses; + bool numericResponses; + bool longResponses; + boolean doEcho; + String EOLN; + char EC='+'; + char ECS[32]; + + ZCommand(); + void loadConfig(); + + FlowControlType getFlowControlType(); + int getConfigFlagBitmap(); + + void sendOfficialResponse(ZResult res); + void serialIncoming(); + void loop(); + void reset(); +}; + + diff --git a/include/zconfigmode.h b/include/zconfigmode.h new file mode 100644 index 0000000..e018d32 --- /dev/null +++ b/include/zconfigmode.h @@ -0,0 +1,67 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +class ZConfig : public ZMode +{ + private: + enum ZConfigMenu + { + ZCFGMENU_MAIN=0, + ZCFGMENU_NUM=1, + ZCFGMENU_ADDRESS=2, + ZCFGMENU_OPTIONS=3, + ZCFGMENU_WIMENU=4, + ZCFGMENU_WIFIPW=5, + ZCFGMENU_WICONFIRM=6, + ZCFGMENU_FLOW=7, + ZCFGMENU_BBSMENU=8, + ZCFGMENU_NEWPORT=9, + ZCFGMENU_NEWHOST=10, + ZCFGMENU_NOTES=11, + ZCFGMENU_NETMENU=12, + ZCFGMENU_SUBNET=13, + ZCFGMENU_NEWPRINT=14 + } currState; + + ZSerial serial; // storage for serial settings only + + void switchBackToCommandMode(); + void doModeCommand(); + bool showMenu; + bool savedEcho; + String EOLN; + const char *EOLNC; + unsigned long lastNumber; + String lastAddress; + String lastOptions; + String lastNotes; + WiFiServerSpec serverSpec; + bool newListen; + bool useDHCP; + bool settingsChanged=false; + char netOpt = ' '; + int lastNumNetworks=0; + IPAddress lastIP; + IPAddress lastDNS; + IPAddress lastGW; + IPAddress lastSN; + + public: + void switchTo(); + void serialIncoming(); + void loop(); +}; + diff --git a/include/zhostcmmode.h b/include/zhostcmmode.h new file mode 100644 index 0000000..ed64902 --- /dev/null +++ b/include/zhostcmmode.h @@ -0,0 +1,34 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_HOSTCM +#include "proto_hostcm.h" + +class ZHostCMMode : public ZMode +{ + private: + void switchBackToCommandMode(); + HostCM *proto = 0; + + public: + void switchTo(); + void serialIncoming(); + void loop(); +}; + +#endif +#endif diff --git a/include/zircmode.h b/include/zircmode.h new file mode 100644 index 0000000..95dc23d --- /dev/null +++ b/include/zircmode.h @@ -0,0 +1,54 @@ +/* + * zircmode.h + * + * Created on: May 18, 2022 + * Author: Bo Zimmerman + */ + +#ifdef INCLUDE_IRCC +class ZIRCMode: public ZMode +{ +private: + ZSerial serial; // storage for serial settings only + bool showMenu; + bool savedEcho; + String EOLN; + const char *EOLNC; + WiFiClientNode *current = null; + unsigned long lastNumber; + unsigned long timeout=0; + String buf; + String nick; + String lastAddress; + String lastOptions; + String lastNotes; + String channelName; + bool joinReceived; + enum ZIRCMenu + { + ZIRCMENU_MAIN=0, + ZIRCMENU_NICK=1, + ZIRCMENU_ADDRESS=2, + ZIRCMENU_NUM=3, + ZIRCMENU_NOTES=4, + ZIRCMENU_OPTIONS=5, + ZIRCMENU_COMMAND=6 + } currState; + enum ZIRCState + { + ZIRCSTATE_WAIT=0, + ZIRCSTATE_COMMAND=1 + } ircState; + + void switchBackToCommandMode(); + void doIRCCommand(); + void loopMenuMode(); + void loopCommandMode(); + +public: + void switchTo(); + void serialIncoming(); + void loop(); +}; + +#endif /* INCLUDE_IRCC */ diff --git a/include/zprint.h b/include/zprint.h new file mode 100644 index 0000000..207240d --- /dev/null +++ b/include/zprint.h @@ -0,0 +1,66 @@ +/* + Copyright 2020-2020 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +enum PrintPayloadType +{ + PETSCII, + ASCII, + RAW +}; + +static unsigned int DEFAULT_DELAY_MS = 5000; + +class ZPrint : public ZMode +{ + private: + WiFiClientNode *wifiSock = null; + File tfile; + Stream *outStream = null; + unsigned int timeoutDelayMs = DEFAULT_DELAY_MS; + char *lastPrinterSpec = 0; + unsigned long currentExpiresTimeMs = 0; + unsigned long nextFlushMs = 0; + PrintPayloadType payloadType = PETSCII; + unsigned long lastNonPlusTimeMs = 0; + int plussesInARow=0; + size_t pdex=0; + size_t coldex=0; + char pbuf[258]; + ZSerial serial; + char lastLastC = 0; + char lastC = 0; + short jobNum = 0; + + size_t writeStr(char *s); + size_t writeChunk(char *s, int len); + void switchBackToCommandMode(bool error); + ZResult finishSwitchTo(char *hostIp, char *req, int port, bool doSSL); + void announcePrintJob(const char *hostIp, const int port, const char *req); + + public: + + ZResult switchTo(char *vbuf, int vlen, bool petscii); + ZResult switchToPostScript(char *prefix); + void setLastPrinterSpec(const char *spec); + bool testPrinterSpec(const char *vbuf, int vlen, bool petscii); + char *getLastPrinterSpec(); + void setTimeoutDelayMs(int ms); + int getTimeoutDelayMs(); + + void serialIncoming(); + void loop(); +}; + diff --git a/include/zslipmode.h b/include/zslipmode.h new file mode 100644 index 0000000..1c4d059 --- /dev/null +++ b/include/zslipmode.h @@ -0,0 +1,34 @@ +/* + * zslipmode.h + * + * Created on: May 17, 2022 + * Author: Bo Zimmerman + */ + +#ifdef INCLUDE_SLIP +extern "C" { +#include "lwip/raw.h" +#include "slipif.h" +} + +static ZSerial sserial; +class ZSLIPMode: public ZMode +{ +private: + void switchBackToCommandMode(); + String inPacket; + bool started=false; + bool escaped=false; + raw_pcb *_pcb = 0; + +public: + static const char SLIP_END = '\xc0'; + static const char SLIP_ESC = '\xdb'; + static const char SLIP_ESC_END = '\xdc'; + static const char SLIP_ESC_ESC = '\xdd'; + void switchTo(); + void serialIncoming(); + void loop(); +}; + +#endif /* INCLUDE_SLIP_ */ diff --git a/include/zstream.h b/include/zstream.h new file mode 100644 index 0000000..47b90eb --- /dev/null +++ b/include/zstream.h @@ -0,0 +1,51 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define ZSTREAM_ESC_BUF_MAX 10 + +class ZStream : public ZMode +{ + private: + WiFiClientNode *current = null; + unsigned long lastNonPlusTimeMs = 0; + unsigned long currentExpiresTimeMs = 0; + unsigned long nextFlushMs = 0; + int plussesInARow = 0; + ZSerial serial; + int lastDTR = 0; + uint8_t escBuf[ZSTREAM_ESC_BUF_MAX]; + unsigned long nextAlarm = millis() + 5000; + + void switchBackToCommandMode(bool logout); + void socketWrite(uint8_t c); + void socketWrite(uint8_t *buf, uint8_t len); + void baudDelay(); + + bool isPETSCII(); + bool isEcho(); + FlowControlType getFlowControl(); + bool isTelnet(); + bool isDisconnectedOnStreamExit(); + + + public: + + void switchTo(WiFiClientNode *conn); + + void serialIncoming(); + void loop(); +}; + diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/libssh2/agent.h b/lib/libssh2/agent.h new file mode 100644 index 0000000..8dfaa03 --- /dev/null +++ b/lib/libssh2/agent.h @@ -0,0 +1,114 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_AGENT_H +#define __LIBSSH2_AGENT_H +/* + * Copyright (c) 2009 by Daiki Ueno + * Copyright (C) 2010-2014 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" +#include "session.h" +#ifdef WIN32 +#include +#endif + +/* non-blocking mode on agent connection is not yet implemented, but + for future use. */ +typedef enum { + agent_NB_state_init = 0, + agent_NB_state_request_created, + agent_NB_state_request_length_sent, + agent_NB_state_request_sent, + agent_NB_state_response_length_received, + agent_NB_state_response_received +} agent_nonblocking_states; + +typedef struct agent_transaction_ctx { + unsigned char *request; + size_t request_len; + unsigned char *response; + size_t response_len; + agent_nonblocking_states state; + size_t send_recv_total; +} *agent_transaction_ctx_t; + +typedef int (*agent_connect_func)(LIBSSH2_AGENT *agent); +typedef int (*agent_transact_func)(LIBSSH2_AGENT *agent, + agent_transaction_ctx_t transctx); +typedef int (*agent_disconnect_func)(LIBSSH2_AGENT *agent); + +struct agent_publickey { + struct list_node node; + + /* this is the struct we expose externally */ + struct libssh2_agent_publickey external; +}; + +struct agent_ops { + agent_connect_func connect; + agent_transact_func transact; + agent_disconnect_func disconnect; +}; + +struct _LIBSSH2_AGENT +{ + LIBSSH2_SESSION *session; /* the session this "belongs to" */ + + libssh2_socket_t fd; + + struct agent_ops *ops; + + struct agent_transaction_ctx transctx; + struct agent_publickey *identity; + struct list_head head; /* list of public keys */ + + char *identity_agent_path; /* Path to a custom identity agent socket */ + +#ifdef WIN32 + OVERLAPPED overlapped; + HANDLE pipe; + BOOL pending_io; +#endif +}; + +#ifdef WIN32 +extern struct agent_ops agent_ops_openssh; +#endif + +#endif /* __LIBSSH2_AGENT_H */ +#endif diff --git a/lib/libssh2/bcrypt_pbkdf.c b/lib/libssh2/bcrypt_pbkdf.c new file mode 100644 index 0000000..e05a362 --- /dev/null +++ b/lib/libssh2/bcrypt_pbkdf.c @@ -0,0 +1,185 @@ +#if defined(ESP32) +/* $OpenBSD: bcrypt_pbkdf.c,v 1.4 2013/07/29 00:55:53 tedu Exp $ */ +/* + * Copyright (c) 2013 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef HAVE_BCRYPT_PBKDF + +#include "libssh2_priv.h" +#include +#include +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "blf.h" + +#define MINIMUM(a,b) (((a) < (b)) ? (a) : (b)) + +/* + * pkcs #5 pbkdf2 implementation using the "bcrypt" hash + * + * The bcrypt hash function is derived from the bcrypt password hashing + * function with the following modifications: + * 1. The input password and salt are preprocessed with SHA512. + * 2. The output length is expanded to 256 bits. + * 3. Subsequently the magic string to be encrypted is lengthened and modified + * to "OxychromaticBlowfishSwatDynamite" + * 4. The hash function is defined to perform 64 rounds of initial state + * expansion. (More rounds are performed by iterating the hash.) + * + * Note that this implementation pulls the SHA512 operations into the caller + * as a performance optimization. + * + * One modification from official pbkdf2. Instead of outputting key material + * linearly, we mix it. pbkdf2 has a known weakness where if one uses it to + * generate (i.e.) 512 bits of key material for use as two 256 bit keys, an + * attacker can merely run once through the outer loop below, but the user + * always runs it twice. Shuffling output bytes requires computing the + * entirety of the key material to assemble any subkey. This is something a + * wise caller could do; we just do it for you. + */ + +#define BCRYPT_BLOCKS 8 +#define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) + +static void +bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +{ + blf_ctx state; + uint8_t ciphertext[BCRYPT_HASHSIZE] = { + 'O', 'x', 'y', 'c', 'h', 'r', 'o', 'm', 'a', 't', 'i', 'c', + 'B', 'l', 'o', 'w', 'f', 'i', 's', 'h', + 'S', 'w', 'a', 't', + 'D', 'y', 'n', 'a', 'm', 'i', 't', 'e' }; + uint32_t cdata[BCRYPT_BLOCKS]; + int i; + uint16_t j; + uint16_t shalen = SHA512_DIGEST_LENGTH; + + /* key expansion */ + Blowfish_initstate(&state); + Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen); + for(i = 0; i < 64; i++) { + Blowfish_expand0state(&state, sha2salt, shalen); + Blowfish_expand0state(&state, sha2pass, shalen); + } + + /* encryption */ + j = 0; + for(i = 0; i < BCRYPT_BLOCKS; i++) + cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), + &j); + for(i = 0; i < 64; i++) + blf_enc(&state, cdata, BCRYPT_BLOCKS / 2); + + /* copy out */ + for(i = 0; i < BCRYPT_BLOCKS; i++) { + out[4 * i + 3] = (cdata[i] >> 24) & 0xff; + out[4 * i + 2] = (cdata[i] >> 16) & 0xff; + out[4 * i + 1] = (cdata[i] >> 8) & 0xff; + out[4 * i + 0] = cdata[i] & 0xff; + } + + /* zap */ + _libssh2_explicit_zero(ciphertext, sizeof(ciphertext)); + _libssh2_explicit_zero(cdata, sizeof(cdata)); + _libssh2_explicit_zero(&state, sizeof(state)); +} + +int +bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, + size_t saltlen, + uint8_t *key, size_t keylen, unsigned int rounds) +{ + uint8_t sha2pass[SHA512_DIGEST_LENGTH]; + uint8_t sha2salt[SHA512_DIGEST_LENGTH]; + uint8_t out[BCRYPT_HASHSIZE]; + uint8_t tmpout[BCRYPT_HASHSIZE]; + uint8_t *countsalt; + size_t i, j, amt, stride; + uint32_t count; + size_t origkeylen = keylen; + libssh2_sha512_ctx ctx; + + /* nothing crazy */ + if(rounds < 1) + return -1; + if(passlen == 0 || saltlen == 0 || keylen == 0 || + keylen > sizeof(out) * sizeof(out) || saltlen > 1<<20) + return -1; + countsalt = calloc(1, saltlen + 4); + if(countsalt == NULL) + return -1; + stride = (keylen + sizeof(out) - 1) / sizeof(out); + amt = (keylen + stride - 1) / stride; + + memcpy(countsalt, salt, saltlen); + + /* collapse password */ + (void)libssh2_sha512_init(&ctx); + libssh2_sha512_update(ctx, pass, passlen); + libssh2_sha512_final(ctx, sha2pass); + + /* generate key, sizeof(out) at a time */ + for(count = 1; keylen > 0; count++) { + countsalt[saltlen + 0] = (count >> 24) & 0xff; + countsalt[saltlen + 1] = (count >> 16) & 0xff; + countsalt[saltlen + 2] = (count >> 8) & 0xff; + countsalt[saltlen + 3] = count & 0xff; + + /* first round, salt is salt */ + (void)libssh2_sha512_init(&ctx); + libssh2_sha512_update(ctx, countsalt, saltlen + 4); + libssh2_sha512_final(ctx, sha2salt); + + bcrypt_hash(sha2pass, sha2salt, tmpout); + memcpy(out, tmpout, sizeof(out)); + + for(i = 1; i < rounds; i++) { + /* subsequent rounds, salt is previous output */ + (void)libssh2_sha512_init(&ctx); + libssh2_sha512_update(ctx, tmpout, sizeof(tmpout)); + libssh2_sha512_final(ctx, sha2salt); + + bcrypt_hash(sha2pass, sha2salt, tmpout); + for(j = 0; j < sizeof(out); j++) + out[j] ^= tmpout[j]; + } + + /* + * pbkdf2 deviation: output the key material non-linearly. + */ + amt = MINIMUM(amt, keylen); + for(i = 0; i < amt; i++) { + size_t dest = i * stride + (count - 1); + if(dest >= origkeylen) { + break; + } + key[dest] = out[i]; + } + keylen -= i; + } + + /* zap */ + _libssh2_explicit_zero(out, sizeof(out)); + free(countsalt); + + return 0; +} +#endif /* HAVE_BCRYPT_PBKDF */ +#endif diff --git a/lib/libssh2/blf.h b/lib/libssh2/blf.h new file mode 100644 index 0000000..46fc422 --- /dev/null +++ b/lib/libssh2/blf.h @@ -0,0 +1,88 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_BLF_H +#define __LIBSSH2_BLF_H +/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N + 2)*4) /* 576 bits */ + +/* Blowfish context */ +typedef struct BlowfishContext { + uint32_t S[4][256]; /* S-Boxes */ + uint32_t P[BLF_N + 2]; /* Subkeys */ +} blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_decipher(blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_initstate(blf_ctx *); +void Blowfish_expand0state(blf_ctx *, const uint8_t *, uint16_t); +void Blowfish_expandstate +(blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t); + +/* Standard Blowfish */ + +void blf_key(blf_ctx *, const uint8_t *, uint16_t); +void blf_enc(blf_ctx *, uint32_t *, uint16_t); +void blf_dec(blf_ctx *, uint32_t *, uint16_t); + +void blf_ecb_encrypt(blf_ctx *, uint8_t *, uint32_t); +void blf_ecb_decrypt(blf_ctx *, uint8_t *, uint32_t); + +void blf_cbc_encrypt(blf_ctx *, uint8_t *, uint8_t *, uint32_t); +void blf_cbc_decrypt(blf_ctx *, uint8_t *, uint8_t *, uint32_t); + +/* Converts uint8_t to uint32_t */ +uint32_t Blowfish_stream2word(const uint8_t *, uint16_t, uint16_t *); + +/* bcrypt with pbkd */ +int bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, + size_t saltlen, + uint8_t *key, size_t keylen, unsigned int rounds); + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ +#endif /* __LIBSSH2_BLF_H */ +#endif diff --git a/lib/libssh2/blowfish.c b/lib/libssh2/blowfish.c new file mode 100644 index 0000000..a27105d --- /dev/null +++ b/lib/libssh2/blowfish.c @@ -0,0 +1,696 @@ +#if defined(ESP32) +/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ +/* + * Blowfish block cipher for OpenBSD + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Implementation advice by David Mazieres . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code is derived from section 14.3 and the given source + * in section V of Applied Cryptography, second edition. + * Blowfish is an unpatented fast block cipher designed by + * Bruce Schneier. + */ + + +#if !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || \ + !defined(HAVE_BLF_ENC)) + +#if 0 +#include /* used for debugging */ +#include +#endif + +#include + +#include "libssh2.h" +#include "blf.h" + +#undef inline +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* Function for Feistel Networks */ + +#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ + + (s)[0x100 + (((x)>>16)&0xFF)]) \ + ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ + + (s)[0x300 + ( (x) &0xFF)]) + +#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) + +void +Blowfish_encipher(blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[0]; + BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); + BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); + BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); + BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); + BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); + BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); + BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); + BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); + + *xl = Xr ^ p[17]; + *xr = Xl; +} + +void +Blowfish_decipher(blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[17]; + BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); + BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); + BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); + BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); + BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); + BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); + BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); + BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); + + *xl = Xr ^ p[0]; + *xr = Xl; +} + +void +Blowfish_initstate(blf_ctx *c) +{ + /* P-box and S-box tables initialized with digits of Pi */ + + static const blf_ctx initstate = + { { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} + }, + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } }; + + *c = initstate; +} + +uint32_t +Blowfish_stream2word(const uint8_t *data, uint16_t databytes, + uint16_t *current) +{ + uint8_t i; + uint16_t j; + uint32_t temp; + + temp = 0x00000000; + j = *current; + + for(i = 0; i < 4; i++, j++) { + if(j >= databytes) + j = 0; + temp = (temp << 8) | data[j]; + } + + *current = j; + return temp; +} + +void +Blowfish_expand0state(blf_ctx *c, const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for(i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for(i = 0; i < BLF_N + 2; i += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for(i = 0; i < 4; i++) { + for(k = 0; k < 256; k += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } +} + + +void +Blowfish_expandstate(blf_ctx *c, const uint8_t *data, uint16_t databytes, + const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for(i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for(i = 0; i < BLF_N + 2; i += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for(i = 0; i < 4; i++) { + for(k = 0; k < 256; k += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } + +} + +void +blf_key(blf_ctx *c, const uint8_t *k, uint16_t len) +{ + /* Initialize S-boxes and subkeys with Pi */ + Blowfish_initstate(c); + + /* Transform S-boxes and subkeys with key */ + Blowfish_expand0state(c, k, len); +} + +void +blf_enc(blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for(i = 0; i < blocks; i++) { + Blowfish_encipher(c, d, d + 1); + d += 2; + } +} + +void +blf_dec(blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for(i = 0; i < blocks; i++) { + Blowfish_decipher(c, d, d + 1); + d += 2; + } +} + +void +blf_ecb_encrypt(blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for(i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +blf_ecb_decrypt(blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for(i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +blf_cbc_encrypt(blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i, j; + + for(i = 0; i < len; i += 8) { + for(j = 0; j < 8; j++) + data[j] ^= iv[j]; + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + iv = data; + data += 8; + } +} + +void +blf_cbc_decrypt(blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint8_t *iv; + uint32_t i, j; + + iv = data + len - 16; + data = data + len - 8; + for(i = len - 8; i >= 8; i -= 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for(j = 0; j < 8; j++) + data[j] ^= iv[j]; + iv -= 8; + data -= 8; + } + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for(j = 0; j < 8; j++) + data[j] ^= iva[j]; +} + +#if 0 +void +report(uint32_t data[], uint16_t len) +{ + uint16_t i; + for(i = 0; i < len; i += 2) + printf("Block %0hd: %08lx %08lx.\n", + i / 2, data[i], data[i + 1]); +} +void +main(void) +{ + + blf_ctx c; + char key[] = "AAAAA"; + char key2[] = "abcdefghijklmnopqrstuvwxyz"; + + uint32_t data[10]; + uint32_t data2[] = + {0x424c4f57l, 0x46495348l}; + + uint16_t i; + + /* First test */ + for(i = 0; i < 10; i++) + data[i] = i; + + blf_key(&c, (uint8_t *) key, 5); + blf_enc(&c, data, 5); + blf_dec(&c, data, 1); + blf_dec(&c, data + 2, 4); + printf("Should read as 0 - 9.\n"); + report(data, 10); + + /* Second test */ + blf_key(&c, (uint8_t *) key2, strlen(key2)); + blf_enc(&c, data2, 1); + printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); + report(data2, 2); + blf_dec(&c, data2, 1); + report(data2, 2); +} +#endif + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && \ + (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || \ + '!defined(HAVE_BLF_ENC)) */ +#endif diff --git a/lib/libssh2/channel.c b/lib/libssh2/channel.c new file mode 100644 index 0000000..d0fba10 --- /dev/null +++ b/lib/libssh2/channel.c @@ -0,0 +1,2908 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2005 Mikhail Gusarov + * Copyright (c) 2008-2019 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifdef HAVE_INTTYPES_H +#include +#endif +#include + +#include "channel.h" +#include "transport.h" +#include "packet.h" +#include "session.h" + +/* + * _libssh2_channel_nextid + * + * Determine the next channel ID we can use at our end + */ +uint32_t +_libssh2_channel_nextid(LIBSSH2_SESSION * session) +{ + uint32_t id = session->next_channel; + LIBSSH2_CHANNEL *channel; + + channel = _libssh2_list_first(&session->channels); + + while(channel) { + if(channel->local.id > id) { + id = channel->local.id; + } + channel = _libssh2_list_next(&channel->node); + } + + /* This is a shortcut to avoid waiting for close packets on channels we've + * forgotten about, This *could* be a problem if we request and close 4 + * billion or so channels in too rapid succession for the remote end to + * respond, but the worst case scenario is that some data meant for + * another channel Gets picked up by the new one.... Pretty unlikely all + * told... + */ + session->next_channel = id + 1; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Allocated new channel ID#%lu", + id); + return id; +} + +/* + * _libssh2_channel_locate + * + * Locate a channel pointer by number + */ +LIBSSH2_CHANNEL * +_libssh2_channel_locate(LIBSSH2_SESSION *session, uint32_t channel_id) +{ + LIBSSH2_CHANNEL *channel; + LIBSSH2_LISTENER *l; + + for(channel = _libssh2_list_first(&session->channels); + channel; + channel = _libssh2_list_next(&channel->node)) { + if(channel->local.id == channel_id) + return channel; + } + + /* We didn't find the channel in the session, let's then check its + listeners since each listener may have its own set of pending channels + */ + for(l = _libssh2_list_first(&session->listeners); l; + l = _libssh2_list_next(&l->node)) { + for(channel = _libssh2_list_first(&l->queue); + channel; + channel = _libssh2_list_next(&channel->node)) { + if(channel->local.id == channel_id) + return channel; + } + } + + return NULL; +} + +/* + * _libssh2_channel_open + * + * Establish a generic session channel + */ +LIBSSH2_CHANNEL * +_libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, + size_t message_len) +{ + static const unsigned char reply_codes[3] = { + SSH_MSG_CHANNEL_OPEN_CONFIRMATION, + SSH_MSG_CHANNEL_OPEN_FAILURE, + 0 + }; + unsigned char *s; + int rc; + + if(session->open_state == libssh2_NB_state_idle) { + session->open_channel = NULL; + session->open_packet = NULL; + session->open_data = NULL; + /* 17 = packet_type(1) + channel_type_len(4) + sender_channel(4) + + * window_size(4) + packet_size(4) */ + session->open_packet_len = channel_type_len + 17; + session->open_local_channel = _libssh2_channel_nextid(session); + + /* Zero the whole thing out */ + memset(&session->open_packet_requirev_state, 0, + sizeof(session->open_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Opening Channel - win %d pack %d", window_size, + packet_size); + session->open_channel = + LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if(!session->open_channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate space for channel data"); + return NULL; + } + session->open_channel->channel_type_len = channel_type_len; + session->open_channel->channel_type = + LIBSSH2_ALLOC(session, channel_type_len); + if(!session->open_channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating memory for channel type name"); + LIBSSH2_FREE(session, session->open_channel); + session->open_channel = NULL; + return NULL; + } + memcpy(session->open_channel->channel_type, channel_type, + channel_type_len); + + /* REMEMBER: local as in locally sourced */ + session->open_channel->local.id = session->open_local_channel; + session->open_channel->remote.window_size = window_size; + session->open_channel->remote.window_size_initial = window_size; + session->open_channel->remote.packet_size = packet_size; + session->open_channel->session = session; + + _libssh2_list_add(&session->channels, + &session->open_channel->node); + + s = session->open_packet = + LIBSSH2_ALLOC(session, session->open_packet_len); + if(!session->open_packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate temporary space for packet"); + goto channel_error; + } + *(s++) = SSH_MSG_CHANNEL_OPEN; + _libssh2_store_str(&s, channel_type, channel_type_len); + _libssh2_store_u32(&s, session->open_local_channel); + _libssh2_store_u32(&s, window_size); + _libssh2_store_u32(&s, packet_size); + + /* Do not copy the message */ + + session->open_state = libssh2_NB_state_created; + } + + if(session->open_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + session->open_packet, + session->open_packet_len, + message, message_len); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending channel-open request"); + return NULL; + } + else if(rc) { + _libssh2_error(session, rc, + "Unable to send channel-open request"); + goto channel_error; + } + + session->open_state = libssh2_NB_state_sent; + } + + if(session->open_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->open_data, + &session->open_data_len, 1, + session->open_packet + 5 + + channel_type_len, 4, + &session->open_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + return NULL; + } + else if(rc) { + _libssh2_error(session, rc, "Unexpected error"); + goto channel_error; + } + + if(session->open_data_len < 1) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + goto channel_error; + } + + if(session->open_data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { + + if(session->open_data_len < 17) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + goto channel_error; + } + + session->open_channel->remote.id = + _libssh2_ntohu32(session->open_data + 5); + session->open_channel->local.window_size = + _libssh2_ntohu32(session->open_data + 9); + session->open_channel->local.window_size_initial = + _libssh2_ntohu32(session->open_data + 9); + session->open_channel->local.packet_size = + _libssh2_ntohu32(session->open_data + 13); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Connection Established - ID: %lu/%lu win: %lu/%lu" + " pack: %lu/%lu", + session->open_channel->local.id, + session->open_channel->remote.id, + session->open_channel->local.window_size, + session->open_channel->remote.window_size, + session->open_channel->local.packet_size, + session->open_channel->remote.packet_size); + LIBSSH2_FREE(session, session->open_packet); + session->open_packet = NULL; + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + + session->open_state = libssh2_NB_state_idle; + return session->open_channel; + } + + if(session->open_data[0] == SSH_MSG_CHANNEL_OPEN_FAILURE) { + unsigned int reason_code = + _libssh2_ntohu32(session->open_data + 5); + switch(reason_code) { + case SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure " + "(administratively prohibited)"); + break; + case SSH_OPEN_CONNECT_FAILED: + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure (connect failed)"); + break; + case SSH_OPEN_UNKNOWN_CHANNELTYPE: + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure (unknown channel type)"); + break; + case SSH_OPEN_RESOURCE_SHORTAGE: + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure (resource shortage)"); + break; + default: + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure"); + } + } + } + + channel_error: + + if(session->open_data) { + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + } + if(session->open_packet) { + LIBSSH2_FREE(session, session->open_packet); + session->open_packet = NULL; + } + if(session->open_channel) { + unsigned char channel_id[4]; + LIBSSH2_FREE(session, session->open_channel->channel_type); + + _libssh2_list_remove(&session->open_channel->node); + + /* Clear out packets meant for this channel */ + _libssh2_htonu32(channel_id, session->open_channel->local.id); + while((_libssh2_packet_ask(session, SSH_MSG_CHANNEL_DATA, + &session->open_data, + &session->open_data_len, 1, + channel_id, 4) >= 0) + || + (_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA, + &session->open_data, + &session->open_data_len, 1, + channel_id, 4) >= 0)) { + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + } + + LIBSSH2_FREE(session, session->open_channel); + session->open_channel = NULL; + } + + session->open_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_channel_open_ex + * + * Establish a generic session channel + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *type, + unsigned int type_len, + unsigned int window_size, unsigned int packet_size, + const char *msg, unsigned int msg_len) +{ + LIBSSH2_CHANNEL *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + _libssh2_channel_open(session, type, type_len, + window_size, packet_size, + (unsigned char *)msg, + msg_len)); + return ptr; +} + +/* + * libssh2_channel_direct_tcpip_ex + * + * Tunnel TCP/IP connect through the SSH session to direct host/port + */ +static LIBSSH2_CHANNEL * +channel_direct_tcpip(LIBSSH2_SESSION * session, const char *host, + int port, const char *shost, int sport) +{ + LIBSSH2_CHANNEL *channel; + unsigned char *s; + + if(session->direct_state == libssh2_NB_state_idle) { + session->direct_host_len = strlen(host); + session->direct_shost_len = strlen(shost); + /* host_len(4) + port(4) + shost_len(4) + sport(4) */ + session->direct_message_len = + session->direct_host_len + session->direct_shost_len + 16; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting direct-tcpip session from %s:%d to %s:%d", + shost, sport, host, port); + + s = session->direct_message = + LIBSSH2_ALLOC(session, session->direct_message_len); + if(!session->direct_message) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "direct-tcpip connection"); + return NULL; + } + _libssh2_store_str(&s, host, session->direct_host_len); + _libssh2_store_u32(&s, port); + _libssh2_store_str(&s, shost, session->direct_shost_len); + _libssh2_store_u32(&s, sport); + } + + channel = + _libssh2_channel_open(session, "direct-tcpip", + sizeof("direct-tcpip") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, + session->direct_message, + session->direct_message_len); + + if(!channel && + libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) { + /* The error code is still set to LIBSSH2_ERROR_EAGAIN, set our state + to created to avoid re-creating the package on next invoke */ + session->direct_state = libssh2_NB_state_created; + return NULL; + } + /* by default we set (keep?) idle state... */ + session->direct_state = libssh2_NB_state_idle; + + LIBSSH2_FREE(session, session->direct_message); + session->direct_message = NULL; + + return channel; +} + +/* + * libssh2_channel_direct_tcpip_ex + * + * Tunnel TCP/IP connect through the SSH session to direct host/port + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, const char *host, + int port, const char *shost, int sport) +{ + LIBSSH2_CHANNEL *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + channel_direct_tcpip(session, host, port, + shost, sport)); + return ptr; +} + +/* + * channel_forward_listen + * + * Bind a port on the remote host and listen for connections + */ +static LIBSSH2_LISTENER * +channel_forward_listen(LIBSSH2_SESSION * session, const char *host, + int port, int *bound_port, int queue_maxsize) +{ + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 }; + int rc; + + if(!host) + host = "0.0.0.0"; + + if(session->fwdLstn_state == libssh2_NB_state_idle) { + session->fwdLstn_host_len = strlen(host); + /* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + + port(4) */ + session->fwdLstn_packet_len = + session->fwdLstn_host_len + (sizeof("tcpip-forward") - 1) + 14; + + /* Zero the whole thing out */ + memset(&session->fwdLstn_packet_requirev_state, 0, + sizeof(session->fwdLstn_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting tcpip-forward session for %s:%d", host, + port); + + s = session->fwdLstn_packet = + LIBSSH2_ALLOC(session, session->fwdLstn_packet_len); + if(!session->fwdLstn_packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for setenv packet"); + return NULL; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + _libssh2_store_str(&s, "tcpip-forward", sizeof("tcpip-forward") - 1); + *(s++) = 0x01; /* want_reply */ + + _libssh2_store_str(&s, host, session->fwdLstn_host_len); + _libssh2_store_u32(&s, port); + + session->fwdLstn_state = libssh2_NB_state_created; + } + + if(session->fwdLstn_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + session->fwdLstn_packet, + session->fwdLstn_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending global-request packet for " + "forward listen request"); + return NULL; + } + else if(rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send global-request packet for forward " + "listen request"); + LIBSSH2_FREE(session, session->fwdLstn_packet); + session->fwdLstn_packet = NULL; + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + LIBSSH2_FREE(session, session->fwdLstn_packet); + session->fwdLstn_packet = NULL; + + session->fwdLstn_state = libssh2_NB_state_sent; + } + + if(session->fwdLstn_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 0, NULL, 0, + &session->fwdLstn_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + return NULL; + } + else if(rc || (data_len < 1)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, "Unknown"); + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + + if(data[0] == SSH_MSG_REQUEST_SUCCESS) { + LIBSSH2_LISTENER *listener; + + listener = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_LISTENER)); + if(!listener) + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for listener queue"); + else { + listener->host = + LIBSSH2_ALLOC(session, session->fwdLstn_host_len + 1); + if(!listener->host) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for listener queue"); + LIBSSH2_FREE(session, listener); + listener = NULL; + } + else { + listener->session = session; + memcpy(listener->host, host, session->fwdLstn_host_len); + listener->host[session->fwdLstn_host_len] = 0; + if(data_len >= 5 && !port) { + listener->port = _libssh2_ntohu32(data + 1); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Dynamic tcpip-forward port " + "allocated: %d", + listener->port); + } + else + listener->port = port; + + listener->queue_size = 0; + listener->queue_maxsize = queue_maxsize; + + /* append this to the parent's list of listeners */ + _libssh2_list_add(&session->listeners, &listener->node); + + if(bound_port) { + *bound_port = listener->port; + } + } + } + + LIBSSH2_FREE(session, data); + session->fwdLstn_state = libssh2_NB_state_idle; + return listener; + } + else if(data[0] == SSH_MSG_REQUEST_FAILURE) { + LIBSSH2_FREE(session, data); + _libssh2_error(session, LIBSSH2_ERROR_REQUEST_DENIED, + "Unable to complete request for forward-listen"); + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + } + + session->fwdLstn_state = libssh2_NB_state_idle; + + return NULL; +} + +/* + * libssh2_channel_forward_listen_ex + * + * Bind a port on the remote host and listen for connections + */ +LIBSSH2_API LIBSSH2_LISTENER * +libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, const char *host, + int port, int *bound_port, int queue_maxsize) +{ + LIBSSH2_LISTENER *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + channel_forward_listen(session, host, port, bound_port, + queue_maxsize)); + return ptr; +} + +/* + * _libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +int _libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) +{ + LIBSSH2_SESSION *session = listener->session; + LIBSSH2_CHANNEL *queued; + unsigned char *packet, *s; + size_t host_len = strlen(listener->host); + /* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + + port(4) */ + size_t packet_len = + host_len + 14 + sizeof("cancel-tcpip-forward") - 1; + int rc; + int retcode = 0; + + if(listener->chanFwdCncl_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Cancelling tcpip-forward session for %s:%d", + listener->host, listener->port); + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if(!packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for setenv packet"); + return LIBSSH2_ERROR_ALLOC; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + _libssh2_store_str(&s, "cancel-tcpip-forward", + sizeof("cancel-tcpip-forward") - 1); + *(s++) = 0x00; /* want_reply */ + + _libssh2_store_str(&s, listener->host, host_len); + _libssh2_store_u32(&s, listener->port); + + listener->chanFwdCncl_state = libssh2_NB_state_created; + } + else { + packet = listener->chanFwdCncl_data; + } + + if(listener->chanFwdCncl_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, packet, packet_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending forward request"); + listener->chanFwdCncl_data = packet; + return rc; + } + else if(rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send global-request packet for forward " + "listen request"); + /* set the state to something we don't check for, for the + unfortunate situation where we get an EAGAIN further down + when trying to bail out due to errors! */ + listener->chanFwdCncl_state = libssh2_NB_state_sent; + retcode = LIBSSH2_ERROR_SOCKET_SEND; + } + LIBSSH2_FREE(session, packet); + + listener->chanFwdCncl_state = libssh2_NB_state_sent; + } + + queued = _libssh2_list_first(&listener->queue); + while(queued) { + LIBSSH2_CHANNEL *next = _libssh2_list_next(&queued->node); + + rc = _libssh2_channel_free(queued); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + queued = next; + } + LIBSSH2_FREE(session, listener->host); + + /* remove this entry from the parent's list of listeners */ + _libssh2_list_remove(&listener->node); + + LIBSSH2_FREE(session, listener); + + return retcode; +} + +/* + * libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +LIBSSH2_API int +libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) +{ + int rc; + + if(!listener) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, listener->session, + _libssh2_channel_forward_cancel(listener)); + return rc; +} + +/* + * channel_forward_accept + * + * Accept a connection + */ +static LIBSSH2_CHANNEL * +channel_forward_accept(LIBSSH2_LISTENER *listener) +{ + int rc; + + do { + rc = _libssh2_transport_read(listener->session); + } while(rc > 0); + + if(_libssh2_list_first(&listener->queue)) { + LIBSSH2_CHANNEL *channel = _libssh2_list_first(&listener->queue); + + /* detach channel from listener's queue */ + _libssh2_list_remove(&channel->node); + + listener->queue_size--; + + /* add channel to session's channel list */ + _libssh2_list_add(&channel->session->channels, &channel->node); + + return channel; + } + + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(listener->session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for packet"); + } + else + _libssh2_error(listener->session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, + "Channel not found"); + return NULL; +} + +/* + * libssh2_channel_forward_accept + * + * Accept a connection + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) +{ + LIBSSH2_CHANNEL *ptr; + + if(!listener) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, listener->session, + channel_forward_accept(listener)); + return ptr; + +} + +/* + * channel_setenv + * + * Set an environment variable prior to requesting a shell/program/subsystem + */ +static int channel_setenv(LIBSSH2_CHANNEL *channel, + const char *varname, unsigned int varname_len, + const char *value, unsigned int value_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *data; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + size_t data_len; + int rc; + + if(channel->setenv_state == libssh2_NB_state_idle) { + /* 21 = packet_type(1) + channel_id(4) + request_len(4) + + * request(3)"env" + want_reply(1) + varname_len(4) + value_len(4) */ + channel->setenv_packet_len = varname_len + value_len + 21; + + /* Zero the whole thing out */ + memset(&channel->setenv_packet_requirev_state, 0, + sizeof(channel->setenv_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Setting remote environment variable: %s=%s on " + "channel %lu/%lu", + varname, value, channel->local.id, channel->remote.id); + + s = channel->setenv_packet = + LIBSSH2_ALLOC(session, channel->setenv_packet_len); + if(!channel->setenv_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for setenv packet"); + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, "env", sizeof("env") - 1); + *(s++) = 0x01; + _libssh2_store_str(&s, varname, varname_len); + _libssh2_store_str(&s, value, value_len); + + channel->setenv_state = libssh2_NB_state_created; + } + + if(channel->setenv_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + channel->setenv_packet, + channel->setenv_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending setenv request"); + return rc; + } + else if(rc) { + LIBSSH2_FREE(session, channel->setenv_packet); + channel->setenv_packet = NULL; + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send channel-request packet for " + "setenv request"); + } + LIBSSH2_FREE(session, channel->setenv_packet); + channel->setenv_packet = NULL; + + _libssh2_htonu32(channel->setenv_local_channel, channel->local.id); + + channel->setenv_state = libssh2_NB_state_sent; + } + + if(channel->setenv_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->setenv_local_channel, 4, + &channel-> + setenv_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + if(rc) { + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Failed getting response for " + "channel-setenv"); + } + else if(data_len < 1) { + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + } + + if(data[0] == SSH_MSG_CHANNEL_SUCCESS) { + LIBSSH2_FREE(session, data); + channel->setenv_state = libssh2_NB_state_idle; + return 0; + } + + LIBSSH2_FREE(session, data); + } + + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for channel-setenv"); +} + +/* + * libssh2_channel_setenv_ex + * + * Set an environment variable prior to requesting a shell/program/subsystem + */ +LIBSSH2_API int +libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, + const char *varname, unsigned int varname_len, + const char *value, unsigned int value_len) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_setenv(channel, varname, varname_len, + value, value_len)); + return rc; +} + +/* + * channel_request_pty + * Duh... Request a PTY + */ +static int channel_request_pty(LIBSSH2_CHANNEL *channel, + const char *term, unsigned int term_len, + const char *modes, unsigned int modes_len, + int width, int height, + int width_px, int height_px) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + int rc; + + if(channel->reqPTY_state == libssh2_NB_state_idle) { + /* 41 = packet_type(1) + channel(4) + pty_req_len(4) + "pty_req"(7) + + * want_reply(1) + term_len(4) + width(4) + height(4) + width_px(4) + + * height_px(4) + modes_len(4) */ + if(term_len + modes_len > 256) { + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "term + mode lengths too large"); + } + + channel->reqPTY_packet_len = term_len + modes_len + 41; + + /* Zero the whole thing out */ + memset(&channel->reqPTY_packet_requirev_state, 0, + sizeof(channel->reqPTY_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Allocating tty on channel %lu/%lu", channel->local.id, + channel->remote.id); + + s = channel->reqPTY_packet; + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, (char *)"pty-req", sizeof("pty-req") - 1); + + *(s++) = 0x01; + + _libssh2_store_str(&s, term, term_len); + _libssh2_store_u32(&s, width); + _libssh2_store_u32(&s, height); + _libssh2_store_u32(&s, width_px); + _libssh2_store_u32(&s, height_px); + _libssh2_store_str(&s, modes, modes_len); + + channel->reqPTY_state = libssh2_NB_state_created; + } + + if(channel->reqPTY_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqPTY_packet, + channel->reqPTY_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending pty request"); + return rc; + } + else if(rc) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send pty-request packet"); + } + _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); + + channel->reqPTY_state = libssh2_NB_state_sent; + } + + if(channel->reqPTY_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + unsigned char code; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->reqPTY_local_channel, 4, + &channel->reqPTY_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc || data_len < 1) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Failed to require the PTY package"); + } + + code = data[0]; + + LIBSSH2_FREE(session, data); + channel->reqPTY_state = libssh2_NB_state_idle; + + if(code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for " + "channel request-pty"); +} + +/** + * channel_request_auth_agent + * The actual re-entrant method which requests an auth agent. + * */ +static int channel_request_auth_agent(LIBSSH2_CHANNEL *channel, + const char *request_str, + int request_str_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + int rc; + + if(channel->req_auth_agent_state == libssh2_NB_state_idle) { + /* Only valid options are "auth-agent-req" and + * "auth-agent-req_at_openssh.com" so we make sure it is not + * actually longer than the longest possible. */ + if(request_str_len > 26) { + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "request_str length too large"); + } + + /* + * Length: 24 or 36 = packet_type(1) + channel(4) + req_len(4) + + * request_str (variable) + want_reply (1) */ + channel->req_auth_agent_packet_len = 10 + request_str_len; + + /* Zero out the requireev state to reset */ + memset(&channel->req_auth_agent_requirev_state, 0, + sizeof(channel->req_auth_agent_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting auth agent on channel %lu/%lu", + channel->local.id, channel->remote.id); + + /* + * byte SSH_MSG_CHANNEL_REQUEST + * uint32 recipient channel + * string "auth-agent-req" + * boolean want reply + * */ + s = channel->req_auth_agent_packet; + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, (char *)request_str, request_str_len); + *(s++) = 0x01; + + channel->req_auth_agent_state = libssh2_NB_state_created; + } + + if(channel->req_auth_agent_state == libssh2_NB_state_created) { + /* Send the packet, we can use sizeof() on the packet because it + * is always completely filled; there are no variable length fields. */ + rc = _libssh2_transport_send(session, channel->req_auth_agent_packet, + channel->req_auth_agent_packet_len, + NULL, 0); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending auth-agent request"); + } + else if(rc) { + channel->req_auth_agent_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send auth-agent request"); + } + _libssh2_htonu32(channel->req_auth_agent_local_channel, + channel->local.id); + channel->req_auth_agent_state = libssh2_NB_state_sent; + } + + if(channel->req_auth_agent_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + unsigned char code; + + rc = _libssh2_packet_requirev( + session, reply_codes, &data, &data_len, 1, + channel->req_auth_agent_local_channel, + 4, &channel->req_auth_agent_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + channel->req_auth_agent_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Failed to request auth-agent"); + } + + code = data[0]; + + LIBSSH2_FREE(session, data); + channel->req_auth_agent_state = libssh2_NB_state_idle; + + if(code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for auth-agent"); +} + +/** + * libssh2_channel_request_auth_agent + * Requests that agent forwarding be enabled for the session. The + * request must be sent over a specific channel, which starts the agent + * listener on the remote side. Once the channel is closed, the agent + * listener continues to exist. + * */ +LIBSSH2_API int +libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + rc = LIBSSH2_ERROR_CHANNEL_UNKNOWN; + + /* The current RFC draft for agent forwarding says you're supposed to + * send "auth-agent-req," but most SSH servers out there right now + * actually expect "auth-agent-req@openssh.com", so we try that + * first. */ + if(channel->req_auth_agent_try_state == libssh2_NB_state_idle) { + BLOCK_ADJUST(rc, channel->session, + channel_request_auth_agent(channel, + "auth-agent-req@openssh.com", + 26)); + + /* If we failed (but not with EAGAIN), then we move onto + * the next step to try another request type. */ + if(rc != LIBSSH2_ERROR_NONE && + rc != LIBSSH2_ERROR_EAGAIN) + channel->req_auth_agent_try_state = libssh2_NB_state_sent; + } + + if(channel->req_auth_agent_try_state == libssh2_NB_state_sent) { + BLOCK_ADJUST(rc, channel->session, + channel_request_auth_agent(channel, + "auth-agent-req", 14)); + + /* If we failed without an EAGAIN, then move on with this + * state machine. */ + if(rc != LIBSSH2_ERROR_NONE && + rc != LIBSSH2_ERROR_EAGAIN) + channel->req_auth_agent_try_state = libssh2_NB_state_sent1; + } + + /* If things are good, reset the try state. */ + if(rc == LIBSSH2_ERROR_NONE) + channel->req_auth_agent_try_state = libssh2_NB_state_idle; + + return rc; +} + +/* + * libssh2_channel_request_pty_ex + * Duh... Request a PTY + */ +LIBSSH2_API int +libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, const char *term, + unsigned int term_len, const char *modes, + unsigned int modes_len, int width, int height, + int width_px, int height_px) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_request_pty(channel, term, term_len, modes, + modes_len, width, height, + width_px, height_px)); + return rc; +} + +static int +channel_request_pty_size(LIBSSH2_CHANNEL * channel, int width, + int height, int width_px, int height_px) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + int rc; + int retcode = LIBSSH2_ERROR_PROTO; + + if(channel->reqPTY_state == libssh2_NB_state_idle) { + channel->reqPTY_packet_len = 39; + + /* Zero the whole thing out */ + memset(&channel->reqPTY_packet_requirev_state, 0, + sizeof(channel->reqPTY_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "changing tty size on channel %lu/%lu", + channel->local.id, + channel->remote.id); + + s = channel->reqPTY_packet; + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, (char *)"window-change", + sizeof("window-change") - 1); + *(s++) = 0x00; /* Don't reply */ + _libssh2_store_u32(&s, width); + _libssh2_store_u32(&s, height); + _libssh2_store_u32(&s, width_px); + _libssh2_store_u32(&s, height_px); + + channel->reqPTY_state = libssh2_NB_state_created; + } + + if(channel->reqPTY_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqPTY_packet, + channel->reqPTY_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending window-change request"); + return rc; + } + else if(rc) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send window-change packet"); + } + _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); + retcode = LIBSSH2_ERROR_NONE; + } + + channel->reqPTY_state = libssh2_NB_state_idle; + return retcode; +} + +LIBSSH2_API int +libssh2_channel_request_pty_size_ex(LIBSSH2_CHANNEL *channel, int width, + int height, int width_px, int height_px) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_request_pty_size(channel, width, height, width_px, + height_px)); + return rc; +} + +/* Keep this an even number */ +#define LIBSSH2_X11_RANDOM_COOKIE_LEN 32 + +/* + * channel_x11_req + * Request X11 forwarding + */ +static int +channel_x11_req(LIBSSH2_CHANNEL *channel, int single_connection, + const char *auth_proto, const char *auth_cookie, + int screen_number) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + size_t proto_len = + auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1); + size_t cookie_len = + auth_cookie ? strlen(auth_cookie) : LIBSSH2_X11_RANDOM_COOKIE_LEN; + int rc; + + if(channel->reqX11_state == libssh2_NB_state_idle) { + /* 30 = packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) + + * want_reply(1) + single_cnx(1) + proto_len(4) + cookie_len(4) + + * screen_num(4) */ + channel->reqX11_packet_len = proto_len + cookie_len + 30; + + /* Zero the whole thing out */ + memset(&channel->reqX11_packet_requirev_state, 0, + sizeof(channel->reqX11_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting x11-req for channel %lu/%lu: single=%d " + "proto=%s cookie=%s screen=%d", + channel->local.id, channel->remote.id, + single_connection, + auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", + auth_cookie ? auth_cookie : "", screen_number); + + s = channel->reqX11_packet = + LIBSSH2_ALLOC(session, channel->reqX11_packet_len); + if(!channel->reqX11_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for pty-request"); + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, "x11-req", sizeof("x11-req") - 1); + + *(s++) = 0x01; /* want_reply */ + *(s++) = single_connection ? 0x01 : 0x00; + + _libssh2_store_str(&s, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", + proto_len); + + _libssh2_store_u32(&s, cookie_len); + if(auth_cookie) { + memcpy(s, auth_cookie, cookie_len); + } + else { + int i; + /* note: the extra +1 below is necessary since the sprintf() + loop will always write 3 bytes so the last one will write + the trailing zero at the LIBSSH2_X11_RANDOM_COOKIE_LEN/2 + border */ + unsigned char buffer[(LIBSSH2_X11_RANDOM_COOKIE_LEN / 2) + 1]; + + if(_libssh2_random(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2)) { + return _libssh2_error(session, LIBSSH2_ERROR_RANDGEN, + "Unable to get random bytes " + "for x11-req cookie"); + } + for(i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) { + snprintf((char *)&s[i*2], 3, "%02X", buffer[i]); + } + } + s += cookie_len; + + _libssh2_store_u32(&s, screen_number); + channel->reqX11_state = libssh2_NB_state_created; + } + + if(channel->reqX11_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqX11_packet, + channel->reqX11_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending X11-req packet"); + return rc; + } + if(rc) { + LIBSSH2_FREE(session, channel->reqX11_packet); + channel->reqX11_packet = NULL; + channel->reqX11_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send x11-req packet"); + } + LIBSSH2_FREE(session, channel->reqX11_packet); + channel->reqX11_packet = NULL; + + _libssh2_htonu32(channel->reqX11_local_channel, channel->local.id); + + channel->reqX11_state = libssh2_NB_state_sent; + } + + if(channel->reqX11_state == libssh2_NB_state_sent) { + size_t data_len; + unsigned char *data; + unsigned char code; + + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->reqX11_local_channel, 4, + &channel->reqX11_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc || data_len < 1) { + channel->reqX11_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "waiting for x11-req response packet"); + } + + code = data[0]; + LIBSSH2_FREE(session, data); + channel->reqX11_state = libssh2_NB_state_idle; + + if(code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for channel x11-req"); +} + +/* + * libssh2_channel_x11_req_ex + * Request X11 forwarding + */ +LIBSSH2_API int +libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, + const char *auth_proto, const char *auth_cookie, + int screen_number) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_x11_req(channel, single_connection, auth_proto, + auth_cookie, screen_number)); + return rc; +} + + +/* + * _libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +int +_libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + int rc; + + if(channel->process_state == libssh2_NB_state_end) { + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "Channel can not be reused"); + } + + if(channel->process_state == libssh2_NB_state_idle) { + /* 10 = packet_type(1) + channel(4) + request_len(4) + want_reply(1) */ + channel->process_packet_len = request_len + 10; + + /* Zero the whole thing out */ + memset(&channel->process_packet_requirev_state, 0, + sizeof(channel->process_packet_requirev_state)); + + if(message) + channel->process_packet_len += + 4; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "starting request(%s) on channel %lu/%lu, message=%s", + request, channel->local.id, channel->remote.id, + message ? message : ""); + s = channel->process_packet = + LIBSSH2_ALLOC(session, channel->process_packet_len); + if(!channel->process_packet) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for channel-process request"); + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, request, request_len); + *(s++) = 0x01; + + if(message) + _libssh2_store_u32(&s, message_len); + + channel->process_state = libssh2_NB_state_created; + } + + if(channel->process_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + channel->process_packet, + channel->process_packet_len, + (unsigned char *)message, message_len); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending channel request"); + return rc; + } + else if(rc) { + LIBSSH2_FREE(session, channel->process_packet); + channel->process_packet = NULL; + channel->process_state = libssh2_NB_state_end; + return _libssh2_error(session, rc, + "Unable to send channel request"); + } + LIBSSH2_FREE(session, channel->process_packet); + channel->process_packet = NULL; + + _libssh2_htonu32(channel->process_local_channel, channel->local.id); + + channel->process_state = libssh2_NB_state_sent; + } + + if(channel->process_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + unsigned char code; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->process_local_channel, 4, + &channel->process_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc || data_len < 1) { + channel->process_state = libssh2_NB_state_end; + return _libssh2_error(session, rc, + "Failed waiting for channel success"); + } + + code = data[0]; + LIBSSH2_FREE(session, data); + channel->process_state = libssh2_NB_state_end; + + if(code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for " + "channel-process-startup"); +} + +/* + * libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +LIBSSH2_API int +libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *req, unsigned int req_len, + const char *msg, unsigned int msg_len) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_process_startup(channel, req, req_len, + msg, msg_len)); + return rc; +} + + +/* + * libssh2_channel_set_blocking + * + * Set a channel's BEHAVIOR blocking on or off. The socket will remain non- + * blocking. + */ +LIBSSH2_API void +libssh2_channel_set_blocking(LIBSSH2_CHANNEL * channel, int blocking) +{ + if(channel) + (void) _libssh2_session_set_blocking(channel->session, blocking); +} + +/* + * _libssh2_channel_flush + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +int +_libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid) +{ + if(channel->flush_state == libssh2_NB_state_idle) { + LIBSSH2_PACKET *packet = + _libssh2_list_first(&channel->session->packets); + channel->flush_refund_bytes = 0; + channel->flush_flush_bytes = 0; + + while(packet) { + unsigned char packet_type; + LIBSSH2_PACKET *next = _libssh2_list_next(&packet->node); + + if(packet->data_len < 1) { + packet = next; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + packet_type = packet->data[0]; + + if(((packet_type == SSH_MSG_CHANNEL_DATA) + || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && ((packet->data_len >= 5) + && (_libssh2_ntohu32(packet->data + 1) + == channel->local.id))) { + /* It's our channel at least */ + int packet_stream_id; + + if(packet_type == SSH_MSG_CHANNEL_DATA) { + packet_stream_id = 0; + } + else if(packet->data_len >= 9) { + packet_stream_id = _libssh2_ntohu32(packet->data + 5); + } + else { + channel->flush_state = libssh2_NB_state_idle; + return _libssh2_error(channel->session, + LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + } + + if((streamid == LIBSSH2_CHANNEL_FLUSH_ALL) + || ((packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA) + && ((streamid == LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) + || (streamid == packet_stream_id))) + || ((packet_type == SSH_MSG_CHANNEL_DATA) + && (streamid == 0))) { + size_t bytes_to_flush = packet->data_len - + packet->data_head; + + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Flushing %d bytes of data from stream " + "%lu on channel %lu/%lu", + bytes_to_flush, packet_stream_id, + channel->local.id, channel->remote.id); + + /* It's one of the streams we wanted to flush */ + channel->flush_refund_bytes += packet->data_len - 13; + channel->flush_flush_bytes += bytes_to_flush; + + LIBSSH2_FREE(channel->session, packet->data); + + /* remove this packet from the parent's list */ + _libssh2_list_remove(&packet->node); + LIBSSH2_FREE(channel->session, packet); + } + } + packet = next; + } + + channel->flush_state = libssh2_NB_state_created; + } + + channel->read_avail -= channel->flush_flush_bytes; + channel->remote.window_size -= channel->flush_flush_bytes; + + if(channel->flush_refund_bytes) { + int rc = + _libssh2_channel_receive_window_adjust(channel, + channel->flush_refund_bytes, + 1, NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + channel->flush_state = libssh2_NB_state_idle; + + return channel->flush_flush_bytes; +} + +/* + * libssh2_channel_flush_ex + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +LIBSSH2_API int +libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int stream) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_flush(channel, stream)); + return rc; +} + +/* + * libssh2_channel_get_exit_status + * + * Return the channel's program exit status. Note that the actual protocol + * provides the full 32bit this function returns. We cannot abuse it to + * return error values in case of errors so we return a zero if channel is + * NULL. + */ +LIBSSH2_API int +libssh2_channel_get_exit_status(LIBSSH2_CHANNEL *channel) +{ + if(!channel) + return 0; + + return channel->exit_status; +} + +/* + * libssh2_channel_get_exit_signal + * + * Get exit signal (without leading "SIG"), error message, and language + * tag into newly allocated buffers of indicated length. Caller can + * use NULL pointers to indicate that the value should not be set. The + * *_len variables are set if they are non-NULL even if the + * corresponding string parameter is NULL. Returns LIBSSH2_ERROR_NONE + * on success, or an API error code. + */ +LIBSSH2_API int +libssh2_channel_get_exit_signal(LIBSSH2_CHANNEL *channel, + char **exitsignal, + size_t *exitsignal_len, + char **errmsg, + size_t *errmsg_len, + char **langtag, + size_t *langtag_len) +{ + size_t namelen = 0; + + if(channel) { + LIBSSH2_SESSION *session = channel->session; + + if(channel->exit_signal) { + namelen = strlen(channel->exit_signal); + if(exitsignal) { + *exitsignal = LIBSSH2_ALLOC(session, namelen + 1); + if(!*exitsignal) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for signal name"); + } + memcpy(*exitsignal, channel->exit_signal, namelen); + (*exitsignal)[namelen] = '\0'; + } + if(exitsignal_len) + *exitsignal_len = namelen; + } + else { + if(exitsignal) + *exitsignal = NULL; + if(exitsignal_len) + *exitsignal_len = 0; + } + + /* TODO: set error message and language tag */ + + if(errmsg) + *errmsg = NULL; + + if(errmsg_len) + *errmsg_len = 0; + + if(langtag) + *langtag = NULL; + + if(langtag_len) + *langtag_len = 0; + } + + return LIBSSH2_ERROR_NONE; +} + +/* + * _libssh2_channel_receive_window_adjust + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Calls _libssh2_error() ! + */ +int +_libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL * channel, + uint32_t adjustment, + unsigned char force, + unsigned int *store) +{ + int rc; + + if(store) + *store = channel->remote.window_size; + + if(channel->adjust_state == libssh2_NB_state_idle) { + if(!force + && (adjustment + channel->adjust_queue < + LIBSSH2_CHANNEL_MINADJUST)) { + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Queueing %lu bytes for receive window adjustment " + "for channel %lu/%lu", + adjustment, channel->local.id, channel->remote.id); + channel->adjust_queue += adjustment; + return 0; + } + + if(!adjustment && !channel->adjust_queue) { + return 0; + } + + adjustment += channel->adjust_queue; + channel->adjust_queue = 0; + + /* Adjust the window based on the block we just freed */ + channel->adjust_adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST; + _libssh2_htonu32(&channel->adjust_adjust[1], channel->remote.id); + _libssh2_htonu32(&channel->adjust_adjust[5], adjustment); + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Adjusting window %lu bytes for data on " + "channel %lu/%lu", + adjustment, channel->local.id, channel->remote.id); + + channel->adjust_state = libssh2_NB_state_created; + } + + rc = _libssh2_transport_send(channel->session, channel->adjust_adjust, 9, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(channel->session, rc, + "Would block sending window adjust"); + return rc; + } + else if(rc) { + channel->adjust_queue = adjustment; + return _libssh2_error(channel->session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send transfer-window adjustment " + "packet, deferring"); + } + else { + channel->remote.window_size += adjustment; + } + + channel->adjust_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_receive_window_adjust + * + * DEPRECATED + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Returns the new size of the receive window (as understood by remote end). + * Note that it might return EAGAIN too which is highly stupid. + * + */ +LIBSSH2_API unsigned long +libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, + unsigned long adj, + unsigned char force) +{ + unsigned int window; + int rc; + + if(!channel) + return (unsigned long)LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, adj, + force, &window)); + + /* stupid - but this is how it was made to work before and this is just + kept for backwards compatibility */ + return rc ? (unsigned long)rc : window; +} + +/* + * libssh2_channel_receive_window_adjust2 + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Stores the new size of the receive window in the data 'window' points to. + * + * Returns the "normal" error code: 0 for success, negative for failure. + */ +LIBSSH2_API int +libssh2_channel_receive_window_adjust2(LIBSSH2_CHANNEL *channel, + unsigned long adj, + unsigned char force, + unsigned int *window) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, adj, force, + window)); + return rc; +} + +int +_libssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) +{ + if(channel->extData2_state == libssh2_NB_state_idle) { + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Setting channel %lu/%lu handle_extended_data" + " mode to %d", + channel->local.id, channel->remote.id, ignore_mode); + channel->remote.extended_data_ignore_mode = (char)ignore_mode; + + channel->extData2_state = libssh2_NB_state_created; + } + + if(channel->extData2_state == libssh2_NB_state_idle) { + if(ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) { + int rc = + _libssh2_channel_flush(channel, + LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); + if(LIBSSH2_ERROR_EAGAIN == rc) + return rc; + } + } + + channel->extData2_state = libssh2_NB_state_idle; + return 0; +} + +/* + * libssh2_channel_handle_extended_data2() + * + */ +LIBSSH2_API int +libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, + int mode) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_extended_data(channel, + mode)); + return rc; +} + +/* + * libssh2_channel_handle_extended_data + * + * DEPRECATED DO NOTE USE! + * + * How should extended data look to the calling app? Keep it in separate + * channels[_read() _read_stdder()]? (NORMAL) Merge the extended data to the + * standard data? [everything via _read()]? (MERGE) Ignore it entirely [toss + * out packets as they come in]? (IGNORE) + */ +LIBSSH2_API void +libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, + int ignore_mode) +{ + (void)libssh2_channel_handle_extended_data2(channel, ignore_mode); +} + + + +/* + * _libssh2_channel_read + * + * Read data from a channel + * + * It is important to not return 0 until the currently read channel is + * complete. If we read stuff from the wire but it was no payload data to fill + * in the buffer with, we MUST make sure to return LIBSSH2_ERROR_EAGAIN. + * + * The receive window must be maintained (enlarged) by the user of this + * function. + */ +ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + size_t bytes_read = 0; + size_t bytes_want; + int unlink_packet; + LIBSSH2_PACKET *read_packet; + LIBSSH2_PACKET *read_next; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "channel_read() wants %d bytes from channel %lu/%lu " + "stream #%d", + (int) buflen, channel->local.id, channel->remote.id, + stream_id); + + /* expand the receiving window first if it has become too narrow */ + if((channel->read_state == libssh2_NB_state_jump1) || + (channel->remote.window_size < + channel->remote.window_size_initial / 4 * 3 + buflen) ) { + + uint32_t adjustment = channel->remote.window_size_initial + buflen - + channel->remote.window_size; + if(adjustment < LIBSSH2_CHANNEL_MINADJUST) + adjustment = LIBSSH2_CHANNEL_MINADJUST; + + /* the actual window adjusting may not finish so we need to deal with + this special state here */ + channel->read_state = libssh2_NB_state_jump1; + rc = _libssh2_channel_receive_window_adjust(channel, adjustment, + 0, NULL); + if(rc) + return rc; + + channel->read_state = libssh2_NB_state_idle; + } + + /* Process all pending incoming packets. Tests prove that this way + produces faster transfers. */ + do { + rc = _libssh2_transport_read(session); + } while(rc > 0); + + if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) + return _libssh2_error(session, rc, "transport read"); + + read_packet = _libssh2_list_first(&session->packets); + while(read_packet && (bytes_read < buflen)) { + /* previously this loop condition also checked for + !channel->remote.close but we cannot let it do this: + + We may have a series of packets to read that are still pending even + if a close has been received. Acknowledging the close too early + makes us flush buffers prematurely and loose data. + */ + + LIBSSH2_PACKET *readpkt = read_packet; + + /* In case packet gets destroyed during this iteration */ + read_next = _libssh2_list_next(&readpkt->node); + + if(readpkt->data_len < 5) { + read_packet = read_next; + + if(readpkt->data_len != 1 || + readpkt->data[0] != SSH_MSG_REQUEST_FAILURE) { + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + } + + continue; + } + + channel->read_local_id = + _libssh2_ntohu32(readpkt->data + 1); + + /* + * Either we asked for a specific extended data stream + * (and data was available), + * or the standard stream (and data was available), + * or the standard stream with extended_data_merge + * enabled and data was available + */ + if((stream_id + && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == channel->read_local_id) + && (readpkt->data_len >= 9) + && (stream_id == (int) _libssh2_ntohu32(readpkt->data + 5))) + || (!stream_id && (readpkt->data[0] == SSH_MSG_CHANNEL_DATA) + && (channel->local.id == channel->read_local_id)) + || (!stream_id + && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == channel->read_local_id) + && (channel->remote.extended_data_ignore_mode == + LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { + + /* figure out much more data we want to read */ + bytes_want = buflen - bytes_read; + unlink_packet = FALSE; + + if(bytes_want >= (readpkt->data_len - readpkt->data_head)) { + /* we want more than this node keeps, so adjust the number and + delete this node after the copy */ + bytes_want = readpkt->data_len - readpkt->data_head; + unlink_packet = TRUE; + } + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "channel_read() got %d of data from %lu/%lu/%d%s", + bytes_want, channel->local.id, + channel->remote.id, stream_id, + unlink_packet?" [ul]":""); + + /* copy data from this struct to the target buffer */ + memcpy(&buf[bytes_read], + &readpkt->data[readpkt->data_head], bytes_want); + + /* advance pointer and counter */ + readpkt->data_head += bytes_want; + bytes_read += bytes_want; + + /* if drained, remove from list */ + if(unlink_packet) { + /* detach readpkt from session->packets list */ + _libssh2_list_remove(&readpkt->node); + + LIBSSH2_FREE(session, readpkt->data); + LIBSSH2_FREE(session, readpkt); + } + } + + /* check the next struct in the chain */ + read_packet = read_next; + } + + if(!bytes_read) { + /* If the channel is already at EOF or even closed, we need to signal + that back. We may have gotten that info while draining the incoming + transport layer until EAGAIN so we must not be fooled by that + return code. */ + if(channel->remote.eof || channel->remote.close) + return 0; + else if(rc != LIBSSH2_ERROR_EAGAIN) + return 0; + + /* if the transport layer said EAGAIN then we say so as well */ + return _libssh2_error(session, rc, "would block"); + } + + channel->read_avail -= bytes_read; + channel->remote.window_size -= bytes_read; + + return bytes_read; +} + +/* + * libssh2_channel_read_ex + * + * Read data from a channel (blocking or non-blocking depending on set state) + * + * When this is done non-blocking, it is important to not return 0 until the + * currently read channel is complete. If we read stuff from the wire but it + * was no payload data to fill in the buffer with, we MUST make sure to return + * LIBSSH2_ERROR_EAGAIN. + * + * This function will first make sure there's a receive window enough to + * receive a full buffer's wort of contents. An application may choose to + * adjust the receive window more to increase transfer performance. + */ +LIBSSH2_API ssize_t +libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, + size_t buflen) +{ + int rc; + unsigned long recv_window; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + recv_window = libssh2_channel_window_read_ex(channel, NULL, NULL); + + if(buflen > recv_window) { + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, buflen, + 1, NULL)); + } + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_read(channel, stream_id, buf, buflen)); + return rc; +} + +/* + * _libssh2_channel_packet_data_len + * + * Return the size of the data block of the current packet, or 0 if there + * isn't a packet. + */ +size_t +_libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) +{ + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_PACKET *read_packet; + LIBSSH2_PACKET *next_packet; + uint32_t read_local_id; + + read_packet = _libssh2_list_first(&session->packets); + if(read_packet == NULL) + return 0; + + while(read_packet) { + + next_packet = _libssh2_list_next(&read_packet->node); + + if(read_packet->data_len < 5) { + read_packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + read_local_id = _libssh2_ntohu32(read_packet->data + 1); + + /* + * Either we asked for a specific extended data stream + * (and data was available), + * or the standard stream (and data was available), + * or the standard stream with extended_data_merge + * enabled and data was available + */ + if((stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == read_local_id) + && (read_packet->data_len >= 9) + && (stream_id == (int) _libssh2_ntohu32(read_packet->data + 5))) + || + (!stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_DATA) + && (channel->local.id == read_local_id)) + || + (!stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == read_local_id) + && (channel->remote.extended_data_ignore_mode + == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { + return (read_packet->data_len - read_packet->data_head); + } + + read_packet = next_packet; + } + + return 0; +} + +/* + * _libssh2_channel_write + * + * Send data to a channel. Note that if this returns EAGAIN, the caller must + * call this function again with the SAME input arguments. + * + * Returns: number of bytes sent, or if it returns a negative number, that is + * the error code! + */ +ssize_t +_libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen) +{ + int rc = 0; + LIBSSH2_SESSION *session = channel->session; + ssize_t wrote = 0; /* counter for this specific this call */ + + /* In theory we could split larger buffers into several smaller packets + * but it turns out to be really hard and nasty to do while still offering + * the API/prototype. + * + * Instead we only deal with the first 32K in this call and for the parent + * function to call it again with the remainder! 32K is a conservative + * limit based on the text in RFC4253 section 6.1. + */ + if(buflen > 32700) + buflen = 32700; + + if(channel->write_state == libssh2_NB_state_idle) { + unsigned char *s = channel->write_packet; + + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Writing %d bytes on channel %lu/%lu, stream #%d", + (int) buflen, channel->local.id, channel->remote.id, + stream_id); + + if(channel->local.close) + return _libssh2_error(channel->session, + LIBSSH2_ERROR_CHANNEL_CLOSED, + "We've already closed this channel"); + else if(channel->local.eof) + return _libssh2_error(channel->session, + LIBSSH2_ERROR_CHANNEL_EOF_SENT, + "EOF has already been received, " + "data might be ignored"); + + /* drain the incoming flow first, mostly to make sure we get all + * pending window adjust packets */ + do + rc = _libssh2_transport_read(session); + while(rc > 0); + + if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) { + return _libssh2_error(channel->session, rc, + "Failure while draining incoming flow"); + } + + if(channel->local.window_size <= 0) { + /* there's no room for data so we stop */ + + /* Waiting on the socket to be writable would be wrong because we + * would be back here immediately, but a readable socket might + * herald an incoming window adjustment. + */ + session->socket_block_directions = LIBSSH2_SESSION_BLOCK_INBOUND; + + return (rc == LIBSSH2_ERROR_EAGAIN?rc:0); + } + + channel->write_bufwrite = buflen; + + *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : + SSH_MSG_CHANNEL_DATA; + _libssh2_store_u32(&s, channel->remote.id); + if(stream_id) + _libssh2_store_u32(&s, stream_id); + + /* Don't exceed the remote end's limits */ + /* REMEMBER local means local as the SOURCE of the data */ + if(channel->write_bufwrite > channel->local.window_size) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Splitting write block due to %lu byte " + "window_size on %lu/%lu/%d", + channel->local.window_size, channel->local.id, + channel->remote.id, stream_id); + channel->write_bufwrite = channel->local.window_size; + } + if(channel->write_bufwrite > channel->local.packet_size) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Splitting write block due to %lu byte " + "packet_size on %lu/%lu/%d", + channel->local.packet_size, channel->local.id, + channel->remote.id, stream_id); + channel->write_bufwrite = channel->local.packet_size; + } + /* store the size here only, the buffer is passed in as-is to + _libssh2_transport_send() */ + _libssh2_store_u32(&s, channel->write_bufwrite); + channel->write_packet_len = s - channel->write_packet; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Sending %d bytes on channel %lu/%lu, stream_id=%d", + (int) channel->write_bufwrite, channel->local.id, + channel->remote.id, stream_id); + + channel->write_state = libssh2_NB_state_created; + } + + if(channel->write_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->write_packet, + channel->write_packet_len, + buf, channel->write_bufwrite); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, rc, + "Unable to send channel data"); + } + else if(rc) { + channel->write_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel data"); + } + /* Shrink local window size */ + channel->local.window_size -= channel->write_bufwrite; + + wrote += channel->write_bufwrite; + + /* Since _libssh2_transport_write() succeeded, we must return + now to allow the caller to provide the next chunk of data. + + We cannot move on to send the next piece of data that may + already have been provided in this same function call, as we + risk getting EAGAIN for that and we can't return information + both about sent data as well as EAGAIN. So, by returning short + now, the caller will call this function again with new data to + send */ + + channel->write_state = libssh2_NB_state_idle; + + return wrote; + } + + return LIBSSH2_ERROR_INVAL; /* reaching this point is really bad */ +} + +/* + * libssh2_channel_write_ex + * + * Send data to a channel + */ +LIBSSH2_API ssize_t +libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, + const char *buf, size_t buflen) +{ + ssize_t rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_write(channel, stream_id, + (unsigned char *)buf, buflen)); + return rc; +} + +/* + * channel_send_eof + * + * Send EOF on channel + */ +static int channel_send_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char packet[5]; /* packet_type(1) + channelno(4) */ + int rc; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Sending EOF on channel %lu/%lu", + channel->local.id, channel->remote.id); + packet[0] = SSH_MSG_CHANNEL_EOF; + _libssh2_htonu32(packet + 1, channel->remote.id); + rc = _libssh2_transport_send(session, packet, 5, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending EOF"); + return rc; + } + else if(rc) { + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send EOF on channel"); + } + channel->local.eof = 1; + + return 0; +} + +/* + * libssh2_channel_send_eof + * + * Send EOF on channel + */ +LIBSSH2_API int +libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_send_eof(channel)); + return rc; +} + +/* + * libssh2_channel_eof + * + * Read channel's eof status + */ +LIBSSH2_API int +libssh2_channel_eof(LIBSSH2_CHANNEL * channel) +{ + LIBSSH2_SESSION *session; + LIBSSH2_PACKET *packet; + LIBSSH2_PACKET *next_packet; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + session = channel->session; + packet = _libssh2_list_first(&session->packets); + + while(packet) { + + next_packet = _libssh2_list_next(&packet->node); + + if(packet->data_len < 1) { + packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + if(((packet->data[0] == SSH_MSG_CHANNEL_DATA) + || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && ((packet->data_len >= 5) + && (channel->local.id == _libssh2_ntohu32(packet->data + 1)))) { + /* There's data waiting to be read yet, mask the EOF status */ + return 0; + } + packet = next_packet; + } + + return channel->remote.eof; +} + +/* + * channel_wait_eof + * + * Awaiting channel EOF + */ +static int channel_wait_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + + if(channel->wait_eof_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Awaiting EOF for channel %lu/%lu", channel->local.id, + channel->remote.id); + + channel->wait_eof_state = libssh2_NB_state_created; + } + + /* + * While channel is not eof, read more packets from the network. + * Either the EOF will be set or network timeout will occur. + */ + do { + if(channel->remote.eof) { + break; + } + + if((channel->remote.window_size == channel->read_avail) && + session->api_block_mode) + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_FULL, + "Receiving channel window " + "has been exhausted"); + + rc = _libssh2_transport_read(session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc < 0) { + channel->wait_eof_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "_libssh2_transport_read() bailed out!"); + } + } while(1); + + channel->wait_eof_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_wait_eof + * + * Awaiting channel EOF + */ +LIBSSH2_API int +libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_wait_eof(channel)); + return rc; +} + +int _libssh2_channel_close(LIBSSH2_CHANNEL * channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc = 0; + + if(channel->local.close) { + /* Already closed, act like we sent another close, + * even though we didn't... shhhhhh */ + channel->close_state = libssh2_NB_state_idle; + return 0; + } + + if(!channel->local.eof) { + rc = channel_send_eof(channel); + if(rc) { + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + _libssh2_error(session, rc, + "Unable to send EOF, but closing channel anyway"); + } + } + + /* ignore if we have received a remote eof or not, as it is now too + late for us to wait for it. Continue closing! */ + + if(channel->close_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Closing channel %lu/%lu", + channel->local.id, channel->remote.id); + + channel->close_packet[0] = SSH_MSG_CHANNEL_CLOSE; + _libssh2_htonu32(channel->close_packet + 1, channel->remote.id); + + channel->close_state = libssh2_NB_state_created; + } + + if(channel->close_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->close_packet, 5, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending close-channel"); + return rc; + + } + else if(rc) { + _libssh2_error(session, rc, + "Unable to send close-channel request, " + "but closing anyway"); + /* skip waiting for the response and fall through to + LIBSSH2_CHANNEL_CLOSE below */ + + } + else + channel->close_state = libssh2_NB_state_sent; + } + + if(channel->close_state == libssh2_NB_state_sent) { + /* We must wait for the remote SSH_MSG_CHANNEL_CLOSE message */ + + while(!channel->remote.close && !rc && + (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED)) + rc = _libssh2_transport_read(session); + } + + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* set the local close state first when we're perfectly confirmed to + not do any more EAGAINs */ + channel->local.close = 1; + + /* We call the callback last in this function to make it keep the local + data as long as EAGAIN is returned. */ + if(channel->close_cb) { + LIBSSH2_CHANNEL_CLOSE(session, channel); + } + + channel->close_state = libssh2_NB_state_idle; + } + + /* return 0 or an error */ + return rc >= 0 ? 0 : rc; +} + +/* + * libssh2_channel_close + * + * Close a channel + */ +LIBSSH2_API int +libssh2_channel_close(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_close(channel) ); + return rc; +} + +/* + * channel_wait_closed + * + * Awaiting channel close after EOF + */ +static int channel_wait_closed(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + + if(!channel->remote.eof) { + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "libssh2_channel_wait_closed() invoked when " + "channel is not in EOF state"); + } + + if(channel->wait_closed_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Awaiting close of channel %lu/%lu", channel->local.id, + channel->remote.id); + + channel->wait_closed_state = libssh2_NB_state_created; + } + + /* + * While channel is not closed, read more packets from the network. + * Either the channel will be closed or network timeout will occur. + */ + if(!channel->remote.close) { + do { + rc = _libssh2_transport_read(session); + if(channel->remote.close) + /* it is now closed, move on! */ + break; + } while(rc > 0); + if(rc < 0) + return rc; + } + + channel->wait_closed_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_wait_closed + * + * Awaiting channel close after EOF + */ +LIBSSH2_API int +libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_wait_closed(channel)); + return rc; +} + +/* + * _libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +int _libssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char channel_id[4]; + unsigned char *data; + size_t data_len; + int rc; + + assert(session); + + if(channel->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Freeing channel %lu/%lu resources", channel->local.id, + channel->remote.id); + + channel->free_state = libssh2_NB_state_created; + } + + /* Allow channel freeing even when the socket has lost its connection */ + if(!channel->local.close + && (session->socket_state == LIBSSH2_SOCKET_CONNECTED)) { + rc = _libssh2_channel_close(channel); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + /* ignore all other errors as they otherwise risk blocking the channel + free from happening */ + } + + channel->free_state = libssh2_NB_state_idle; + + if(channel->exit_signal) { + LIBSSH2_FREE(session, channel->exit_signal); + } + + /* + * channel->remote.close *might* not be set yet, Well... + * We've sent the close packet, what more do you want? + * Just let packet_add ignore it when it finally arrives + */ + + /* Clear out packets meant for this channel */ + _libssh2_htonu32(channel_id, channel->local.id); + while((_libssh2_packet_ask(session, SSH_MSG_CHANNEL_DATA, &data, + &data_len, 1, channel_id, 4) >= 0) + || + (_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, + &data_len, 1, channel_id, 4) >= 0)) { + LIBSSH2_FREE(session, data); + } + + /* free "channel_type" */ + if(channel->channel_type) { + LIBSSH2_FREE(session, channel->channel_type); + } + + /* Unlink from channel list */ + _libssh2_list_remove(&channel->node); + + /* + * Make sure all memory used in the state variables are free + */ + if(channel->setenv_packet) { + LIBSSH2_FREE(session, channel->setenv_packet); + } + if(channel->reqX11_packet) { + LIBSSH2_FREE(session, channel->reqX11_packet); + } + if(channel->process_packet) { + LIBSSH2_FREE(session, channel->process_packet); + } + + LIBSSH2_FREE(session, channel); + + return 0; +} + +/* + * libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +LIBSSH2_API int +libssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_free(channel)); + return rc; +} +/* + * libssh2_channel_window_read_ex + * + * Check the status of the read window. Returns the number of bytes which the + * remote end may send without overflowing the window limit read_avail (if + * passed) will be populated with the number of bytes actually available to be + * read window_size_initial (if passed) will be populated with the + * window_size_initial as defined by the channel_open request + */ +LIBSSH2_API unsigned long +libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, + unsigned long *read_avail, + unsigned long *window_size_initial) +{ + if(!channel) + return 0; /* no channel, no window! */ + + if(window_size_initial) { + *window_size_initial = channel->remote.window_size_initial; + } + + if(read_avail) { + size_t bytes_queued = 0; + LIBSSH2_PACKET *next_packet; + LIBSSH2_PACKET *packet = + _libssh2_list_first(&channel->session->packets); + + while(packet) { + unsigned char packet_type; + next_packet = _libssh2_list_next(&packet->node); + + if(packet->data_len < 1) { + packet = next_packet; + _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, + "Unexpected packet length"); + continue; + } + + packet_type = packet->data[0]; + + if(((packet_type == SSH_MSG_CHANNEL_DATA) + || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && ((packet->data_len >= 5) + && (_libssh2_ntohu32(packet->data + 1) == + channel->local.id))) { + bytes_queued += packet->data_len - packet->data_head; + } + + packet = next_packet; + } + + *read_avail = bytes_queued; + } + + return channel->remote.window_size; +} + +/* + * libssh2_channel_window_write_ex + * + * Check the status of the write window Returns the number of bytes which may + * be safely written on the channel without blocking window_size_initial (if + * passed) will be populated with the size of the initial window as defined by + * the channel_open request + */ +LIBSSH2_API unsigned long +libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, + unsigned long *window_size_initial) +{ + if(!channel) + return 0; /* no channel, no window! */ + + if(window_size_initial) { + /* For locally initiated channels this is very often 0, so it's not + * *that* useful as information goes */ + *window_size_initial = channel->local.window_size_initial; + } + + return channel->local.window_size; +} +#endif diff --git a/lib/libssh2/channel.h b/lib/libssh2/channel.h new file mode 100644 index 0000000..403a260 --- /dev/null +++ b/lib/libssh2/channel.h @@ -0,0 +1,143 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_CHANNEL_H +#define __LIBSSH2_CHANNEL_H +/* Copyright (c) 2008-2010 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* + * _libssh2_channel_receive_window_adjust + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Always non-blocking. + */ +int _libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL * channel, + uint32_t adjustment, + unsigned char force, + unsigned int *store); + +/* + * _libssh2_channel_flush + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +int _libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid); + +/* + * _libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +int _libssh2_channel_free(LIBSSH2_CHANNEL *channel); + +int +_libssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode); + +/* + * _libssh2_channel_write + * + * Send data to a channel + */ +ssize_t +_libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen); + +/* + * _libssh2_channel_open + * + * Establish a generic session channel + */ +LIBSSH2_CHANNEL * +_libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, size_t message_len); + + +/* + * _libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +int +_libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len); + +/* + * _libssh2_channel_read + * + * Read data from a channel + * + * It is important to not return 0 until the currently read channel is + * complete. If we read stuff from the wire but it was no payload data to fill + * in the buffer with, we MUST make sure to return PACKET_EAGAIN. + */ +ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen); + +uint32_t _libssh2_channel_nextid(LIBSSH2_SESSION * session); + +LIBSSH2_CHANNEL *_libssh2_channel_locate(LIBSSH2_SESSION * session, + uint32_t channel_id); + +size_t _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, + int stream_id); + +int _libssh2_channel_close(LIBSSH2_CHANNEL * channel); + +/* + * _libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +int _libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); + +#endif /* __LIBSSH2_CHANNEL_H */ + +#endif diff --git a/lib/libssh2/comp.c b/lib/libssh2/comp.c new file mode 100644 index 0000000..e82b8c9 --- /dev/null +++ b/lib/libssh2/comp.c @@ -0,0 +1,379 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007, 2019, Sara Golemon + * Copyright (c) 2010-2014, Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#ifdef LIBSSH2_HAVE_ZLIB +#include +#undef compress /* dodge name clash with ZLIB macro */ +#endif + +#include "comp.h" + +/* ******** + * none * + ******** */ + +/* + * comp_method_none_comp + * + * Minimalist compression: Absolutely none + */ +static int +comp_method_none_comp(LIBSSH2_SESSION *session, + unsigned char *dest, + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract) +{ + (void) session; + (void) abstract; + (void) dest; + (void) dest_len; + (void) src; + (void) src_len; + + return 0; +} + +/* + * comp_method_none_decomp + * + * Minimalist decompression: Absolutely none + */ +static int +comp_method_none_decomp(LIBSSH2_SESSION * session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, void **abstract) +{ + (void) session; + (void) payload_limit; + (void) abstract; + *dest = (unsigned char *) src; + *dest_len = src_len; + return 0; +} + + + +static const LIBSSH2_COMP_METHOD comp_method_none = { + "none", + 0, /* not really compressing */ + 0, /* isn't used in userauth, go figure */ + NULL, + comp_method_none_comp, + comp_method_none_decomp, + NULL +}; + +#ifdef LIBSSH2_HAVE_ZLIB +/* ******** + * zlib * + ******** */ + +/* Memory management wrappers + * Yes, I realize we're doing a callback to a callback, + * Deal... + */ + +static voidpf +comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque; + + return (voidpf) LIBSSH2_ALLOC(session, items * size); +} + +static void +comp_method_zlib_free(voidpf opaque, voidpf address) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque; + + LIBSSH2_FREE(session, address); +} + + + +/* libssh2_comp_method_zlib_init + * All your bandwidth are belong to us (so save some) + */ +static int +comp_method_zlib_init(LIBSSH2_SESSION * session, int compr, + void **abstract) +{ + z_stream *strm; + int status; + + strm = LIBSSH2_CALLOC(session, sizeof(z_stream)); + if(!strm) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "zlib compression/decompression"); + } + + strm->opaque = (voidpf) session; + strm->zalloc = (alloc_func) comp_method_zlib_alloc; + strm->zfree = (free_func) comp_method_zlib_free; + if(compr) { + /* deflate */ + status = deflateInit(strm, Z_DEFAULT_COMPRESSION); + } + else { + /* inflate */ + status = inflateInit(strm); + } + + if(status != Z_OK) { + LIBSSH2_FREE(session, strm); + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib error %d", status); + return LIBSSH2_ERROR_COMPRESS; + } + *abstract = strm; + + return LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_comp_method_zlib_comp + * + * Compresses source to destination. Without allocation. + */ +static int +comp_method_zlib_comp(LIBSSH2_SESSION *session, + unsigned char *dest, + + /* dest_len is a pointer to allow this function to + update it with the final actual size used */ + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract) +{ + z_stream *strm = *abstract; + int out_maxlen = *dest_len; + int status; + + strm->next_in = (unsigned char *) src; + strm->avail_in = src_len; + strm->next_out = dest; + strm->avail_out = out_maxlen; + + status = deflate(strm, Z_PARTIAL_FLUSH); + + if((status == Z_OK) && (strm->avail_out > 0)) { + *dest_len = out_maxlen - strm->avail_out; + return 0; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib compression error %d, avail_out", + status, strm->avail_out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, "compression failure"); +} + +/* + * libssh2_comp_method_zlib_decomp + * + * Decompresses source to destination. Allocates the output memory. + */ +static int +comp_method_zlib_decomp(LIBSSH2_SESSION * session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, void **abstract) +{ + z_stream *strm = *abstract; + /* A short-term alloc of a full data chunk is better than a series of + reallocs */ + char *out; + size_t out_maxlen = src_len; + + if(src_len <= SIZE_MAX / 4) + out_maxlen = src_len * 4; + else + out_maxlen = payload_limit; + + /* If strm is null, then we have not yet been initialized. */ + if(strm == NULL) + return _libssh2_error(session, LIBSSH2_ERROR_COMPRESS, + "decompression uninitialized");; + + /* In practice they never come smaller than this */ + if(out_maxlen < 25) + out_maxlen = 25; + + if(out_maxlen > payload_limit) + out_maxlen = payload_limit; + + strm->next_in = (unsigned char *) src; + strm->avail_in = src_len; + strm->next_out = (unsigned char *) LIBSSH2_ALLOC(session, out_maxlen); + out = (char *) strm->next_out; + strm->avail_out = out_maxlen; + if(!strm->next_out) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate decompression buffer"); + + /* Loop until it's all inflated or hit error */ + for(;;) { + int status; + size_t out_ofs; + char *newout; + + status = inflate(strm, Z_PARTIAL_FLUSH); + + if(status == Z_OK) { + if(strm->avail_out > 0) + /* status is OK and the output buffer has not been exhausted + so we're done */ + break; + } + else if(status == Z_BUF_ERROR) { + /* the input data has been exhausted so we are done */ + break; + } + else { + /* error state */ + LIBSSH2_FREE(session, out); + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib error %d", status); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "decompression failure"); + } + + if(out_maxlen > payload_limit || out_maxlen > SIZE_MAX / 2) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "Excessive growth in decompression phase"); + } + + /* If we get here we need to grow the output buffer and try again */ + out_ofs = out_maxlen - strm->avail_out; + out_maxlen *= 2; + newout = LIBSSH2_REALLOC(session, out, out_maxlen); + if(!newout) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to expand decompression buffer"); + } + out = newout; + strm->next_out = (unsigned char *) out + out_ofs; + strm->avail_out = out_maxlen - out_ofs; + } + + *dest = (unsigned char *) out; + *dest_len = out_maxlen - strm->avail_out; + + return 0; +} + + +/* libssh2_comp_method_zlib_dtor + * All done, no more compression for you + */ +static int +comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compr, void **abstract) +{ + z_stream *strm = *abstract; + + if(strm) { + if(compr) + deflateEnd(strm); + else + inflateEnd(strm); + LIBSSH2_FREE(session, strm); + } + + *abstract = NULL; + return 0; +} + +static const LIBSSH2_COMP_METHOD comp_method_zlib = { + "zlib", + 1, /* yes, this compresses */ + 1, /* do compression during userauth */ + comp_method_zlib_init, + comp_method_zlib_comp, + comp_method_zlib_decomp, + comp_method_zlib_dtor, +}; + +static const LIBSSH2_COMP_METHOD comp_method_zlib_openssh = { + "zlib@openssh.com", + 1, /* yes, this compresses */ + 0, /* don't use compression during userauth */ + comp_method_zlib_init, + comp_method_zlib_comp, + comp_method_zlib_decomp, + comp_method_zlib_dtor, +}; +#endif /* LIBSSH2_HAVE_ZLIB */ + +/* If compression is enabled by the API, then this array is used which then + may allow compression if zlib is available at build time */ +static const LIBSSH2_COMP_METHOD *comp_methods[] = { +#ifdef LIBSSH2_HAVE_ZLIB + &comp_method_zlib, + &comp_method_zlib_openssh, +#endif /* LIBSSH2_HAVE_ZLIB */ + &comp_method_none, + NULL +}; + +/* If compression is disabled by the API, then this array is used */ +static const LIBSSH2_COMP_METHOD *no_comp_methods[] = { + &comp_method_none, + NULL +}; + +const LIBSSH2_COMP_METHOD ** +_libssh2_comp_methods(LIBSSH2_SESSION *session) +{ + if(session->flag.compress) + return comp_methods; + else + return no_comp_methods; +} +#endif diff --git a/lib/libssh2/comp.h b/lib/libssh2/comp.h new file mode 100644 index 0000000..e1bc97a --- /dev/null +++ b/lib/libssh2/comp.h @@ -0,0 +1,46 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_COMP_H +#define __LIBSSH2_COMP_H +/* Copyright (C) 2009-2010 by Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" + +const LIBSSH2_COMP_METHOD **_libssh2_comp_methods(LIBSSH2_SESSION *session); + +#endif /* __LIBSSH2_COMP_H */ +#endif diff --git a/lib/libssh2/crypt.c b/lib/libssh2/crypt.c new file mode 100644 index 0000000..9be925f --- /dev/null +++ b/lib/libssh2/crypt.c @@ -0,0 +1,353 @@ +#if defined(ESP32) +/* Copyright (c) 2009, 2010 Simon Josefsson + * Copyright (c) 2004-2007, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifdef LIBSSH2_CRYPT_NONE + +/* crypt_none_crypt + * Minimalist cipher: VERY secure *wink* + */ +static int +crypt_none_crypt(LIBSSH2_SESSION * session, unsigned char *buf, + void **abstract) +{ + /* Do nothing to the data! */ + return 0; +} + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_none = { + "none", + "DEK-Info: NONE", + 8, /* blocksize (SSH2 defines minimum blocksize as 8) */ + 0, /* iv_len */ + 0, /* secret_len */ + 0, /* flags */ + NULL, + crypt_none_crypt, + NULL +}; +#endif /* LIBSSH2_CRYPT_NONE */ + +struct crypt_ctx +{ + int encrypt; + _libssh2_cipher_type(algo); + _libssh2_cipher_ctx h; +}; + +static int +crypt_init(LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, + unsigned char *iv, int *free_iv, + unsigned char *secret, int *free_secret, + int encrypt, void **abstract) +{ + struct crypt_ctx *ctx = LIBSSH2_ALLOC(session, + sizeof(struct crypt_ctx)); + if(!ctx) + return LIBSSH2_ERROR_ALLOC; + + ctx->encrypt = encrypt; + ctx->algo = method->algo; + if(_libssh2_cipher_init(&ctx->h, ctx->algo, iv, secret, encrypt)) { + LIBSSH2_FREE(session, ctx); + return -1; + } + *abstract = ctx; + *free_iv = 1; + *free_secret = 1; + return 0; +} + +static int +crypt_encrypt(LIBSSH2_SESSION * session, unsigned char *block, + size_t blocksize, void **abstract) +{ + struct crypt_ctx *cctx = *(struct crypt_ctx **) abstract; + (void) session; + return _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block, + blocksize); +} + +static int +crypt_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + struct crypt_ctx **cctx = (struct crypt_ctx **) abstract; + if(cctx && *cctx) { + _libssh2_cipher_dtor(&(*cctx)->h); + LIBSSH2_FREE(session, *cctx); + *abstract = NULL; + } + return 0; +} + +#if LIBSSH2_AES_CTR +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_ctr = { + "aes128-ctr", + "", + 16, /* blocksize */ + 16, /* initial value length */ + 16, /* secret length -- 16*8 == 128bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes128ctr +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_ctr = { + "aes192-ctr", + "", + 16, /* blocksize */ + 16, /* initial value length */ + 24, /* secret length -- 24*8 == 192bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes192ctr +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_ctr = { + "aes256-ctr", + "", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256ctr +}; +#endif + +#if LIBSSH2_AES +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_cbc = { + "aes128-cbc", + "DEK-Info: AES-128-CBC", + 16, /* blocksize */ + 16, /* initial value length */ + 16, /* secret length -- 16*8 == 128bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes128 +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_cbc = { + "aes192-cbc", + "DEK-Info: AES-192-CBC", + 16, /* blocksize */ + 16, /* initial value length */ + 24, /* secret length -- 24*8 == 192bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes192 +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_cbc = { + "aes256-cbc", + "DEK-Info: AES-256-CBC", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256 +}; + +/* rijndael-cbc@lysator.liu.se == aes256-cbc */ +static const LIBSSH2_CRYPT_METHOD + libssh2_crypt_method_rijndael_cbc_lysator_liu_se = { + "rijndael-cbc@lysator.liu.se", + "DEK-Info: AES-256-CBC", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256 +}; +#endif /* LIBSSH2_AES */ + +#if LIBSSH2_BLOWFISH +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_blowfish_cbc = { + "blowfish-cbc", + "", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_blowfish +}; +#endif /* LIBSSH2_BLOWFISH */ + +#if LIBSSH2_RC4 +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour = { + "arcfour", + "DEK-Info: RC4", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_arcfour +}; + +static int +crypt_init_arcfour128(LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, + unsigned char *iv, int *free_iv, + unsigned char *secret, int *free_secret, + int encrypt, void **abstract) +{ + int rc; + + rc = crypt_init(session, method, iv, free_iv, secret, free_secret, + encrypt, abstract); + if(rc == 0) { + struct crypt_ctx *cctx = *(struct crypt_ctx **) abstract; + unsigned char block[8]; + size_t discard = 1536; + for(; discard; discard -= 8) + _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block, + method->blocksize); + } + + return rc; +} + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour128 = { + "arcfour128", + "", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init_arcfour128, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_arcfour +}; +#endif /* LIBSSH2_RC4 */ + +#if LIBSSH2_CAST +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_cast128_cbc = { + "cast128-cbc", + "", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_cast5 +}; +#endif /* LIBSSH2_CAST */ + +#if LIBSSH2_3DES +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = { + "3des-cbc", + "DEK-Info: DES-EDE3-CBC", + 8, /* blocksize */ + 8, /* initial value length */ + 24, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_3des +}; +#endif + +/* These are the crypt methods that are available to be negotiated. Methods + towards the start are chosen in preference to ones further down the list. */ +static const LIBSSH2_CRYPT_METHOD *_libssh2_crypt_methods[] = { +#if LIBSSH2_AES_CTR + &libssh2_crypt_method_aes256_ctr, + &libssh2_crypt_method_aes192_ctr, + &libssh2_crypt_method_aes128_ctr, +#endif /* LIBSSH2_AES */ +#if LIBSSH2_AES + &libssh2_crypt_method_aes256_cbc, + &libssh2_crypt_method_rijndael_cbc_lysator_liu_se, /* == aes256-cbc */ + &libssh2_crypt_method_aes192_cbc, + &libssh2_crypt_method_aes128_cbc, +#endif /* LIBSSH2_AES */ +#if LIBSSH2_BLOWFISH + &libssh2_crypt_method_blowfish_cbc, +#endif /* LIBSSH2_BLOWFISH */ +#if LIBSSH2_RC4 + &libssh2_crypt_method_arcfour128, + &libssh2_crypt_method_arcfour, +#endif /* LIBSSH2_RC4 */ +#if LIBSSH2_CAST + &libssh2_crypt_method_cast128_cbc, +#endif /* LIBSSH2_CAST */ +#if LIBSSH2_3DES + &libssh2_crypt_method_3des_cbc, +#endif /* LIBSSH2_DES */ +#ifdef LIBSSH2_CRYPT_NONE + &libssh2_crypt_method_none, +#endif + NULL +}; + +/* Expose to kex.c */ +const LIBSSH2_CRYPT_METHOD ** +libssh2_crypt_methods(void) +{ + return _libssh2_crypt_methods; +} +#endif diff --git a/lib/libssh2/crypto.h b/lib/libssh2/crypto.h new file mode 100644 index 0000000..a5893f1 --- /dev/null +++ b/lib/libssh2/crypto.h @@ -0,0 +1,338 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_CRYPTO_H +#define __LIBSSH2_CRYPTO_H +/* Copyright (C) 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2010-2019 Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#if defined(LIBSSH2_OPENSSL) || defined(LIBSSH2_WOLFSSL) +#include "openssl.h" +#endif + +#ifdef LIBSSH2_LIBGCRYPT +#include "libgcrypt.h" +#endif + +#ifdef LIBSSH2_WINCNG +#include "wincng.h" +#endif + +#ifdef LIBSSH2_OS400QC3 +#include "os400qc3.h" +#endif + +#ifdef LIBSSH2_MBEDTLS +#include "mbedtls.h" +#endif + +#define LIBSSH2_ED25519_KEY_LEN 32 +#define LIBSSH2_ED25519_PRIVATE_KEY_LEN 64 +#define LIBSSH2_ED25519_SIG_LEN 64 + +#if LIBSSH2_RSA +int _libssh2_rsa_new(libssh2_rsa_ctx ** rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, unsigned long coefflen); +int _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); +int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsa, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len); +int _libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session, + libssh2_rsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, + size_t *signature_len); +#if LIBSSH2_RSA_SHA2 +int _libssh2_rsa_sha2_sign(LIBSSH2_SESSION * session, + libssh2_rsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, + size_t *signature_len); +int _libssh2_rsa_sha2_verify(libssh2_rsa_ctx * rsa, + size_t hash_len, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len); +#endif +int _libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); +#endif + +#if LIBSSH2_DSA +int _libssh2_dsa_new(libssh2_dsa_ctx ** dsa, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *gdata, + unsigned long glen, + const unsigned char *ydata, + unsigned long ylen, + const unsigned char *x, unsigned long x_len); +int _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); +int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, + const unsigned char *sig, + const unsigned char *m, unsigned long m_len); +int _libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx, + const unsigned char *hash, + unsigned long hash_len, unsigned char *sig); +int _libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); +#endif + +#if LIBSSH2_ECDSA +int +_libssh2_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx ** ecdsactx, + const unsigned char *k, + size_t k_len, + libssh2_curve_type type); + +int +_libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); + +int +_libssh2_ecdsa_new_private_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); + +int +_libssh2_ecdsa_verify(libssh2_ecdsa_ctx * ctx, + const unsigned char *r, size_t r_len, + const unsigned char *s, size_t s_len, + const unsigned char *m, size_t m_len); + +int +_libssh2_ecdsa_create_key(LIBSSH2_SESSION *session, + _libssh2_ec_key **out_private_key, + unsigned char **out_public_key_octal, + size_t *out_public_key_octal_len, + libssh2_curve_type curve_type); + +int +_libssh2_ecdh_gen_k(_libssh2_bn **k, _libssh2_ec_key *private_key, + const unsigned char *server_public_key, + size_t server_public_key_len); + +int +_libssh2_ecdsa_sign(LIBSSH2_SESSION *session, libssh2_ecdsa_ctx *ec_ctx, + const unsigned char *hash, unsigned long hash_len, + unsigned char **signature, size_t *signature_len); + +int _libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + +int _libssh2_ecdsa_new_private_frommemory_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + +libssh2_curve_type +_libssh2_ecdsa_get_curve_type(libssh2_ecdsa_ctx *ec_ctx); + +int +_libssh2_ecdsa_curve_type_from_name(const char *name, + libssh2_curve_type *out_type); + +#endif /* LIBSSH2_ECDSA */ + +#if LIBSSH2_ED25519 + +int +_libssh2_curve25519_new(LIBSSH2_SESSION *session, uint8_t **out_public_key, + uint8_t **out_private_key); + +int +_libssh2_curve25519_gen_k(_libssh2_bn **k, + uint8_t private_key[LIBSSH2_ED25519_KEY_LEN], + uint8_t server_public_key[LIBSSH2_ED25519_KEY_LEN]); + +int +_libssh2_ed25519_verify(libssh2_ed25519_ctx *ctx, const uint8_t *s, + size_t s_len, const uint8_t *m, size_t m_len); + +int +_libssh2_ed25519_new_private(libssh2_ed25519_ctx **ed_ctx, + LIBSSH2_SESSION *session, + const char *filename, const uint8_t *passphrase); + +int +_libssh2_ed25519_new_private_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filename, + const uint8_t *passphrase); + +int +_libssh2_ed25519_new_public(libssh2_ed25519_ctx **ed_ctx, + LIBSSH2_SESSION *session, + const unsigned char *raw_pub_key, + const uint8_t key_len); + +int +_libssh2_ed25519_sign(libssh2_ed25519_ctx *ctx, LIBSSH2_SESSION *session, + uint8_t **out_sig, size_t *out_sig_len, + const uint8_t *message, size_t message_len); + +int +_libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx **ed_ctx, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + +int +_libssh2_ed25519_new_private_frommemory_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + +#endif /* LIBSSH2_ED25519 */ + + +int _libssh2_cipher_init(_libssh2_cipher_ctx * h, + _libssh2_cipher_type(algo), + unsigned char *iv, + unsigned char *secret, int encrypt); + +int _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, + _libssh2_cipher_type(algo), + int encrypt, unsigned char *block, size_t blocksize); + +int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase); + +int _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); + + +int _libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); + +/** + * @function _libssh2_supported_key_sign_algorithms + * @abstract Returns supported algorithms used for upgrading public + * key signing RFC 8332 + * @discussion Based on the incoming key_method value, this function + * will return supported algorithms that can upgrade the key method + * @related _libssh2_key_sign_algorithm() + * @param key_method current key method, usually the default key sig method + * @param key_method_len length of the key method buffer + * @result comma seperated list of supported upgrade options per RFC 8332, if + * there is no upgrade option return NULL + */ + +const char * +_libssh2_supported_key_sign_algorithms(LIBSSH2_SESSION *session, + unsigned char *key_method, + size_t key_method_len); + +#endif /* __LIBSSH2_CRYPTO_H */ +#endif diff --git a/lib/libssh2/global.c b/lib/libssh2/global.c new file mode 100644 index 0000000..9aec87b --- /dev/null +++ b/lib/libssh2/global.c @@ -0,0 +1,80 @@ +#if defined(ESP32) +/* Copyright (c) 2010 Lars Nordin + * Copyright (C) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +static int _libssh2_initialized = 0; +static int _libssh2_init_flags = 0; + +LIBSSH2_API int +libssh2_init(int flags) +{ + if(_libssh2_initialized == 0 && !(flags & LIBSSH2_INIT_NO_CRYPTO)) { + libssh2_crypto_init(); + } + + _libssh2_initialized++; + _libssh2_init_flags |= flags; + + return 0; +} + +LIBSSH2_API void +libssh2_exit(void) +{ + if(_libssh2_initialized == 0) + return; + + _libssh2_initialized--; + + if(_libssh2_initialized == 0 && + !(_libssh2_init_flags & LIBSSH2_INIT_NO_CRYPTO)) { + libssh2_crypto_exit(); + } + + return; +} + +void +_libssh2_init_if_needed(void) +{ + if(_libssh2_initialized == 0) + (void)libssh2_init (0); +} +#endif diff --git a/lib/libssh2/hostkey.c b/lib/libssh2/hostkey.c new file mode 100644 index 0000000..290c426 --- /dev/null +++ b/lib/libssh2/hostkey.c @@ -0,0 +1,1389 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2006, Sara Golemon + * Copyright (c) 2009-2019 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#if LIBSSH2_RSA +/* *********** + * ssh-rsa * + *********** */ + +static int hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_rsa_init + * + * Initialize the server hostkey working area with e/n pair + */ +static int +hostkey_method_ssh_rsa_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + unsigned char *e, *n, *type; + size_t e_len, n_len, type_len; + struct string_buf buf; + + if(*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + if(hostkey_data_len < 19) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); + return -1; + } + + buf.data = (unsigned char *)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; + + if(_libssh2_get_string(&buf, &type, &type_len)) { + return -1; + } + + /* we accept one of 3 header types */ + if(type_len == 7 && strncmp("ssh-rsa", (char *)type, 7) == 0) { + /* ssh-rsa */ + } +#if LIBSSH2_RSA_SHA2 + else if(type_len == 12 && strncmp("rsa-sha2-256", (char *)type, 12) == 0) { + /* rsa-sha2-256 */ + } + else if(type_len == 12 && strncmp("rsa-sha2-512", (char *)type, 12) == 0) { + /* rsa-sha2-512 */ + } +#endif + else { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "unexpected rsa type: %.*s", type_len, type); + return -1; + } + + if(_libssh2_get_string(&buf, &e, &e_len)) + return -1; + + if(_libssh2_get_string(&buf, &n, &n_len)) + return -1; + + if(!_libssh2_eob(&buf)) + return -1; + + if(_libssh2_rsa_new(&rsactx, e, e_len, n, n_len, NULL, 0, + NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0)) { + return -1; + } + + *abstract = rsactx; + + return 0; +} + +/* + * hostkey_method_ssh_rsa_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + int ret; + + if(*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_rsa_new_private(&rsactx, session, privkeyfile, passphrase); + if(ret) { + return -1; + } + + *abstract = rsactx; + + return 0; +} + +/* + * hostkey_method_ssh_rsa_initPEMFromMemory + * + * Load a Private Key from a memory + */ +static int +hostkey_method_ssh_rsa_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + int ret; + + if(*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_rsa_new_private_frommemory(&rsactx, session, + privkeyfiledata, + privkeyfiledata_len, passphrase); + if(ret) { + return -1; + } + + *abstract = rsactx; + + return 0; +} + +/* + * hostkey_method_ssh_rsa_sign + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_rsa_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + /* Skip past keyname_len(4) + keyname(7){"ssh-rsa"} + signature_len(4) */ + if(sig_len < 15) + return -1; + + sig += 15; + sig_len -= 15; + return _libssh2_rsa_sha1_verify(rsactx, sig, sig_len, m, m_len); +} + +/* + * hostkey_method_ssh_rsa_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_rsa_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + +#ifdef _libssh2_rsa_sha1_signv + return _libssh2_rsa_sha1_signv(session, signature, signature_len, + veccount, datavec, rsactx); +#else + int ret; + int i; + unsigned char hash[SHA_DIGEST_LENGTH]; + libssh2_sha1_ctx ctx; + + (void)libssh2_sha1_init(&ctx); + for(i = 0; i < veccount; i++) { + libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha1_final(ctx, hash); + + ret = _libssh2_rsa_sha1_sign(session, rsactx, hash, SHA_DIGEST_LENGTH, + signature, signature_len); + if(ret) { + return -1; + } + + return 0; +#endif +} + +/* + * hostkey_method_ssh_rsa_sha2_256_sig_verify + * + * Verify signature created by remote + */ +#if LIBSSH2_RSA_SHA2 + +static int +hostkey_method_ssh_rsa_sha2_256_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + /* Skip past keyname_len(4) + keyname(12){"rsa-sha2-256"} + + signature_len(4) */ + if(sig_len < 20) + return -1; + + sig += 20; + sig_len -= 20; + return _libssh2_rsa_sha2_verify(rsactx, SHA256_DIGEST_LENGTH, sig, sig_len, + m, m_len); +} + +/* + * hostkey_method_ssh_rsa_sha2_256_signv + * + * Construct a signature from an array of vectors + */ + +static int +hostkey_method_ssh_rsa_sha2_256_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + +#ifdef _libssh2_rsa_sha2_256_signv + return _libssh2_rsa_sha2_256_signv(session, signature, signature_len, + veccount, datavec, rsactx); +#else + int ret; + int i; + unsigned char hash[SHA256_DIGEST_LENGTH]; + libssh2_sha256_ctx ctx; + + if(!libssh2_sha256_init(&ctx)) { + return -1; + } + for(i = 0; i < veccount; i++) { + libssh2_sha256_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha256_final(ctx, hash); + + ret = _libssh2_rsa_sha2_sign(session, rsactx, hash, SHA256_DIGEST_LENGTH, + signature, signature_len); + if(ret) { + return -1; + } + + return 0; +#endif +} + +/* + * hostkey_method_ssh_rsa_sha2_512_sig_verify + * + * Verify signature created by remote + */ + +static int +hostkey_method_ssh_rsa_sha2_512_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + /* Skip past keyname_len(4) + keyname(12){"rsa-sha2-512"} + + signature_len(4) */ + if(sig_len < 20) + return -1; + + sig += 20; + sig_len -= 20; + return _libssh2_rsa_sha2_verify(rsactx, SHA512_DIGEST_LENGTH, sig, + sig_len, m, m_len); +} + + +/* + * hostkey_method_ssh_rsa_sha2_512_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_rsa_sha2_512_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + +#ifdef _libssh2_rsa_sha2_512_signv + return _libssh2_rsa_sha2_512_signv(session, signature, signature_len, + veccount, datavec, rsactx); +#else + int ret; + int i; + unsigned char hash[SHA512_DIGEST_LENGTH]; + libssh2_sha512_ctx ctx; + + if(!libssh2_sha512_init(&ctx)) { + return -1; + } + for(i = 0; i < veccount; i++) { + libssh2_sha512_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha512_final(ctx, hash); + + ret = _libssh2_rsa_sha2_sign(session, rsactx, hash, SHA512_DIGEST_LENGTH, + signature, signature_len); + if(ret) { + return -1; + } + + return 0; +#endif +} + +#endif /* LIBSSH2_RSA_SHA2 */ + + +/* + * hostkey_method_ssh_rsa_dtor + * + * Shutdown the hostkey + */ +static int +hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + _libssh2_rsa_free(rsactx); + + *abstract = NULL; + + return 0; +} + +#ifdef OPENSSL_NO_MD5 +#define MD5_DIGEST_LENGTH 16 +#endif + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa = { + "ssh-rsa", + MD5_DIGEST_LENGTH, + hostkey_method_ssh_rsa_init, + hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_initPEMFromMemory, + hostkey_method_ssh_rsa_sig_verify, + hostkey_method_ssh_rsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_rsa_dtor, +}; + +#if LIBSSH2_RSA_SHA2 + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa_sha2_256 = { + "rsa-sha2-256", + SHA256_DIGEST_LENGTH, + hostkey_method_ssh_rsa_init, + hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_initPEMFromMemory, + hostkey_method_ssh_rsa_sha2_256_sig_verify, + hostkey_method_ssh_rsa_sha2_256_signv, + NULL, /* encrypt */ + hostkey_method_ssh_rsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa_sha2_512 = { + "rsa-sha2-512", + SHA512_DIGEST_LENGTH, + hostkey_method_ssh_rsa_init, + hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_initPEMFromMemory, + hostkey_method_ssh_rsa_sha2_512_sig_verify, + hostkey_method_ssh_rsa_sha2_512_signv, + NULL, /* encrypt */ + hostkey_method_ssh_rsa_dtor, +}; + +#endif /* LIBSSH2_RSA_SHA2 */ + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa_cert = { + "ssh-rsa-cert-v01@openssh.com", + MD5_DIGEST_LENGTH, + NULL, + hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_initPEMFromMemory, + NULL, + hostkey_method_ssh_rsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_rsa_dtor, +}; + +#endif /* LIBSSH2_RSA */ + +#if LIBSSH2_DSA +/* *********** + * ssh-dss * + *********** */ + +static int hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_dss_init + * + * Initialize the server hostkey working area with p/q/g/y set + */ +static int +hostkey_method_ssh_dss_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + unsigned char *p, *q, *g, *y; + size_t p_len, q_len, g_len, y_len; + struct string_buf buf; + + if(*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + if(hostkey_data_len < 27) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); + return -1; + } + + buf.data = (unsigned char *)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; + + if(_libssh2_match_string(&buf, "ssh-dss")) + return -1; + + if(_libssh2_get_string(&buf, &p, &p_len)) + return -1; + + if(_libssh2_get_string(&buf, &q, &q_len)) + return -1; + + if(_libssh2_get_string(&buf, &g, &g_len)) + return -1; + + if(_libssh2_get_string(&buf, &y, &y_len)) + return -1; + + if(!_libssh2_eob(&buf)) + return -1; + + if(_libssh2_dsa_new(&dsactx, p, p_len, q, q_len, + g, g_len, y, y_len, NULL, 0)) { + return -1; + } + + *abstract = dsactx; + + return 0; +} + +/* + * hostkey_method_ssh_dss_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + int ret; + + if(*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_dsa_new_private(&dsactx, session, privkeyfile, passphrase); + if(ret) { + return -1; + } + + *abstract = dsactx; + + return 0; +} + +/* + * hostkey_method_ssh_dss_initPEMFromMemory + * + * Load a Private Key from memory + */ +static int +hostkey_method_ssh_dss_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + int ret; + + if(*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_dsa_new_private_frommemory(&dsactx, session, + privkeyfiledata, + privkeyfiledata_len, passphrase); + if(ret) { + return -1; + } + + *abstract = dsactx; + + return 0; +} + +/* + * libssh2_hostkey_method_ssh_dss_sign + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_dss_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + + /* Skip past keyname_len(4) + keyname(7){"ssh-dss"} + signature_len(4) */ + if(sig_len != 55) { + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid DSS signature length"); + } + + sig += 15; + sig_len -= 15; + + return _libssh2_dsa_sha1_verify(dsactx, sig, m, m_len); +} + +/* + * hostkey_method_ssh_dss_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_dss_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + unsigned char hash[SHA_DIGEST_LENGTH]; + libssh2_sha1_ctx ctx; + int i; + + *signature = LIBSSH2_CALLOC(session, 2 * SHA_DIGEST_LENGTH); + if(!*signature) { + return -1; + } + + *signature_len = 2 * SHA_DIGEST_LENGTH; + + (void)libssh2_sha1_init(&ctx); + for(i = 0; i < veccount; i++) { + libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha1_final(ctx, hash); + + if(_libssh2_dsa_sha1_sign(dsactx, hash, SHA_DIGEST_LENGTH, *signature)) { + LIBSSH2_FREE(session, *signature); + return -1; + } + + return 0; +} + +/* + * libssh2_hostkey_method_ssh_dss_dtor + * + * Shutdown the hostkey method + */ +static int +hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + (void) session; + + _libssh2_dsa_free(dsactx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_dss = { + "ssh-dss", + MD5_DIGEST_LENGTH, + hostkey_method_ssh_dss_init, + hostkey_method_ssh_dss_initPEM, + hostkey_method_ssh_dss_initPEMFromMemory, + hostkey_method_ssh_dss_sig_verify, + hostkey_method_ssh_dss_signv, + NULL, /* encrypt */ + hostkey_method_ssh_dss_dtor, +}; +#endif /* LIBSSH2_DSA */ + +#if LIBSSH2_ECDSA + +/* *********** + * ecdsa-sha2-nistp256/384/521 * + *********** */ + +static int +hostkey_method_ssh_ecdsa_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_ecdsa_init + * + * Initialize the server hostkey working area with e/n pair + */ +static int +hostkey_method_ssh_ecdsa_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_ecdsa_ctx *ecdsactx = NULL; + unsigned char *type_str, *domain, *public_key; + size_t key_len, len; + libssh2_curve_type type; + struct string_buf buf; + + if(abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + if(hostkey_data_len < 39) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); + return -1; + } + + buf.data = (unsigned char *)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; + + if(_libssh2_get_string(&buf, &type_str, &len) || len != 19) + return -1; + + if(strncmp((char *) type_str, "ecdsa-sha2-nistp256", 19) == 0) { + type = LIBSSH2_EC_CURVE_NISTP256; + } + else if(strncmp((char *) type_str, "ecdsa-sha2-nistp384", 19) == 0) { + type = LIBSSH2_EC_CURVE_NISTP384; + } + else if(strncmp((char *) type_str, "ecdsa-sha2-nistp521", 19) == 0) { + type = LIBSSH2_EC_CURVE_NISTP521; + } + else { + return -1; + } + + if(_libssh2_get_string(&buf, &domain, &len) || len != 8) + return -1; + + if(type == LIBSSH2_EC_CURVE_NISTP256 && + strncmp((char *)domain, "nistp256", 8) != 0) { + return -1; + } + else if(type == LIBSSH2_EC_CURVE_NISTP384 && + strncmp((char *)domain, "nistp384", 8) != 0) { + return -1; + } + else if(type == LIBSSH2_EC_CURVE_NISTP521 && + strncmp((char *)domain, "nistp521", 8) != 0) { + return -1; + } + + /* public key */ + if(_libssh2_get_string(&buf, &public_key, &key_len)) + return -1; + + if(!_libssh2_eob(&buf)) + return -1; + + if(_libssh2_ecdsa_curve_name_with_octal_new(&ecdsactx, public_key, + key_len, type)) + return -1; + + if(abstract != NULL) + *abstract = ecdsactx; + + return 0; +} + +/* + * hostkey_method_ssh_ecdsa_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_ecdsa_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = NULL; + int ret; + + if(abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ecdsa_new_private(&ec_ctx, session, + privkeyfile, passphrase); + + if(abstract != NULL) + *abstract = ec_ctx; + + return ret; +} + +/* + * hostkey_method_ssh_ecdsa_initPEMFromMemory + * + * Load a Private Key from memory + */ +static int +hostkey_method_ssh_ecdsa_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = NULL; + int ret; + + if(abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ecdsa_new_private_frommemory(&ec_ctx, session, + privkeyfiledata, + privkeyfiledata_len, + passphrase); + if(ret) { + return -1; + } + + if(abstract != NULL) + *abstract = ec_ctx; + + return 0; +} + +/* + * hostkey_method_ecdsa_sig_verify + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_ecdsa_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + unsigned char *r, *s, *name; + size_t r_len, s_len, name_len; + uint32_t len; + struct string_buf buf; + libssh2_ecdsa_ctx *ctx = (libssh2_ecdsa_ctx *) (*abstract); + + (void) session; + + if(sig_len < 35) + return -1; + + /* keyname_len(4) + keyname(19){"ecdsa-sha2-nistp256"} + + signature_len(4) */ + buf.data = (unsigned char *)sig; + buf.dataptr = buf.data; + buf.len = sig_len; + + if(_libssh2_get_string(&buf, &name, &name_len) || name_len != 19) + return -1; + + if(_libssh2_get_u32(&buf, &len) != 0 || len < 8) + return -1; + + if(_libssh2_get_string(&buf, &r, &r_len)) + return -1; + + if(_libssh2_get_string(&buf, &s, &s_len)) + return -1; + + return _libssh2_ecdsa_verify(ctx, r, r_len, s, s_len, m, m_len); +} + + +#define LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(digest_type) \ + { \ + unsigned char hash[SHA##digest_type##_DIGEST_LENGTH]; \ + libssh2_sha##digest_type##_ctx ctx; \ + int i; \ + (void)libssh2_sha##digest_type##_init(&ctx); \ + for(i = 0; i < veccount; i++) { \ + libssh2_sha##digest_type##_update(ctx, datavec[i].iov_base, \ + datavec[i].iov_len); \ + } \ + libssh2_sha##digest_type##_final(ctx, hash); \ + ret = _libssh2_ecdsa_sign(session, ec_ctx, hash, \ + SHA##digest_type##_DIGEST_LENGTH, \ + signature, signature_len); \ + } + + +/* + * hostkey_method_ecdsa_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_ecdsa_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = (libssh2_ecdsa_ctx *) (*abstract); + libssh2_curve_type type = _libssh2_ecdsa_get_curve_type(ec_ctx); + int ret = 0; + + if(type == LIBSSH2_EC_CURVE_NISTP256) { + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(256); + } + else if(type == LIBSSH2_EC_CURVE_NISTP384) { + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(384); + } + else if(type == LIBSSH2_EC_CURVE_NISTP521) { + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(512); + } + else { + return -1; + } + + return ret; +} + +/* + * hostkey_method_ssh_ecdsa_dtor + * + * Shutdown the hostkey by freeing EC_KEY context + */ +static int +hostkey_method_ssh_ecdsa_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_ecdsa_ctx *keyctx = (libssh2_ecdsa_ctx *) (*abstract); + (void) session; + + if(keyctx != NULL) + _libssh2_ecdsa_free(keyctx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp256 = { + "ecdsa-sha2-nistp256", + SHA256_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp384 = { + "ecdsa-sha2-nistp384", + SHA384_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp521 = { + "ecdsa-sha2-nistp521", + SHA512_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp256_cert = { + "ecdsa-sha2-nistp256-cert-v01@openssh.com", + SHA256_DIGEST_LENGTH, + NULL, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + NULL, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp384_cert = { + "ecdsa-sha2-nistp384-cert-v01@openssh.com", + SHA384_DIGEST_LENGTH, + NULL, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + NULL, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp521_cert = { + "ecdsa-sha2-nistp521-cert-v01@openssh.com", + SHA512_DIGEST_LENGTH, + NULL, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + NULL, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +#endif /* LIBSSH2_ECDSA */ + +#if LIBSSH2_ED25519 + +/* *********** + * ed25519 * + *********** */ + +static int hostkey_method_ssh_ed25519_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_ed25519_init + * + * Initialize the server hostkey working area with e/n pair + */ +static int +hostkey_method_ssh_ed25519_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + size_t key_len; + unsigned char *key; + libssh2_ed25519_ctx *ctx = NULL; + struct string_buf buf; + + if(*abstract) { + hostkey_method_ssh_ed25519_dtor(session, abstract); + *abstract = NULL; + } + + if(hostkey_data_len < 19) { + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, + "host key length too short"); + return -1; + } + + buf.data = (unsigned char *)hostkey_data; + buf.dataptr = buf.data; + buf.len = hostkey_data_len; + + if(_libssh2_match_string(&buf, "ssh-ed25519")) + return -1; + + /* public key */ + if(_libssh2_get_string(&buf, &key, &key_len)) + return -1; + + if(!_libssh2_eob(&buf)) + return -1; + + if(_libssh2_ed25519_new_public(&ctx, session, key, key_len) != 0) { + return -1; + } + + *abstract = ctx; + + return 0; +} + +/* + * hostkey_method_ssh_ed25519_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_ed25519_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ed25519_ctx *ec_ctx = NULL; + int ret; + + if(*abstract) { + hostkey_method_ssh_ed25519_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ed25519_new_private(&ec_ctx, session, + privkeyfile, passphrase); + if(ret) { + return -1; + } + + *abstract = ec_ctx; + + return ret; +} + +/* + * hostkey_method_ssh_ed25519_initPEMFromMemory + * + * Load a Private Key from memory + */ +static int +hostkey_method_ssh_ed25519_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ed25519_ctx *ed_ctx = NULL; + int ret; + + if(abstract != NULL && *abstract) { + hostkey_method_ssh_ed25519_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ed25519_new_private_frommemory(&ed_ctx, session, + privkeyfiledata, + privkeyfiledata_len, + passphrase); + if(ret) { + return -1; + } + + if(abstract != NULL) + *abstract = ed_ctx; + + return 0; +} + +/* + * hostkey_method_ssh_ed25519_sig_verify + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_ed25519_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_ed25519_ctx *ctx = (libssh2_ed25519_ctx *) (*abstract); + (void) session; + + if(sig_len < 19) + return -1; + + /* Skip past keyname_len(4) + keyname(11){"ssh-ed25519"} + + signature_len(4) */ + sig += 19; + sig_len -= 19; + + if(sig_len != LIBSSH2_ED25519_SIG_LEN) + return -1; + + return _libssh2_ed25519_verify(ctx, sig, sig_len, m, m_len); +} + +/* + * hostkey_method_ssh_ed25519_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_ed25519_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_ed25519_ctx *ctx = (libssh2_ed25519_ctx *) (*abstract); + + if(veccount != 1) { + return -1; + } + + return _libssh2_ed25519_sign(ctx, session, signature, signature_len, + datavec[0].iov_base, datavec[0].iov_len); +} + + +/* + * hostkey_method_ssh_ed25519_dtor + * + * Shutdown the hostkey by freeing key context + */ +static int +hostkey_method_ssh_ed25519_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_ed25519_ctx *keyctx = (libssh2_ed25519_ctx*) (*abstract); + (void) session; + + if(keyctx) + _libssh2_ed25519_free(keyctx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_ed25519 = { + "ssh-ed25519", + SHA256_DIGEST_LENGTH, + hostkey_method_ssh_ed25519_init, + hostkey_method_ssh_ed25519_initPEM, + hostkey_method_ssh_ed25519_initPEMFromMemory, + hostkey_method_ssh_ed25519_sig_verify, + hostkey_method_ssh_ed25519_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ed25519_dtor, +}; + +#endif /*LIBSSH2_ED25519*/ + + +static const LIBSSH2_HOSTKEY_METHOD *hostkey_methods[] = { +#if LIBSSH2_ECDSA + &hostkey_method_ecdsa_ssh_nistp256, + &hostkey_method_ecdsa_ssh_nistp384, + &hostkey_method_ecdsa_ssh_nistp521, + &hostkey_method_ecdsa_ssh_nistp256_cert, + &hostkey_method_ecdsa_ssh_nistp384_cert, + &hostkey_method_ecdsa_ssh_nistp521_cert, +#endif +#if LIBSSH2_ED25519 + &hostkey_method_ssh_ed25519, +#endif +#if LIBSSH2_RSA +#if LIBSSH2_RSA_SHA2 + &hostkey_method_ssh_rsa_sha2_512, + &hostkey_method_ssh_rsa_sha2_256, +#endif /* LIBSSH2_RSA_SHA2 */ + &hostkey_method_ssh_rsa, + &hostkey_method_ssh_rsa_cert, +#endif /* LIBSSH2_RSA */ +#if LIBSSH2_DSA + &hostkey_method_ssh_dss, +#endif /* LIBSSH2_DSA */ + NULL +}; + +const LIBSSH2_HOSTKEY_METHOD ** +libssh2_hostkey_methods(void) +{ + return hostkey_methods; +} + +/* + * libssh2_hostkey_hash + * + * Returns hash signature + * Returned buffer should NOT be freed + * Length of buffer is determined by hash type + * i.e. MD5 == 16, SHA1 == 20, SHA256 == 32 + */ +LIBSSH2_API const char * +libssh2_hostkey_hash(LIBSSH2_SESSION * session, int hash_type) +{ + switch(hash_type) { +#if LIBSSH2_MD5 + case LIBSSH2_HOSTKEY_HASH_MD5: + return (session->server_hostkey_md5_valid) + ? (char *) session->server_hostkey_md5 + : NULL; + break; +#endif /* LIBSSH2_MD5 */ + case LIBSSH2_HOSTKEY_HASH_SHA1: + return (session->server_hostkey_sha1_valid) + ? (char *) session->server_hostkey_sha1 + : NULL; + break; + case LIBSSH2_HOSTKEY_HASH_SHA256: + return (session->server_hostkey_sha256_valid) + ? (char *) session->server_hostkey_sha256 + : NULL; + break; + default: + return NULL; + } +} + +static int hostkey_type(const unsigned char *hostkey, size_t len) +{ + static const unsigned char rsa[] = { + 0, 0, 0, 0x07, 's', 's', 'h', '-', 'r', 's', 'a' + }; + static const unsigned char dss[] = { + 0, 0, 0, 0x07, 's', 's', 'h', '-', 'd', 's', 's' + }; + static const unsigned char ecdsa_256[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', + 'n', 'i', 's', 't', 'p', '2', '5', '6' + }; + static const unsigned char ecdsa_384[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', + 'n', 'i', 's', 't', 'p', '3', '8', '4' + }; + static const unsigned char ecdsa_521[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', + 'n', 'i', 's', 't', 'p', '5', '2', '1' + }; + static const unsigned char ed25519[] = { + 0, 0, 0, 0x0b, 's', 's', 'h', '-', 'e', 'd', '2', '5', '5', '1', '9' + }; + + if(len < 11) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if(!memcmp(rsa, hostkey, 11)) + return LIBSSH2_HOSTKEY_TYPE_RSA; + + if(!memcmp(dss, hostkey, 11)) + return LIBSSH2_HOSTKEY_TYPE_DSS; + + if(len < 15) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if(!memcmp(ed25519, hostkey, 15)) + return LIBSSH2_HOSTKEY_TYPE_ED25519; + + if(len < 23) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if(!memcmp(ecdsa_256, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA_256; + + if(!memcmp(ecdsa_384, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA_384; + + if(!memcmp(ecdsa_521, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA_521; + + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; +} + +/* + * libssh2_session_hostkey() + * + * Returns the server key and length. + * + */ +LIBSSH2_API const char * +libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len, int *type) +{ + if(session->server_hostkey_len) { + if(len) + *len = session->server_hostkey_len; + if(type) + *type = hostkey_type(session->server_hostkey, + session->server_hostkey_len); + return (char *) session->server_hostkey; + } + if(len) + *len = 0; + return NULL; +} +#endif diff --git a/lib/libssh2/keepalive.c b/lib/libssh2/keepalive.c new file mode 100644 index 0000000..dd733f7 --- /dev/null +++ b/lib/libssh2/keepalive.c @@ -0,0 +1,102 @@ +#if defined(ESP32) +/* Copyright (C) 2010 Simon Josefsson + * Author: Simon Josefsson + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" +#include "transport.h" /* _libssh2_transport_write */ + +/* Keep-alive stuff. */ + +LIBSSH2_API void +libssh2_keepalive_config (LIBSSH2_SESSION *session, + int want_reply, + unsigned interval) +{ + if(interval == 1) + session->keepalive_interval = 2; + else + session->keepalive_interval = interval; + session->keepalive_want_reply = want_reply ? 1 : 0; +} + +LIBSSH2_API int +libssh2_keepalive_send (LIBSSH2_SESSION *session, + int *seconds_to_next) +{ + time_t now; + + if(!session->keepalive_interval) { + if(seconds_to_next) + *seconds_to_next = 0; + return 0; + } + + now = time(NULL); + + if(session->keepalive_last_sent + session->keepalive_interval <= now) { + /* Format is + "SSH_MSG_GLOBAL_REQUEST || 4-byte len || str || want-reply". */ + unsigned char keepalive_data[] + = "\x50\x00\x00\x00\x15keepalive@libssh2.orgW"; + size_t len = sizeof(keepalive_data) - 1; + int rc; + + keepalive_data[len - 1] = + (unsigned char)session->keepalive_want_reply; + + rc = _libssh2_transport_send(session, keepalive_data, len, NULL, 0); + /* Silently ignore PACKET_EAGAIN here: if the write buffer is + already full, sending another keepalive is not useful. */ + if(rc && rc != LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send keepalive message"); + return rc; + } + + session->keepalive_last_sent = now; + if(seconds_to_next) + *seconds_to_next = session->keepalive_interval; + } + else if(seconds_to_next) { + *seconds_to_next = (int) (session->keepalive_last_sent - now) + + session->keepalive_interval; + } + + return 0; +} +#endif diff --git a/lib/libssh2/kex.c b/lib/libssh2/kex.c new file mode 100644 index 0000000..2a5881f --- /dev/null +++ b/lib/libssh2/kex.c @@ -0,0 +1,4165 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2010-2019, Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#include "transport.h" +#include "comp.h" +#include "mac.h" + +#include + +/* define SHA1_DIGEST_LENGTH for the macro below */ +#ifndef SHA1_DIGEST_LENGTH +#define SHA1_DIGEST_LENGTH SHA_DIGEST_LENGTH +#endif + +/* TODO: Switch this to an inline and handle alloc() failures */ +/* Helper macro called from + kex_method_diffie_hellman_group1_sha1_key_exchange */ + +#define LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(value, reqlen, version) \ + { \ + if(type == LIBSSH2_EC_CURVE_NISTP256) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, value, reqlen, version); \ + } \ + else if(type == LIBSSH2_EC_CURVE_NISTP384) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(384, value, reqlen, version); \ + } \ + else if(type == LIBSSH2_EC_CURVE_NISTP521) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(512, value, reqlen, version); \ + } \ + } \ + + +#define LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(digest_type, value, \ + reqlen, version) \ +{ \ + libssh2_sha##digest_type##_ctx hash; \ + unsigned long len = 0; \ + if(!(value)) { \ + value = LIBSSH2_ALLOC(session, \ + reqlen + SHA##digest_type##_DIGEST_LENGTH); \ + } \ + if(value) \ + while(len < (unsigned long)reqlen) { \ + (void)libssh2_sha##digest_type##_init(&hash); \ + libssh2_sha##digest_type##_update(hash, \ + exchange_state->k_value, \ + exchange_state->k_value_len); \ + libssh2_sha##digest_type##_update(hash, \ + exchange_state->h_sig_comp, \ + SHA##digest_type##_DIGEST_LENGTH); \ + if(len > 0) { \ + libssh2_sha##digest_type##_update(hash, value, len); \ + } \ + else { \ + libssh2_sha##digest_type##_update(hash, (version), 1); \ + libssh2_sha##digest_type##_update(hash, session->session_id,\ + session->session_id_len); \ + } \ + libssh2_sha##digest_type##_final(hash, (value) + len); \ + len += SHA##digest_type##_DIGEST_LENGTH; \ + } \ +} + +/*! + * @note The following are wrapper functions used by diffie_hellman_sha_algo(). + * TODO: Switch backend SHA macros to functions to allow function pointers + * @discussion Ideally these would be function pointers but the backend macros + * don't allow it so we have to wrap them up in helper functions + */ + +static void _libssh2_sha_algo_ctx_init(int sha_algo, void *ctx) +{ + if(sha_algo == 512) { + (void)libssh2_sha512_init((libssh2_sha512_ctx*)ctx); + } + else if(sha_algo == 384) { + (void)libssh2_sha384_init((libssh2_sha384_ctx*)ctx); + } + else if(sha_algo == 256) { + (void)libssh2_sha256_init((libssh2_sha256_ctx*)ctx); + } + else if(sha_algo == 1) { + (void)libssh2_sha1_init((libssh2_sha1_ctx*)ctx); + } + else { + assert(0); + } +} + +static void _libssh2_sha_algo_ctx_update(int sha_algo, void *ctx, + void *data, size_t len) +{ + if(sha_algo == 512) { + libssh2_sha512_ctx *_ctx = (libssh2_sha512_ctx*)ctx; + libssh2_sha512_update(*_ctx, data, len); + } + else if(sha_algo == 384) { + libssh2_sha384_ctx *_ctx = (libssh2_sha384_ctx*)ctx; + libssh2_sha384_update(*_ctx, data, len); + } + else if(sha_algo == 256) { + libssh2_sha256_ctx *_ctx = (libssh2_sha256_ctx*)ctx; + libssh2_sha256_update(*_ctx, data, len); + } + else if(sha_algo == 1) { + libssh2_sha1_ctx *_ctx = (libssh2_sha1_ctx*)ctx; + libssh2_sha1_update(*_ctx, data, len); + } + else { +#ifdef LIBSSH2DEBUG + assert(0); +#endif + } +} + +static void _libssh2_sha_algo_ctx_final(int sha_algo, void *ctx, + void *hash) +{ + if(sha_algo == 512) { + libssh2_sha512_ctx *_ctx = (libssh2_sha512_ctx*)ctx; + libssh2_sha512_final(*_ctx, hash); + } + else if(sha_algo == 384) { + libssh2_sha384_ctx *_ctx = (libssh2_sha384_ctx*)ctx; + libssh2_sha384_final(*_ctx, hash); + } + else if(sha_algo == 256) { + libssh2_sha256_ctx *_ctx = (libssh2_sha256_ctx*)ctx; + libssh2_sha256_final(*_ctx, hash); + } + else if(sha_algo == 1) { + libssh2_sha1_ctx *_ctx = (libssh2_sha1_ctx*)ctx; + libssh2_sha1_final(*_ctx, hash); + } + else { +#ifdef LIBSSH2DEBUG + assert(0); +#endif + } +} + +static void _libssh2_sha_algo_value_hash(int sha_algo, + LIBSSH2_SESSION *session, + kmdhgGPshakex_state_t *exchange_state, + unsigned char **data, size_t data_len, + const unsigned char *version) +{ + if(sha_algo == 512) { + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(512, *data, data_len, version); + } + else if(sha_algo == 384) { + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(384, *data, data_len, version); + } + else if(sha_algo == 256) { + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, *data, data_len, version); + } + else if(sha_algo == 1) { + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(1, *data, data_len, version); + } + else { +#ifdef LIBSSH2DEBUG + assert(0); +#endif + } +} + + +/*! + * @function diffie_hellman_sha_algo + * @abstract Diffie Hellman Key Exchange, Group Agnostic, + * SHA Algorithm Agnostic + * @result 0 on success, error code on failure + */ +static int diffie_hellman_sha_algo(LIBSSH2_SESSION *session, + _libssh2_bn *g, + _libssh2_bn *p, + int group_order, + int sha_algo_value, + void *exchange_hash_ctx, + unsigned char packet_type_init, + unsigned char packet_type_reply, + unsigned char *midhash, + unsigned long midhash_len, + kmdhgGPshakex_state_t *exchange_state) +{ + int ret = 0; + int rc; + + int digest_len = 0; + + if(sha_algo_value == 512) + digest_len = SHA512_DIGEST_LENGTH; + else if(sha_algo_value == 384) + digest_len = SHA384_DIGEST_LENGTH; + else if(sha_algo_value == 256) + digest_len = SHA256_DIGEST_LENGTH; + else if(sha_algo_value == 1) + digest_len = SHA1_DIGEST_LENGTH; + else { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "sha algo value is unimplemented"); + goto clean_exit; + } + + if(exchange_state->state == libssh2_NB_state_idle) { + /* Setup initial values */ + exchange_state->e_packet = NULL; + exchange_state->s_packet = NULL; + exchange_state->k_value = NULL; + exchange_state->ctx = _libssh2_bn_ctx_new(); + libssh2_dh_init(&exchange_state->x); + exchange_state->e = _libssh2_bn_init(); /* g^x mod p */ + exchange_state->f = _libssh2_bn_init_from_bin(); /* g^(Random from + server) mod p */ + exchange_state->k = _libssh2_bn_init(); /* The shared secret: f^x mod + p */ + + /* Zero the whole thing out */ + memset(&exchange_state->req_state, 0, sizeof(packet_require_state_t)); + + /* Generate x and e */ + if(_libssh2_bn_bits(p) > LIBSSH2_DH_MAX_MODULUS_BITS) { + ret = _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "dh modulus value is too large"); + goto clean_exit; + } + + rc = libssh2_dh_key_pair(&exchange_state->x, exchange_state->e, g, p, + group_order, exchange_state->ctx); + if(rc) + goto clean_exit; + + /* Send KEX init */ + /* packet_type(1) + String Length(4) + leading 0(1) */ + exchange_state->e_packet_len = + _libssh2_bn_bytes(exchange_state->e) + 6; + if(_libssh2_bn_bits(exchange_state->e) % 8) { + /* Leading 00 not needed */ + exchange_state->e_packet_len--; + } + + exchange_state->e_packet = + LIBSSH2_ALLOC(session, exchange_state->e_packet_len); + if(!exchange_state->e_packet) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory error"); + goto clean_exit; + } + exchange_state->e_packet[0] = packet_type_init; + _libssh2_htonu32(exchange_state->e_packet + 1, + exchange_state->e_packet_len - 5); + if(_libssh2_bn_bits(exchange_state->e) % 8) { + _libssh2_bn_to_bin(exchange_state->e, + exchange_state->e_packet + 5); + } + else { + exchange_state->e_packet[5] = 0; + _libssh2_bn_to_bin(exchange_state->e, + exchange_state->e_packet + 6); + } + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending KEX packet %d", + (int) packet_type_init); + exchange_state->state = libssh2_NB_state_created; + } + + if(exchange_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, exchange_state->e_packet, + exchange_state->e_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send KEX init message"); + goto clean_exit; + } + exchange_state->state = libssh2_NB_state_sent; + } + + if(exchange_state->state == libssh2_NB_state_sent) { + if(session->burn_optimistic_kexinit) { + /* The first KEX packet to come along will be the guess initially + * sent by the server. That guess turned out to be wrong so we + * need to silently ignore it */ + int burn_type; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Waiting for badly guessed KEX packet " + "(to be ignored)"); + burn_type = + _libssh2_packet_burn(session, &exchange_state->burn_state); + if(burn_type == LIBSSH2_ERROR_EAGAIN) { + return burn_type; + } + else if(burn_type <= 0) { + /* Failed to receive a packet */ + ret = burn_type; + goto clean_exit; + } + session->burn_optimistic_kexinit = 0; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Burnt packet of type: %02x", + (unsigned int) burn_type); + } + + exchange_state->state = libssh2_NB_state_sent1; + } + + if(exchange_state->state == libssh2_NB_state_sent1) { + /* Wait for KEX reply */ + struct string_buf buf; + size_t host_key_len; + + rc = _libssh2_packet_require(session, packet_type_reply, + &exchange_state->s_packet, + &exchange_state->s_packet_len, 0, NULL, + 0, &exchange_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + if(rc) { + ret = _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "Timed out waiting for KEX reply"); + goto clean_exit; + } + + /* Parse KEXDH_REPLY */ + if(exchange_state->s_packet_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + goto clean_exit; + } + + buf.data = exchange_state->s_packet; + buf.len = exchange_state->s_packet_len; + buf.dataptr = buf.data; + buf.dataptr++; /* advance past type */ + + if(session->server_hostkey) + LIBSSH2_FREE(session, session->server_hostkey); + + if(_libssh2_copy_string(session, &buf, &(session->server_hostkey), + &host_key_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Could not copy host key"); + goto clean_exit; + } + + session->server_hostkey_len = (uint32_t)host_key_len; + +#if LIBSSH2_MD5 + { + libssh2_md5_ctx fingerprint_ctx; + + if(libssh2_md5_init(&fingerprint_ctx)) { + libssh2_md5_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_md5_final(fingerprint_ctx, + session->server_hostkey_md5); + session->server_hostkey_md5_valid = TRUE; + } + else { + session->server_hostkey_md5_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[50], *fprint = fingerprint; + int i; + for(i = 0; i < 16; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's MD5 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ +#endif /* ! LIBSSH2_MD5 */ + + { + libssh2_sha1_ctx fingerprint_ctx; + + if(libssh2_sha1_init(&fingerprint_ctx)) { + libssh2_sha1_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha1_final(fingerprint_ctx, + session->server_hostkey_sha1); + session->server_hostkey_sha1_valid = TRUE; + } + else { + session->server_hostkey_sha1_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[64], *fprint = fingerprint; + int i; + + for(i = 0; i < 20; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA1 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ + + { + libssh2_sha256_ctx fingerprint_ctx; + + if(libssh2_sha256_init(&fingerprint_ctx)) { + libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha256_final(fingerprint_ctx, + session->server_hostkey_sha256); + session->server_hostkey_sha256_valid = TRUE; + } + else { + session->server_hostkey_sha256_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char *base64Fingerprint = NULL; + _libssh2_base64_encode(session, + (const char *) + session->server_hostkey_sha256, + SHA256_DIGEST_LENGTH, &base64Fingerprint); + if(base64Fingerprint != NULL) { + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA256 Fingerprint: %s", + base64Fingerprint); + LIBSSH2_FREE(session, base64Fingerprint); + } + } +#endif /* LIBSSH2DEBUG */ + + + if(session->hostkey->init(session, session->server_hostkey, + session->server_hostkey_len, + &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to initialize hostkey importer"); + goto clean_exit; + } + + if(_libssh2_get_string(&buf, &(exchange_state->f_value), + &(exchange_state->f_value_len))) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to get f value"); + goto clean_exit; + } + + _libssh2_bn_from_bin(exchange_state->f, exchange_state->f_value_len, + exchange_state->f_value); + + if(_libssh2_get_string(&buf, &(exchange_state->h_sig), + &(exchange_state->h_sig_len))) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to get h sig"); + goto clean_exit; + } + + /* Compute the shared secret */ + libssh2_dh_secret(&exchange_state->x, exchange_state->k, + exchange_state->f, p, exchange_state->ctx); + exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5; + if(_libssh2_bn_bits(exchange_state->k) % 8) { + /* don't need leading 00 */ + exchange_state->k_value_len--; + } + exchange_state->k_value = + LIBSSH2_ALLOC(session, exchange_state->k_value_len); + if(!exchange_state->k_value) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for K"); + goto clean_exit; + } + _libssh2_htonu32(exchange_state->k_value, + exchange_state->k_value_len - 4); + if(_libssh2_bn_bits(exchange_state->k) % 8) { + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4); + } + else { + exchange_state->k_value[4] = 0; + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5); + } + + exchange_state->exchange_hash = (void *)&exchange_hash_ctx; + _libssh2_sha_algo_ctx_init(sha_algo_value, exchange_hash_ctx); + + if(session->local.banner) { + _libssh2_htonu32(exchange_state->h_sig_comp, + strlen((char *) session->local.banner) - 2); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + session->local.banner, + strlen((char *) session->local.banner) - 2); + } + else { + _libssh2_htonu32(exchange_state->h_sig_comp, + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + (unsigned char *) + LIBSSH2_SSH_DEFAULT_BANNER, + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + } + + _libssh2_htonu32(exchange_state->h_sig_comp, + strlen((char *) session->remote.banner)); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + session->remote.banner, + strlen((char *) session->remote.banner)); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->local.kexinit_len); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + session->local.kexinit, + session->local.kexinit_len); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->remote.kexinit_len); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + session->remote.kexinit, + session->remote.kexinit_len); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->server_hostkey_len); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + session->server_hostkey, + session->server_hostkey_len); + + if(packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) { + /* diffie-hellman-group-exchange hashes additional fields */ +#ifdef LIBSSH2_DH_GEX_NEW + _libssh2_htonu32(exchange_state->h_sig_comp, + LIBSSH2_DH_GEX_MINGROUP); + _libssh2_htonu32(exchange_state->h_sig_comp + 4, + LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_htonu32(exchange_state->h_sig_comp + 8, + LIBSSH2_DH_GEX_MAXGROUP); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 12); +#else + _libssh2_htonu32(exchange_state->h_sig_comp, + LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); +#endif + } + + if(midhash) { + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + midhash, midhash_len); + } + + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->e_packet + 1, + exchange_state->e_packet_len - 1); + + _libssh2_htonu32(exchange_state->h_sig_comp, + exchange_state->f_value_len); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp, 4); + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->f_value, + exchange_state->f_value_len); + + _libssh2_sha_algo_ctx_update(sha_algo_value, exchange_hash_ctx, + exchange_state->k_value, + exchange_state->k_value_len); + + _libssh2_sha_algo_ctx_final(sha_algo_value, exchange_hash_ctx, + exchange_state->h_sig_comp); + + if(session->hostkey-> + sig_verify(session, exchange_state->h_sig, + exchange_state->h_sig_len, exchange_state->h_sig_comp, + digest_len, &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, + "Unable to verify hostkey signature"); + goto clean_exit; + } + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending NEWKEYS message"); + exchange_state->c = SSH_MSG_NEWKEYS; + + exchange_state->state = libssh2_NB_state_sent2; + } + + if(exchange_state->state == libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send NEWKEYS message"); + goto clean_exit; + } + + exchange_state->state = libssh2_NB_state_sent3; + } + + if(exchange_state->state == libssh2_NB_state_sent3) { + rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS, + &exchange_state->tmp, + &exchange_state->tmp_len, 0, NULL, 0, + &exchange_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS"); + goto clean_exit; + } + /* The first key exchange has been performed, + switch to active crypt/comp/mac mode */ + session->state |= LIBSSH2_STATE_NEWKEYS; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message"); + + /* This will actually end up being just packet_type(1) + for this packet type anyway */ + LIBSSH2_FREE(session, exchange_state->tmp); + + if(!session->session_id) { + session->session_id = LIBSSH2_ALLOC(session, digest_len); + if(!session->session_id) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for " + "SHA digest"); + goto clean_exit; + } + memcpy(session->session_id, exchange_state->h_sig_comp, + digest_len); + session->session_id_len = digest_len; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "session_id calculated"); + } + + /* Cleanup any existing cipher */ + if(session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + + /* Calculate IV/Secret/Key for each direction */ + if(session->local.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &iv, + session->local.crypt->iv_len, + (const unsigned char *)"A"); + + if(!iv) { + ret = -1; + goto clean_exit; + } + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &secret, + session->local.crypt->secret_len, + (const unsigned char *)"C"); + + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->local.crypt-> + init(session, session->local.crypt, iv, &free_iv, secret, + &free_secret, 1, &session->local.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server IV and Key calculated"); + + if(session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + + if(session->remote.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &iv, + session->remote.crypt->iv_len, + (const unsigned char *)"B"); + if(!iv) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &secret, + session->remote.crypt->secret_len, + (const unsigned char *)"D"); + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->remote.crypt-> + init(session, session->remote.crypt, iv, &free_iv, secret, + &free_secret, 0, &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client IV and Key calculated"); + + if(session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if(session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &key, + session->local.mac->key_len, + (const unsigned char *)"E"); + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->local.mac->init(session, key, &free_key, + &session->local.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->local.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server HMAC Key calculated"); + + if(session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if(session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + _libssh2_sha_algo_value_hash(sha_algo_value, session, + exchange_state, &key, + session->remote.mac->key_len, + (const unsigned char *)"F"); + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->remote.mac->init(session, key, &free_key, + &session->remote.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->remote.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client HMAC Key calculated"); + + /* Initialize compression for each direction */ + + /* Cleanup any existing compression */ + if(session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + + if(session->local.comp && session->local.comp->init) { + if(session->local.comp->init(session, 1, + &session->local.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server compression initialized"); + + if(session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + + if(session->remote.comp && session->remote.comp->init) { + if(session->remote.comp->init(session, 0, + &session->remote.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client compression initialized"); + + } + + clean_exit: + libssh2_dh_dtor(&exchange_state->x); + _libssh2_bn_free(exchange_state->e); + exchange_state->e = NULL; + _libssh2_bn_free(exchange_state->f); + exchange_state->f = NULL; + _libssh2_bn_free(exchange_state->k); + exchange_state->k = NULL; + _libssh2_bn_ctx_free(exchange_state->ctx); + exchange_state->ctx = NULL; + + if(exchange_state->e_packet) { + LIBSSH2_FREE(session, exchange_state->e_packet); + exchange_state->e_packet = NULL; + } + + if(exchange_state->s_packet) { + LIBSSH2_FREE(session, exchange_state->s_packet); + exchange_state->s_packet = NULL; + } + + if(exchange_state->k_value) { + LIBSSH2_FREE(session, exchange_state->k_value); + exchange_state->k_value = NULL; + } + + exchange_state->state = libssh2_NB_state_idle; + + return ret; +} + + + +/* kex_method_diffie_hellman_group1_sha1_key_exchange + * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1 + */ +static int +kex_method_diffie_hellman_group1_sha1_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) +{ + static const unsigned char p_value[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + int ret; + libssh2_sha1_ctx exchange_hash_ctx; + + if(key_state->state == libssh2_NB_state_idle) { + /* g == 2 */ + key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value + (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 128, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group1 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + + ret = diffie_hellman_sha_algo(session, key_state->g, key_state->p, 128, 1, + (void *)&exchange_hash_ctx, + SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, + NULL, 0, &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + key_state->state = libssh2_NB_state_idle; + + return ret; +} + + +/* kex_method_diffie_hellman_group14_key_exchange + * Diffie-Hellman Group14 Key Exchange with hash function callback + */ +typedef int (*diffie_hellman_hash_func_t)(LIBSSH2_SESSION *, + _libssh2_bn *, + _libssh2_bn *, + int, + int, + void *, + unsigned char, + unsigned char, + unsigned char *, + unsigned long, + kmdhgGPshakex_state_t *); +static int +kex_method_diffie_hellman_group14_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state, + int sha_algo_value, + void *exchange_hash_ctx, + diffie_hellman_hash_func_t + hashfunc) +{ + static const unsigned char p_value[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + int ret; + + if(key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value + (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* g == 2 */ + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 256, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group14 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + ret = hashfunc(session, key_state->g, key_state->p, + 256, sha_algo_value, exchange_hash_ctx, SSH_MSG_KEXDH_INIT, + SSH_MSG_KEXDH_REPLY, NULL, 0, &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + + return ret; +} + + + +/* kex_method_diffie_hellman_group14_sha1_key_exchange + * Diffie-Hellman Group14 Key Exchange using SHA1 + */ +static int +kex_method_diffie_hellman_group14_sha1_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) +{ + libssh2_sha1_ctx ctx; + return kex_method_diffie_hellman_group14_key_exchange(session, + key_state, 1, + &ctx, + diffie_hellman_sha_algo); +} + + + +/* kex_method_diffie_hellman_group14_sha256_key_exchange + * Diffie-Hellman Group14 Key Exchange using SHA256 + */ +static int +kex_method_diffie_hellman_group14_sha256_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) +{ + libssh2_sha256_ctx ctx; + return kex_method_diffie_hellman_group14_key_exchange(session, + key_state, 256, + &ctx, + diffie_hellman_sha_algo); +} + +/* kex_method_diffie_hellman_group16_sha512_key_exchange +* Diffie-Hellman Group16 Key Exchange using SHA512 +*/ +static int +kex_method_diffie_hellman_group16_sha512_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) + +{ + static const unsigned char p_value[512] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + int ret; + libssh2_sha512_ctx exchange_hash_ctx; + + if(key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value + (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* g == 2 */ + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 512, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group16 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + + ret = diffie_hellman_sha_algo(session, key_state->g, key_state->p, 512, + 512, (void *)&exchange_hash_ctx, + SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, + NULL, 0, &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + + return ret; +} + +/* kex_method_diffie_hellman_group16_sha512_key_exchange +* Diffie-Hellman Group18 Key Exchange using SHA512 +*/ +static int +kex_method_diffie_hellman_group18_sha512_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) + +{ + static const unsigned char p_value[1024] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x02, 0x84, 0x92, + 0x36, 0xC3, 0xFA, 0xB4, 0xD2, 0x7C, 0x70, 0x26, 0xC1, 0xD4, 0xDC, 0xB2, + 0x60, 0x26, 0x46, 0xDE, 0xC9, 0x75, 0x1E, 0x76, 0x3D, 0xBA, 0x37, 0xBD, + 0xF8, 0xFF, 0x94, 0x06, 0xAD, 0x9E, 0x53, 0x0E, 0xE5, 0xDB, 0x38, 0x2F, + 0x41, 0x30, 0x01, 0xAE, 0xB0, 0x6A, 0x53, 0xED, 0x90, 0x27, 0xD8, 0x31, + 0x17, 0x97, 0x27, 0xB0, 0x86, 0x5A, 0x89, 0x18, 0xDA, 0x3E, 0xDB, 0xEB, + 0xCF, 0x9B, 0x14, 0xED, 0x44, 0xCE, 0x6C, 0xBA, 0xCE, 0xD4, 0xBB, 0x1B, + 0xDB, 0x7F, 0x14, 0x47, 0xE6, 0xCC, 0x25, 0x4B, 0x33, 0x20, 0x51, 0x51, + 0x2B, 0xD7, 0xAF, 0x42, 0x6F, 0xB8, 0xF4, 0x01, 0x37, 0x8C, 0xD2, 0xBF, + 0x59, 0x83, 0xCA, 0x01, 0xC6, 0x4B, 0x92, 0xEC, 0xF0, 0x32, 0xEA, 0x15, + 0xD1, 0x72, 0x1D, 0x03, 0xF4, 0x82, 0xD7, 0xCE, 0x6E, 0x74, 0xFE, 0xF6, + 0xD5, 0x5E, 0x70, 0x2F, 0x46, 0x98, 0x0C, 0x82, 0xB5, 0xA8, 0x40, 0x31, + 0x90, 0x0B, 0x1C, 0x9E, 0x59, 0xE7, 0xC9, 0x7F, 0xBE, 0xC7, 0xE8, 0xF3, + 0x23, 0xA9, 0x7A, 0x7E, 0x36, 0xCC, 0x88, 0xBE, 0x0F, 0x1D, 0x45, 0xB7, + 0xFF, 0x58, 0x5A, 0xC5, 0x4B, 0xD4, 0x07, 0xB2, 0x2B, 0x41, 0x54, 0xAA, + 0xCC, 0x8F, 0x6D, 0x7E, 0xBF, 0x48, 0xE1, 0xD8, 0x14, 0xCC, 0x5E, 0xD2, + 0x0F, 0x80, 0x37, 0xE0, 0xA7, 0x97, 0x15, 0xEE, 0xF2, 0x9B, 0xE3, 0x28, + 0x06, 0xA1, 0xD5, 0x8B, 0xB7, 0xC5, 0xDA, 0x76, 0xF5, 0x50, 0xAA, 0x3D, + 0x8A, 0x1F, 0xBF, 0xF0, 0xEB, 0x19, 0xCC, 0xB1, 0xA3, 0x13, 0xD5, 0x5C, + 0xDA, 0x56, 0xC9, 0xEC, 0x2E, 0xF2, 0x96, 0x32, 0x38, 0x7F, 0xE8, 0xD7, + 0x6E, 0x3C, 0x04, 0x68, 0x04, 0x3E, 0x8F, 0x66, 0x3F, 0x48, 0x60, 0xEE, + 0x12, 0xBF, 0x2D, 0x5B, 0x0B, 0x74, 0x74, 0xD6, 0xE6, 0x94, 0xF9, 0x1E, + 0x6D, 0xBE, 0x11, 0x59, 0x74, 0xA3, 0x92, 0x6F, 0x12, 0xFE, 0xE5, 0xE4, + 0x38, 0x77, 0x7C, 0xB6, 0xA9, 0x32, 0xDF, 0x8C, 0xD8, 0xBE, 0xC4, 0xD0, + 0x73, 0xB9, 0x31, 0xBA, 0x3B, 0xC8, 0x32, 0xB6, 0x8D, 0x9D, 0xD3, 0x00, + 0x74, 0x1F, 0xA7, 0xBF, 0x8A, 0xFC, 0x47, 0xED, 0x25, 0x76, 0xF6, 0x93, + 0x6B, 0xA4, 0x24, 0x66, 0x3A, 0xAB, 0x63, 0x9C, 0x5A, 0xE4, 0xF5, 0x68, + 0x34, 0x23, 0xB4, 0x74, 0x2B, 0xF1, 0xC9, 0x78, 0x23, 0x8F, 0x16, 0xCB, + 0xE3, 0x9D, 0x65, 0x2D, 0xE3, 0xFD, 0xB8, 0xBE, 0xFC, 0x84, 0x8A, 0xD9, + 0x22, 0x22, 0x2E, 0x04, 0xA4, 0x03, 0x7C, 0x07, 0x13, 0xEB, 0x57, 0xA8, + 0x1A, 0x23, 0xF0, 0xC7, 0x34, 0x73, 0xFC, 0x64, 0x6C, 0xEA, 0x30, 0x6B, + 0x4B, 0xCB, 0xC8, 0x86, 0x2F, 0x83, 0x85, 0xDD, 0xFA, 0x9D, 0x4B, 0x7F, + 0xA2, 0xC0, 0x87, 0xE8, 0x79, 0x68, 0x33, 0x03, 0xED, 0x5B, 0xDD, 0x3A, + 0x06, 0x2B, 0x3C, 0xF5, 0xB3, 0xA2, 0x78, 0xA6, 0x6D, 0x2A, 0x13, 0xF8, + 0x3F, 0x44, 0xF8, 0x2D, 0xDF, 0x31, 0x0E, 0xE0, 0x74, 0xAB, 0x6A, 0x36, + 0x45, 0x97, 0xE8, 0x99, 0xA0, 0x25, 0x5D, 0xC1, 0x64, 0xF3, 0x1C, 0xC5, + 0x08, 0x46, 0x85, 0x1D, 0xF9, 0xAB, 0x48, 0x19, 0x5D, 0xED, 0x7E, 0xA1, + 0xB1, 0xD5, 0x10, 0xBD, 0x7E, 0xE7, 0x4D, 0x73, 0xFA, 0xF3, 0x6B, 0xC3, + 0x1E, 0xCF, 0xA2, 0x68, 0x35, 0x90, 0x46, 0xF4, 0xEB, 0x87, 0x9F, 0x92, + 0x40, 0x09, 0x43, 0x8B, 0x48, 0x1C, 0x6C, 0xD7, 0x88, 0x9A, 0x00, 0x2E, + 0xD5, 0xEE, 0x38, 0x2B, 0xC9, 0x19, 0x0D, 0xA6, 0xFC, 0x02, 0x6E, 0x47, + 0x95, 0x58, 0xE4, 0x47, 0x56, 0x77, 0xE9, 0xAA, 0x9E, 0x30, 0x50, 0xE2, + 0x76, 0x56, 0x94, 0xDF, 0xC8, 0x1F, 0x56, 0xE8, 0x80, 0xB9, 0x6E, 0x71, + 0x60, 0xC9, 0x80, 0xDD, 0x98, 0xED, 0xD3, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF + }; + int ret; + libssh2_sha512_ctx exchange_hash_ctx; + + if(key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value + (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* g == 2 */ + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 1024, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group18 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + + ret = diffie_hellman_sha_algo(session, key_state->g, key_state->p, 1024, + 512, (void *)&exchange_hash_ctx, + SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, + NULL, 0, &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + + return ret; +} + +/* kex_method_diffie_hellman_group_exchange_sha1_key_exchange + * Diffie-Hellman Group Exchange Key Exchange using SHA1 + * Negotiates random(ish) group for secret derivation + */ +static int +kex_method_diffie_hellman_group_exchange_sha1_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + int ret = 0; + int rc; + + if(key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init_from_bin(); + key_state->g = _libssh2_bn_init_from_bin(); + /* Ask for a P and G pair */ +#ifdef LIBSSH2_DH_GEX_NEW + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_MINGROUP); + _libssh2_htonu32(key_state->request + 5, LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_htonu32(key_state->request + 9, LIBSSH2_DH_GEX_MAXGROUP); + key_state->request_len = 13; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange " + "(New Method)"); +#else + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_OPTGROUP); + key_state->request_len = 5; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange " + "(Old Method)"); +#endif + + key_state->state = libssh2_NB_state_created; + } + + if(key_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send Group Exchange Request"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent; + } + + if(key_state->state == libssh2_NB_state_sent) { + rc = _libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for GEX_GROUP reply"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if(key_state->state == libssh2_NB_state_sent1) { + size_t p_len, g_len; + unsigned char *p, *g; + struct string_buf buf; + libssh2_sha1_ctx exchange_hash_ctx; + + if(key_state->data_len < 9) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto dh_gex_clean_exit; + } + + buf.data = key_state->data; + buf.dataptr = buf.data; + buf.len = key_state->data_len; + + buf.dataptr++; /* increment to big num */ + + if(_libssh2_get_bignum_bytes(&buf, &p, &p_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + if(_libssh2_get_bignum_bytes(&buf, &g, &g_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + _libssh2_bn_from_bin(key_state->p, p_len, p); + _libssh2_bn_from_bin(key_state->g, g_len, g); + + ret = diffie_hellman_sha_algo(session, key_state->g, key_state->p, + p_len, 1, + (void *)&exchange_hash_ctx, + SSH_MSG_KEX_DH_GEX_INIT, + SSH_MSG_KEX_DH_GEX_REPLY, + key_state->data + 1, + key_state->data_len - 1, + &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + + dh_gex_clean_exit: + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + + return ret; +} + + + +/* kex_method_diffie_hellman_group_exchange_sha256_key_exchange + * Diffie-Hellman Group Exchange Key Exchange using SHA256 + * Negotiates random(ish) group for secret derivation + */ +static int +kex_method_diffie_hellman_group_exchange_sha256_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + int ret = 0; + int rc; + + if(key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init(); + key_state->g = _libssh2_bn_init(); + /* Ask for a P and G pair */ +#ifdef LIBSSH2_DH_GEX_NEW + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_MINGROUP); + _libssh2_htonu32(key_state->request + 5, LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_htonu32(key_state->request + 9, LIBSSH2_DH_GEX_MAXGROUP); + key_state->request_len = 13; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange " + "(New Method SHA256)"); +#else + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_OPTGROUP); + key_state->request_len = 5; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange " + "(Old Method SHA256)"); +#endif + + key_state->state = libssh2_NB_state_created; + } + + if(key_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send " + "Group Exchange Request SHA256"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent; + } + + if(key_state->state == libssh2_NB_state_sent) { + rc = _libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for GEX_GROUP reply SHA256"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if(key_state->state == libssh2_NB_state_sent1) { + unsigned char *p, *g; + size_t p_len, g_len; + struct string_buf buf; + libssh2_sha256_ctx exchange_hash_ctx; + + if(key_state->data_len < 9) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto dh_gex_clean_exit; + } + + buf.data = key_state->data; + buf.dataptr = buf.data; + buf.len = key_state->data_len; + + buf.dataptr++; /* increment to big num */ + + if(_libssh2_get_bignum_bytes(&buf, &p, &p_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + if(_libssh2_get_bignum_bytes(&buf, &g, &g_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected value"); + goto dh_gex_clean_exit; + } + + _libssh2_bn_from_bin(key_state->p, p_len, p); + _libssh2_bn_from_bin(key_state->g, g_len, g); + + ret = diffie_hellman_sha_algo(session, key_state->g, key_state->p, + p_len, 256, + (void *)&exchange_hash_ctx, + SSH_MSG_KEX_DH_GEX_INIT, + SSH_MSG_KEX_DH_GEX_REPLY, + key_state->data + 1, + key_state->data_len - 1, + &key_state->exchange_state); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + + dh_gex_clean_exit: + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + + return ret; +} + + +/* LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY + * + * Macro that create and verifies EC SHA hash with a given digest bytes + * + * Payload format: + * + * string V_C, client's identification string (CR and LF excluded) + * string V_S, server's identification string (CR and LF excluded) + * string I_C, payload of the client's SSH_MSG_KEXINIT + * string I_S, payload of the server's SSH_MSG_KEXINIT + * string K_S, server's public host key + * string Q_C, client's ephemeral public key octet string + * string Q_S, server's ephemeral public key octet string + * mpint K, shared secret + * + */ + +#define LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(digest_type) \ +{ \ + libssh2_sha##digest_type##_ctx ctx; \ + exchange_state->exchange_hash = (void *)&ctx; \ + (void)libssh2_sha##digest_type##_init(&ctx); \ + if(session->local.banner) { \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + strlen((char *) session->local.banner) - 2); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + (char *) session->local.banner, \ + strlen((char *) \ + session->local.banner) \ + - 2); \ + } \ + else { \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + LIBSSH2_SSH_DEFAULT_BANNER, \ + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) \ + - 1); \ + } \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + strlen((char *) session->remote.banner)); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->remote.banner, \ + strlen((char *) \ + session->remote.banner)); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->local.kexinit_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->local.kexinit, \ + session->local.kexinit_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->remote.kexinit_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->remote.kexinit, \ + session->remote.kexinit_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->server_hostkey_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->server_hostkey, \ + session->server_hostkey_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + public_key_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + public_key, \ + public_key_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + server_public_key_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + server_public_key, \ + server_public_key_len); \ + \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->k_value, \ + exchange_state->k_value_len); \ + \ + libssh2_sha##digest_type##_final(ctx, exchange_state->h_sig_comp); \ + \ + if(session->hostkey-> \ + sig_verify(session, exchange_state->h_sig, \ + exchange_state->h_sig_len, exchange_state->h_sig_comp, \ + SHA##digest_type##_DIGEST_LENGTH, \ + &session->server_hostkey_abstract)) { \ + rc = -1; \ + } \ +} \ + + +#if LIBSSH2_ECDSA + +/* kex_session_ecdh_curve_type + * returns the EC curve type by name used in key exchange + */ + +static int +kex_session_ecdh_curve_type(const char *name, libssh2_curve_type *out_type) +{ + int ret = 0; + libssh2_curve_type type; + + if(name == NULL) + return -1; + + if(strcmp(name, "ecdh-sha2-nistp256") == 0) + type = LIBSSH2_EC_CURVE_NISTP256; + else if(strcmp(name, "ecdh-sha2-nistp384") == 0) + type = LIBSSH2_EC_CURVE_NISTP384; + else if(strcmp(name, "ecdh-sha2-nistp521") == 0) + type = LIBSSH2_EC_CURVE_NISTP521; + else { + ret = -1; + } + + if(ret == 0 && out_type) { + *out_type = type; + } + + return ret; +} + + +/* ecdh_sha2_nistp + * Elliptic Curve Diffie Hellman Key Exchange + */ + +static int ecdh_sha2_nistp(LIBSSH2_SESSION *session, libssh2_curve_type type, + unsigned char *data, size_t data_len, + unsigned char *public_key, + size_t public_key_len, _libssh2_ec_key *private_key, + kmdhgGPshakex_state_t *exchange_state) +{ + int ret = 0; + int rc; + + if(data_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Host key data is too short"); + return ret; + } + + if(exchange_state->state == libssh2_NB_state_idle) { + + /* Setup initial values */ + exchange_state->k = _libssh2_bn_init(); + + exchange_state->state = libssh2_NB_state_created; + } + + if(exchange_state->state == libssh2_NB_state_created) { + /* parse INIT reply data */ + + /* host key K_S */ + unsigned char *server_public_key; + size_t server_public_key_len; + struct string_buf buf; + + buf.data = data; + buf.len = data_len; + buf.dataptr = buf.data; + buf.dataptr++; /* Advance past packet type */ + + if(_libssh2_copy_string(session, &buf, &(session->server_hostkey), + &server_public_key_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for a copy " + "of the host key"); + goto clean_exit; + } + + session->server_hostkey_len = (uint32_t)server_public_key_len; + +#if LIBSSH2_MD5 + { + libssh2_md5_ctx fingerprint_ctx; + + if(libssh2_md5_init(&fingerprint_ctx)) { + libssh2_md5_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_md5_final(fingerprint_ctx, + session->server_hostkey_md5); + session->server_hostkey_md5_valid = TRUE; + } + else { + session->server_hostkey_md5_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[50], *fprint = fingerprint; + int i; + for(i = 0; i < 16; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's MD5 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ +#endif /* ! LIBSSH2_MD5 */ + + { + libssh2_sha1_ctx fingerprint_ctx; + + if(libssh2_sha1_init(&fingerprint_ctx)) { + libssh2_sha1_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha1_final(fingerprint_ctx, + session->server_hostkey_sha1); + session->server_hostkey_sha1_valid = TRUE; + } + else { + session->server_hostkey_sha1_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[64], *fprint = fingerprint; + int i; + + for(i = 0; i < 20; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA1 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ + + /* SHA256 */ + { + libssh2_sha256_ctx fingerprint_ctx; + + if(libssh2_sha256_init(&fingerprint_ctx)) { + libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha256_final(fingerprint_ctx, + session->server_hostkey_sha256); + session->server_hostkey_sha256_valid = TRUE; + } + else { + session->server_hostkey_sha256_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char *base64Fingerprint = NULL; + _libssh2_base64_encode(session, + (const char *) + session->server_hostkey_sha256, + SHA256_DIGEST_LENGTH, &base64Fingerprint); + if(base64Fingerprint != NULL) { + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA256 Fingerprint: %s", + base64Fingerprint); + LIBSSH2_FREE(session, base64Fingerprint); + } + } +#endif /* LIBSSH2DEBUG */ + + if(session->hostkey->init(session, session->server_hostkey, + session->server_hostkey_len, + &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to initialize hostkey importer"); + goto clean_exit; + } + + /* server public key Q_S */ + if(_libssh2_get_string(&buf, &server_public_key, + &server_public_key_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + + /* server signature */ + if(_libssh2_get_string(&buf, &exchange_state->h_sig, + &(exchange_state->h_sig_len))) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unexpected ecdh server sig length"); + goto clean_exit; + } + + /* Compute the shared secret K */ + rc = _libssh2_ecdh_gen_k(&exchange_state->k, private_key, + server_public_key, server_public_key_len); + if(rc != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, + "Unable to create ECDH shared secret"); + goto clean_exit; + } + + exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5; + if(_libssh2_bn_bits(exchange_state->k) % 8) { + /* don't need leading 00 */ + exchange_state->k_value_len--; + } + exchange_state->k_value = + LIBSSH2_ALLOC(session, exchange_state->k_value_len); + if(!exchange_state->k_value) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for K"); + goto clean_exit; + } + _libssh2_htonu32(exchange_state->k_value, + exchange_state->k_value_len - 4); + if(_libssh2_bn_bits(exchange_state->k) % 8) { + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4); + } + else { + exchange_state->k_value[4] = 0; + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5); + } + + /* verify hash */ + + switch(type) { + case LIBSSH2_EC_CURVE_NISTP256: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(256); + break; + + case LIBSSH2_EC_CURVE_NISTP384: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(384); + break; + case LIBSSH2_EC_CURVE_NISTP521: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(512); + break; + } + + if(rc != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, + "Unable to verify hostkey signature"); + goto clean_exit; + } + + exchange_state->c = SSH_MSG_NEWKEYS; + exchange_state->state = libssh2_NB_state_sent; + } + + if(exchange_state->state == libssh2_NB_state_sent) { + rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send NEWKEYS message"); + goto clean_exit; + } + + exchange_state->state = libssh2_NB_state_sent2; + } + + if(exchange_state->state == libssh2_NB_state_sent2) { + rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS, + &exchange_state->tmp, + &exchange_state->tmp_len, 0, NULL, 0, + &exchange_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS"); + goto clean_exit; + } + + /* The first key exchange has been performed, + switch to active crypt/comp/mac mode */ + session->state |= LIBSSH2_STATE_NEWKEYS; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message"); + + /* This will actually end up being just packet_type(1) + for this packet type anyway */ + LIBSSH2_FREE(session, exchange_state->tmp); + + if(!session->session_id) { + + size_t digest_length = 0; + + if(type == LIBSSH2_EC_CURVE_NISTP256) + digest_length = SHA256_DIGEST_LENGTH; + else if(type == LIBSSH2_EC_CURVE_NISTP384) + digest_length = SHA384_DIGEST_LENGTH; + else if(type == LIBSSH2_EC_CURVE_NISTP521) + digest_length = SHA512_DIGEST_LENGTH; + else{ + ret = _libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, + "Unknown SHA digest for EC curve"); + goto clean_exit; + + } + session->session_id = LIBSSH2_ALLOC(session, digest_length); + if(!session->session_id) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for " + "SHA digest"); + goto clean_exit; + } + memcpy(session->session_id, exchange_state->h_sig_comp, + digest_length); + session->session_id_len = digest_length; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "session_id calculated"); + } + + /* Cleanup any existing cipher */ + if(session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + + /* Calculate IV/Secret/Key for each direction */ + if(session->local.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(iv, + session->local.crypt-> + iv_len, "A"); + if(!iv) { + ret = -1; + goto clean_exit; + } + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(secret, + session->local.crypt-> + secret_len, "C"); + + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->local.crypt-> + init(session, session->local.crypt, iv, &free_iv, secret, + &free_secret, 1, &session->local.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server IV and Key calculated"); + + if(session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + + if(session->remote.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(iv, + session->remote.crypt-> + iv_len, "B"); + + if(!iv) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(secret, + session->remote.crypt-> + secret_len, "D"); + + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->remote.crypt-> + init(session, session->remote.crypt, iv, &free_iv, secret, + &free_secret, 0, &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client IV and Key calculated"); + + if(session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if(session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(key, + session->local.mac-> + key_len, "E"); + + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->local.mac->init(session, key, &free_key, + &session->local.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->local.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server HMAC Key calculated"); + + if(session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if(session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(key, + session->remote.mac-> + key_len, "F"); + + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->remote.mac->init(session, key, &free_key, + &session->remote.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->remote.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client HMAC Key calculated"); + + /* Initialize compression for each direction */ + + /* Cleanup any existing compression */ + if(session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + + if(session->local.comp && session->local.comp->init) { + if(session->local.comp->init(session, 1, + &session->local.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server compression initialized"); + + if(session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + + if(session->remote.comp && session->remote.comp->init) { + if(session->remote.comp->init(session, 0, + &session->remote.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client compression initialized"); + + } + +clean_exit: + _libssh2_bn_free(exchange_state->k); + exchange_state->k = NULL; + + if(exchange_state->k_value) { + LIBSSH2_FREE(session, exchange_state->k_value); + exchange_state->k_value = NULL; + } + + exchange_state->state = libssh2_NB_state_idle; + + return ret; +} + +/* kex_method_ecdh_key_exchange + * + * Elliptic Curve Diffie Hellman Key Exchange + * supports SHA256/384/512 hashes based on negotated ecdh method + * + */ + +static int +kex_method_ecdh_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + int ret = 0; + int rc = 0; + unsigned char *s; + libssh2_curve_type type; + + if(key_state->state == libssh2_NB_state_idle) { + + key_state->public_key_oct = NULL; + key_state->state = libssh2_NB_state_created; + } + + if(key_state->state == libssh2_NB_state_created) { + rc = kex_session_ecdh_curve_type(session->kex->name, &type); + + if(rc != 0) { + ret = _libssh2_error(session, -1, + "Unknown KEX nistp curve type"); + goto ecdh_clean_exit; + } + + rc = _libssh2_ecdsa_create_key(session, &key_state->private_key, + &key_state->public_key_oct, + &key_state->public_key_oct_len, type); + + if(rc != 0) { + ret = _libssh2_error(session, rc, + "Unable to create private key"); + goto ecdh_clean_exit; + } + + key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; + s = key_state->request + 1; + _libssh2_store_str(&s, (const char *)key_state->public_key_oct, + key_state->public_key_oct_len); + key_state->request_len = key_state->public_key_oct_len + 5; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating ECDH SHA2 NISTP256"); + + key_state->state = libssh2_NB_state_sent; + } + + if(key_state->state == libssh2_NB_state_sent) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send ECDH_INIT"); + goto ecdh_clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if(key_state->state == libssh2_NB_state_sent1) { + rc = _libssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for ECDH_REPLY reply"); + goto ecdh_clean_exit; + } + + key_state->state = libssh2_NB_state_sent2; + } + + if(key_state->state == libssh2_NB_state_sent2) { + + (void)kex_session_ecdh_curve_type(session->kex->name, &type); + + ret = ecdh_sha2_nistp(session, type, key_state->data, + key_state->data_len, + (unsigned char *)key_state->public_key_oct, + key_state->public_key_oct_len, + key_state->private_key, + &key_state->exchange_state); + + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + +ecdh_clean_exit: + + if(key_state->public_key_oct) { + LIBSSH2_FREE(session, key_state->public_key_oct); + key_state->public_key_oct = NULL; + } + + if(key_state->private_key) { + _libssh2_ecdsa_free(key_state->private_key); + key_state->private_key = NULL; + } + + key_state->state = libssh2_NB_state_idle; + + return ret; +} + +#endif /*LIBSSH2_ECDSA*/ + + +#if LIBSSH2_ED25519 + +/* curve25519_sha256 + * Elliptic Curve Key Exchange + */ + +static int +curve25519_sha256(LIBSSH2_SESSION *session, unsigned char *data, + size_t data_len, + unsigned char public_key[LIBSSH2_ED25519_KEY_LEN], + unsigned char private_key[LIBSSH2_ED25519_KEY_LEN], + kmdhgGPshakex_state_t *exchange_state) +{ + int ret = 0; + int rc; + int public_key_len = LIBSSH2_ED25519_KEY_LEN; + + if(data_len < 5) { + return _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Data is too short"); + } + + if(exchange_state->state == libssh2_NB_state_idle) { + + /* Setup initial values */ + exchange_state->k = _libssh2_bn_init(); + + exchange_state->state = libssh2_NB_state_created; + } + + if(exchange_state->state == libssh2_NB_state_created) { + /* parse INIT reply data */ + unsigned char *server_public_key, *server_host_key; + size_t server_public_key_len, hostkey_len; + struct string_buf buf; + + if(data_len < 5) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + + buf.data = data; + buf.len = data_len; + buf.dataptr = buf.data; + buf.dataptr++; /* advance past packet type */ + + if(_libssh2_get_string(&buf, &server_host_key, &hostkey_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + + session->server_hostkey_len = (uint32_t)hostkey_len; + session->server_hostkey = LIBSSH2_ALLOC(session, + session->server_hostkey_len); + if(!session->server_hostkey) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for a copy " + "of the host key"); + goto clean_exit; + } + + memcpy(session->server_hostkey, server_host_key, + session->server_hostkey_len); + +#if LIBSSH2_MD5 + { + libssh2_md5_ctx fingerprint_ctx; + + if(libssh2_md5_init(&fingerprint_ctx)) { + libssh2_md5_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_md5_final(fingerprint_ctx, + session->server_hostkey_md5); + session->server_hostkey_md5_valid = TRUE; + } + else { + session->server_hostkey_md5_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[50], *fprint = fingerprint; + int i; + for(i = 0; i < 16; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's MD5 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ +#endif /* ! LIBSSH2_MD5 */ + + { + libssh2_sha1_ctx fingerprint_ctx; + + if(libssh2_sha1_init(&fingerprint_ctx)) { + libssh2_sha1_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha1_final(fingerprint_ctx, + session->server_hostkey_sha1); + session->server_hostkey_sha1_valid = TRUE; + } + else { + session->server_hostkey_sha1_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[64], *fprint = fingerprint; + int i; + + for(i = 0; i < 20; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA1 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ + + /* SHA256 */ + { + libssh2_sha256_ctx fingerprint_ctx; + + if(libssh2_sha256_init(&fingerprint_ctx)) { + libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha256_final(fingerprint_ctx, + session->server_hostkey_sha256); + session->server_hostkey_sha256_valid = TRUE; + } + else { + session->server_hostkey_sha256_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char *base64Fingerprint = NULL; + _libssh2_base64_encode(session, + (const char *) + session->server_hostkey_sha256, + SHA256_DIGEST_LENGTH, &base64Fingerprint); + if(base64Fingerprint != NULL) { + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA256 Fingerprint: %s", + base64Fingerprint); + LIBSSH2_FREE(session, base64Fingerprint); + } + } +#endif /* LIBSSH2DEBUG */ + + if(session->hostkey->init(session, session->server_hostkey, + session->server_hostkey_len, + &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to initialize hostkey importer"); + goto clean_exit; + } + + /* server public key Q_S */ + if(_libssh2_get_string(&buf, &server_public_key, + &server_public_key_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected key length"); + goto clean_exit; + } + + if(server_public_key_len != LIBSSH2_ED25519_KEY_LEN) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unexpected curve25519 server " + "public key length"); + goto clean_exit; + } + + /* server signature */ + if(_libssh2_get_string(&buf, &exchange_state->h_sig, + &(exchange_state->h_sig_len))) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unexpected curve25519 server sig length"); + goto clean_exit; + } + + /* Compute the shared secret K */ + rc = _libssh2_curve25519_gen_k(&exchange_state->k, private_key, + server_public_key); + if(rc != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, + "Unable to create ECDH shared secret"); + goto clean_exit; + } + + exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5; + if(_libssh2_bn_bits(exchange_state->k) % 8) { + /* don't need leading 00 */ + exchange_state->k_value_len--; + } + exchange_state->k_value = + LIBSSH2_ALLOC(session, exchange_state->k_value_len); + if(!exchange_state->k_value) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for K"); + goto clean_exit; + } + _libssh2_htonu32(exchange_state->k_value, + exchange_state->k_value_len - 4); + if(_libssh2_bn_bits(exchange_state->k) % 8) { + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4); + } + else { + exchange_state->k_value[4] = 0; + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5); + } + + /*/ verify hash */ + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(256); + + if(rc != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, + "Unable to verify hostkey signature"); + goto clean_exit; + } + + exchange_state->c = SSH_MSG_NEWKEYS; + exchange_state->state = libssh2_NB_state_sent; + } + + if(exchange_state->state == libssh2_NB_state_sent) { + rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send NEWKEYS message"); + goto clean_exit; + } + + exchange_state->state = libssh2_NB_state_sent2; + } + + if(exchange_state->state == libssh2_NB_state_sent2) { + rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS, + &exchange_state->tmp, + &exchange_state->tmp_len, 0, NULL, 0, + &exchange_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS"); + goto clean_exit; + } + + /* The first key exchange has been performed, switch to active + crypt/comp/mac mode */ + + session->state |= LIBSSH2_STATE_NEWKEYS; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message"); + + /* This will actually end up being just packet_type(1) for this packet + type anyway */ + LIBSSH2_FREE(session, exchange_state->tmp); + + if(!session->session_id) { + + size_t digest_length = SHA256_DIGEST_LENGTH; + session->session_id = LIBSSH2_ALLOC(session, digest_length); + if(!session->session_id) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for " + "SHA digest"); + goto clean_exit; + } + memcpy(session->session_id, exchange_state->h_sig_comp, + digest_length); + session->session_id_len = digest_length; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "session_id calculated"); + } + + /* Cleanup any existing cipher */ + if(session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + + /* Calculate IV/Secret/Key for each direction */ + if(session->local.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, iv, + session->local.crypt-> + iv_len, "A"); + if(!iv) { + ret = -1; + goto clean_exit; + } + + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, secret, + session->local.crypt-> + secret_len, "C"); + + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->local.crypt-> + init(session, session->local.crypt, iv, &free_iv, secret, + &free_secret, 1, &session->local.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server IV and Key calculated"); + + if(session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + + if(session->remote.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, iv, + session->remote.crypt-> + iv_len, "B"); + + if(!iv) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, secret, + session->remote.crypt-> + secret_len, "D"); + + if(!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if(session->remote.crypt-> + init(session, session->remote.crypt, iv, &free_iv, secret, + &free_secret, 0, &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if(free_iv) { + _libssh2_explicit_zero(iv, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if(free_secret) { + _libssh2_explicit_zero(secret, + session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client IV and Key calculated"); + + if(session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if(session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, key, + session->local.mac-> + key_len, "E"); + + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->local.mac->init(session, key, &free_key, + &session->local.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->local.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server HMAC Key calculated"); + + if(session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if(session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, key, + session->remote.mac-> + key_len, "F"); + + if(!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->remote.mac->init(session, key, &free_key, + &session->remote.mac_abstract); + + if(free_key) { + _libssh2_explicit_zero(key, session->remote.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client HMAC Key calculated"); + + /* Initialize compression for each direction */ + + /* Cleanup any existing compression */ + if(session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + + if(session->local.comp && session->local.comp->init) { + if(session->local.comp->init(session, 1, + &session->local.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server compression initialized"); + + if(session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + + if(session->remote.comp && session->remote.comp->init) { + if(session->remote.comp->init(session, 0, + &session->remote.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client compression initialized"); + } + +clean_exit: + _libssh2_bn_free(exchange_state->k); + exchange_state->k = NULL; + + if(exchange_state->k_value) { + LIBSSH2_FREE(session, exchange_state->k_value); + exchange_state->k_value = NULL; + } + + exchange_state->state = libssh2_NB_state_idle; + + return ret; +} + +/* kex_method_curve25519_key_exchange + * + * Elliptic Curve X25519 Key Exchange with SHA256 hash + * + */ + +static int +kex_method_curve25519_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + int ret = 0; + int rc = 0; + + if(key_state->state == libssh2_NB_state_idle) { + + key_state->public_key_oct = NULL; + key_state->state = libssh2_NB_state_created; + } + + if(key_state->state == libssh2_NB_state_created) { + unsigned char *s = NULL; + + rc = strcmp(session->kex->name, "curve25519-sha256@libssh.org"); + if(rc != 0) + rc = strcmp(session->kex->name, "curve25519-sha256"); + + if(rc != 0) { + ret = _libssh2_error(session, -1, + "Unknown KEX curve25519 curve type"); + goto clean_exit; + } + + rc = _libssh2_curve25519_new(session, + &key_state->curve25519_public_key, + &key_state->curve25519_private_key); + + if(rc != 0) { + ret = _libssh2_error(session, rc, + "Unable to create private key"); + goto clean_exit; + } + + key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; + s = key_state->request + 1; + _libssh2_store_str(&s, (const char *)key_state->curve25519_public_key, + LIBSSH2_ED25519_KEY_LEN); + key_state->request_len = LIBSSH2_ED25519_KEY_LEN + 5; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating curve25519 SHA2"); + + key_state->state = libssh2_NB_state_sent; + } + + if(key_state->state == libssh2_NB_state_sent) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Unable to send ECDH_INIT"); + goto clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if(key_state->state == libssh2_NB_state_sent1) { + rc = _libssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for ECDH_REPLY reply"); + goto clean_exit; + } + + key_state->state = libssh2_NB_state_sent2; + } + + if(key_state->state == libssh2_NB_state_sent2) { + + ret = curve25519_sha256(session, key_state->data, key_state->data_len, + key_state->curve25519_public_key, + key_state->curve25519_private_key, + &key_state->exchange_state); + + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + +clean_exit: + + if(key_state->curve25519_public_key) { + _libssh2_explicit_zero(key_state->curve25519_public_key, + LIBSSH2_ED25519_KEY_LEN); + LIBSSH2_FREE(session, key_state->curve25519_public_key); + key_state->curve25519_public_key = NULL; + } + + if(key_state->curve25519_private_key) { + _libssh2_explicit_zero(key_state->curve25519_private_key, + LIBSSH2_ED25519_KEY_LEN); + LIBSSH2_FREE(session, key_state->curve25519_private_key); + key_state->curve25519_private_key = NULL; + } + + key_state->state = libssh2_NB_state_idle; + + return ret; +} + + +#endif /*LIBSSH2_ED25519*/ + + +#define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 +#define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group1_sha1 = { + "diffie-hellman-group1-sha1", + kex_method_diffie_hellman_group1_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group14_sha1 = { + "diffie-hellman-group14-sha1", + kex_method_diffie_hellman_group14_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group14_sha256 = { + "diffie-hellman-group14-sha256", + kex_method_diffie_hellman_group14_sha256_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group16_sha512 = { + "diffie-hellman-group16-sha512", + kex_method_diffie_hellman_group16_sha512_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group18_sha512 = { + "diffie-hellman-group18-sha512", + kex_method_diffie_hellman_group18_sha512_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_diffie_helman_group_exchange_sha1 = { + "diffie-hellman-group-exchange-sha1", + kex_method_diffie_hellman_group_exchange_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_diffie_helman_group_exchange_sha256 = { + "diffie-hellman-group-exchange-sha256", + kex_method_diffie_hellman_group_exchange_sha256_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +#if LIBSSH2_ECDSA +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp256 = { + "ecdh-sha2-nistp256", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp384 = { + "ecdh-sha2-nistp384", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp521 = { + "ecdh-sha2-nistp521", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; +#endif + +#if LIBSSH2_ED25519 +static const LIBSSH2_KEX_METHOD +kex_method_ssh_curve25519_sha256_libssh = { + "curve25519-sha256@libssh.org", + kex_method_curve25519_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; +static const LIBSSH2_KEX_METHOD +kex_method_ssh_curve25519_sha256 = { + "curve25519-sha256", + kex_method_curve25519_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; +#endif + +/* this kex method signals that client can receive extensions + * as described in https://datatracker.ietf.org/doc/html/rfc8308 +*/ + +static const LIBSSH2_KEX_METHOD +kex_method_extension_negotiation = { + "ext-info-c", + NULL, + 0, +}; + +static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { +#if LIBSSH2_ED25519 + &kex_method_ssh_curve25519_sha256, + &kex_method_ssh_curve25519_sha256_libssh, +#endif +#if LIBSSH2_ECDSA + &kex_method_ecdh_sha2_nistp256, + &kex_method_ecdh_sha2_nistp384, + &kex_method_ecdh_sha2_nistp521, +#endif + &kex_method_diffie_helman_group_exchange_sha256, + &kex_method_diffie_helman_group16_sha512, + &kex_method_diffie_helman_group18_sha512, + &kex_method_diffie_helman_group14_sha256, + &kex_method_diffie_helman_group14_sha1, + &kex_method_diffie_helman_group1_sha1, + &kex_method_diffie_helman_group_exchange_sha1, + &kex_method_extension_negotiation, + NULL +}; + +typedef struct _LIBSSH2_COMMON_METHOD +{ + const char *name; +} LIBSSH2_COMMON_METHOD; + +/* kex_method_strlen + * Calculate the length of a particular method list's resulting string + * Includes SUM(strlen() of each individual method plus 1 (for coma)) - 1 + * (because the last coma isn't used) + * Another sign of bad coding practices gone mad. Pretend you don't see this. + */ +static size_t +kex_method_strlen(LIBSSH2_COMMON_METHOD ** method) +{ + size_t len = 0; + + if(!method || !*method) { + return 0; + } + + while(*method && (*method)->name) { + len += strlen((*method)->name) + 1; + method++; + } + + return len - 1; +} + + + +/* kex_method_list + * Generate formatted preference list in buf + */ +static size_t +kex_method_list(unsigned char *buf, size_t list_strlen, + LIBSSH2_COMMON_METHOD ** method) +{ + _libssh2_htonu32(buf, list_strlen); + buf += 4; + + if(!method || !*method) { + return 4; + } + + while(*method && (*method)->name) { + int mlen = strlen((*method)->name); + memcpy(buf, (*method)->name, mlen); + buf += mlen; + *(buf++) = ','; + method++; + } + + return list_strlen + 4; +} + + + +#define LIBSSH2_METHOD_PREFS_LEN(prefvar, defaultvar) \ + ((prefvar) ? strlen(prefvar) : \ + kex_method_strlen((LIBSSH2_COMMON_METHOD**)(defaultvar))) + +#define LIBSSH2_METHOD_PREFS_STR(buf, prefvarlen, prefvar, defaultvar) \ + if(prefvar) { \ + _libssh2_htonu32((buf), (prefvarlen)); \ + buf += 4; \ + memcpy((buf), (prefvar), (prefvarlen)); \ + buf += (prefvarlen); \ + } \ + else { \ + buf += kex_method_list((buf), (prefvarlen), \ + (LIBSSH2_COMMON_METHOD**)(defaultvar)); \ + } + +/* kexinit + * Send SSH_MSG_KEXINIT packet + */ +static int kexinit(LIBSSH2_SESSION * session) +{ + /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) + + reserved(4) + length longs(40) */ + size_t data_len = 62; + size_t kex_len, hostkey_len = 0; + size_t crypt_cs_len, crypt_sc_len; + size_t comp_cs_len, comp_sc_len; + size_t mac_cs_len, mac_sc_len; + size_t lang_cs_len, lang_sc_len; + unsigned char *data, *s; + int rc; + + if(session->kexinit_state == libssh2_NB_state_idle) { + kex_len = + LIBSSH2_METHOD_PREFS_LEN(session->kex_prefs, libssh2_kex_methods); + hostkey_len = + LIBSSH2_METHOD_PREFS_LEN(session->hostkey_prefs, + libssh2_hostkey_methods()); + crypt_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.crypt_prefs, + libssh2_crypt_methods()); + crypt_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.crypt_prefs, + libssh2_crypt_methods()); + mac_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.mac_prefs, + _libssh2_mac_methods()); + mac_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.mac_prefs, + _libssh2_mac_methods()); + comp_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.comp_prefs, + _libssh2_comp_methods(session)); + comp_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.comp_prefs, + _libssh2_comp_methods(session)); + lang_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.lang_prefs, NULL); + lang_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.lang_prefs, NULL); + + data_len += kex_len + hostkey_len + crypt_cs_len + crypt_sc_len + + comp_cs_len + comp_sc_len + mac_cs_len + mac_sc_len + + lang_cs_len + lang_sc_len; + + s = data = LIBSSH2_ALLOC(session, data_len); + if(!data) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory"); + } + + *(s++) = SSH_MSG_KEXINIT; + + if(_libssh2_random(s, 16)) { + return _libssh2_error(session, LIBSSH2_ERROR_RANDGEN, + "Unable to get random bytes " + "for KEXINIT cookie"); + } + s += 16; + + /* Ennumerating through these lists twice is probably (certainly?) + inefficient from a CPU standpoint, but it saves multiple + malloc/realloc calls */ + LIBSSH2_METHOD_PREFS_STR(s, kex_len, session->kex_prefs, + libssh2_kex_methods); + LIBSSH2_METHOD_PREFS_STR(s, hostkey_len, session->hostkey_prefs, + libssh2_hostkey_methods()); + LIBSSH2_METHOD_PREFS_STR(s, crypt_cs_len, session->local.crypt_prefs, + libssh2_crypt_methods()); + LIBSSH2_METHOD_PREFS_STR(s, crypt_sc_len, session->remote.crypt_prefs, + libssh2_crypt_methods()); + LIBSSH2_METHOD_PREFS_STR(s, mac_cs_len, session->local.mac_prefs, + _libssh2_mac_methods()); + LIBSSH2_METHOD_PREFS_STR(s, mac_sc_len, session->remote.mac_prefs, + _libssh2_mac_methods()); + LIBSSH2_METHOD_PREFS_STR(s, comp_cs_len, session->local.comp_prefs, + _libssh2_comp_methods(session)); + LIBSSH2_METHOD_PREFS_STR(s, comp_sc_len, session->remote.comp_prefs, + _libssh2_comp_methods(session)); + LIBSSH2_METHOD_PREFS_STR(s, lang_cs_len, session->local.lang_prefs, + NULL); + LIBSSH2_METHOD_PREFS_STR(s, lang_sc_len, session->remote.lang_prefs, + NULL); + + /* No optimistic KEX packet follows */ + /* Deal with optimistic packets + * session->flags |= KEXINIT_OPTIMISTIC + * session->flags |= KEXINIT_METHODSMATCH + */ + *(s++) = 0; + + /* Reserved == 0 */ + _libssh2_htonu32(s, 0); + +#ifdef LIBSSH2DEBUG + { + /* Funnily enough, they'll all "appear" to be '\0' terminated */ + unsigned char *p = data + 21; /* type(1) + cookie(16) + len(4) */ + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent KEX: %s", p); + p += kex_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent HOSTKEY: %s", p); + p += hostkey_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_CS: %s", p); + p += crypt_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_SC: %s", p); + p += crypt_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_CS: %s", p); + p += mac_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_SC: %s", p); + p += mac_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_CS: %s", p); + p += comp_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_SC: %s", p); + p += comp_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_CS: %s", p); + p += lang_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_SC: %s", p); + p += lang_sc_len + 4; + } +#endif /* LIBSSH2DEBUG */ + + session->kexinit_state = libssh2_NB_state_created; + } + else { + data = session->kexinit_data; + data_len = session->kexinit_data_len; + /* zap the variables to ensure there is NOT a double free later */ + session->kexinit_data = NULL; + session->kexinit_data_len = 0; + } + + rc = _libssh2_transport_send(session, data, data_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + session->kexinit_data = data; + session->kexinit_data_len = data_len; + return rc; + } + else if(rc) { + LIBSSH2_FREE(session, data); + session->kexinit_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send KEXINIT packet to remote host"); + + } + + if(session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + + session->local.kexinit = data; + session->local.kexinit_len = data_len; + + session->kexinit_state = libssh2_NB_state_idle; + + return 0; +} + +/* kex_agree_instr + * Kex specific variant of strstr() + * Needle must be precede by BOL or ',', and followed by ',' or EOL + */ +static unsigned char * +kex_agree_instr(unsigned char *haystack, unsigned long haystack_len, + const unsigned char *needle, unsigned long needle_len) +{ + unsigned char *s; + unsigned char *end_haystack; + unsigned long left; + + if(haystack == NULL || needle == NULL) { + return NULL; + } + + /* Haystack too short to bother trying */ + if(haystack_len < needle_len || needle_len == 0) { + return NULL; + } + + s = haystack; + end_haystack = &haystack[haystack_len]; + left = end_haystack - s; + + /* Needle at start of haystack */ + if((strncmp((char *) haystack, (char *) needle, needle_len) == 0) && + (needle_len == haystack_len || haystack[needle_len] == ',')) { + return haystack; + } + + /* Search until we run out of comas or we run out of haystack, + whichever comes first */ + while((s = (unsigned char *) memchr((char *) s, ',', left)) != NULL) { + /* Advance buffer past coma if we can */ + left = end_haystack - s; + if((left >= 1) && (left <= haystack_len) && (left > needle_len)) { + s++; + } + else { + return NULL; + } + + /* Needle at X position */ + if((strncmp((char *) s, (char *) needle, needle_len) == 0) && + (((s - haystack) + needle_len) == haystack_len + || s[needle_len] == ',')) { + return s; + } + } + + return NULL; +} + + + +/* kex_get_method_by_name + */ +static const LIBSSH2_COMMON_METHOD * +kex_get_method_by_name(const char *name, size_t name_len, + const LIBSSH2_COMMON_METHOD ** methodlist) +{ + while(*methodlist) { + if((strlen((*methodlist)->name) == name_len) && + (strncmp((*methodlist)->name, name, name_len) == 0)) { + return *methodlist; + } + methodlist++; + } + return NULL; +} + + + +/* kex_agree_hostkey + * Agree on a Hostkey which works with this kex + */ +static int kex_agree_hostkey(LIBSSH2_SESSION * session, + unsigned long kex_flags, + unsigned char *hostkey, unsigned long hostkey_len) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkeyp = libssh2_hostkey_methods(); + unsigned char *s; + + if(session->hostkey_prefs) { + s = (unsigned char *) session->hostkey_prefs; + + while(s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + if(kex_agree_instr(hostkey, hostkey_len, s, method_len)) { + const LIBSSH2_HOSTKEY_METHOD *method = + (const LIBSSH2_HOSTKEY_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + hostkeyp); + + if(!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + /* So far so good, but does it suit our purposes? (Encrypting + vs Signing) */ + if(((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == + 0) || (method->encrypt)) { + /* Either this hostkey can do encryption or this kex just + doesn't require it */ + if(((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) + == 0) || (method->sig_verify)) { + /* Either this hostkey can do signing or this kex just + doesn't require it */ + session->hostkey = method; + return 0; + } + } + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while(hostkeyp && (*hostkeyp) && (*hostkeyp)->name) { + s = kex_agree_instr(hostkey, hostkey_len, + (unsigned char *) (*hostkeyp)->name, + strlen((*hostkeyp)->name)); + if(s) { + /* So far so good, but does it suit our purposes? (Encrypting vs + Signing) */ + if(((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) || + ((*hostkeyp)->encrypt)) { + /* Either this hostkey can do encryption or this kex just + doesn't require it */ + if(((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == + 0) || ((*hostkeyp)->sig_verify)) { + /* Either this hostkey can do signing or this kex just + doesn't require it */ + session->hostkey = *hostkeyp; + return 0; + } + } + } + hostkeyp++; + } + + return -1; +} + + + +/* kex_agree_kex_hostkey + * Agree on a Key Exchange method and a hostkey encoding type + */ +static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, + unsigned long kex_len, unsigned char *hostkey, + unsigned long hostkey_len) +{ + const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods; + unsigned char *s; + + if(session->kex_prefs) { + s = (unsigned char *) session->kex_prefs; + + while(s && *s) { + unsigned char *q, *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + q = kex_agree_instr(kex, kex_len, s, method_len); + if(q) { + const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + kexp); + + if(!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + /* We've agreed on a key exchange method, + * Can we agree on a hostkey that works with this kex? + */ + if(kex_agree_hostkey(session, method->flags, hostkey, + hostkey_len) == 0) { + session->kex = method; + if(session->burn_optimistic_kexinit && (kex == q)) { + /* Server sent an optimistic packet, and client agrees + * with preference cancel burning the first KEX_INIT + * packet that comes in */ + session->burn_optimistic_kexinit = 0; + } + return 0; + } + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while(*kexp && (*kexp)->name) { + s = kex_agree_instr(kex, kex_len, + (unsigned char *) (*kexp)->name, + strlen((*kexp)->name)); + if(s) { + /* We've agreed on a key exchange method, + * Can we agree on a hostkey that works with this kex? + */ + if(kex_agree_hostkey(session, (*kexp)->flags, hostkey, + hostkey_len) == 0) { + session->kex = *kexp; + if(session->burn_optimistic_kexinit && (kex == s)) { + /* Server sent an optimistic packet, and client agrees + * with preference cancel burning the first KEX_INIT + * packet that comes in */ + session->burn_optimistic_kexinit = 0; + } + return 0; + } + } + kexp++; + } + return -1; +} + + + +/* kex_agree_crypt + * Agree on a cipher algo + */ +static int kex_agree_crypt(LIBSSH2_SESSION * session, + libssh2_endpoint_data *endpoint, + unsigned char *crypt, + unsigned long crypt_len) +{ + const LIBSSH2_CRYPT_METHOD **cryptp = libssh2_crypt_methods(); + unsigned char *s; + + (void) session; + + if(endpoint->crypt_prefs) { + s = (unsigned char *) endpoint->crypt_prefs; + + while(s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if(kex_agree_instr(crypt, crypt_len, s, method_len)) { + const LIBSSH2_CRYPT_METHOD *method = + (const LIBSSH2_CRYPT_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + cryptp); + + if(!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->crypt = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while(*cryptp && (*cryptp)->name) { + s = kex_agree_instr(crypt, crypt_len, + (unsigned char *) (*cryptp)->name, + strlen((*cryptp)->name)); + if(s) { + endpoint->crypt = *cryptp; + return 0; + } + cryptp++; + } + + return -1; +} + + + +/* kex_agree_mac + * Agree on a message authentication hash + */ +static int kex_agree_mac(LIBSSH2_SESSION * session, + libssh2_endpoint_data * endpoint, unsigned char *mac, + unsigned long mac_len) +{ + const LIBSSH2_MAC_METHOD **macp = _libssh2_mac_methods(); + unsigned char *s; + (void) session; + + if(endpoint->mac_prefs) { + s = (unsigned char *) endpoint->mac_prefs; + + while(s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if(kex_agree_instr(mac, mac_len, s, method_len)) { + const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + macp); + + if(!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->mac = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while(*macp && (*macp)->name) { + s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name, + strlen((*macp)->name)); + if(s) { + endpoint->mac = *macp; + return 0; + } + macp++; + } + + return -1; +} + + + +/* kex_agree_comp + * Agree on a compression scheme + */ +static int kex_agree_comp(LIBSSH2_SESSION *session, + libssh2_endpoint_data *endpoint, unsigned char *comp, + unsigned long comp_len) +{ + const LIBSSH2_COMP_METHOD **compp = _libssh2_comp_methods(session); + unsigned char *s; + (void) session; + + if(endpoint->comp_prefs) { + s = (unsigned char *) endpoint->comp_prefs; + + while(s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if(kex_agree_instr(comp, comp_len, s, method_len)) { + const LIBSSH2_COMP_METHOD *method = + (const LIBSSH2_COMP_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + compp); + + if(!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->comp = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while(*compp && (*compp)->name) { + s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name, + strlen((*compp)->name)); + if(s) { + endpoint->comp = *compp; + return 0; + } + compp++; + } + + return -1; +} + + +/* TODO: When in server mode we need to turn this logic on its head + * The Client gets to make the final call on "agreed methods" + */ + +/* kex_agree_methods + * Decide which specific method to use of the methods offered by each party + */ +static int kex_agree_methods(LIBSSH2_SESSION * session, unsigned char *data, + unsigned data_len) +{ + unsigned char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc, + *mac_cs, *mac_sc; + size_t kex_len, hostkey_len, crypt_cs_len, crypt_sc_len, comp_cs_len; + size_t comp_sc_len, mac_cs_len, mac_sc_len; + struct string_buf buf; + + if(data_len < 17) + return -1; + + buf.data = (unsigned char *)data; + buf.len = data_len; + buf.dataptr = buf.data; + buf.dataptr++; /* advance past packet type */ + + /* Skip cookie, don't worry, it's preserved in the kexinit field */ + buf.dataptr += 16; + + /* Locate each string */ + if(_libssh2_get_string(&buf, &kex, &kex_len)) + return -1; + if(_libssh2_get_string(&buf, &hostkey, &hostkey_len)) + return -1; + if(_libssh2_get_string(&buf, &crypt_cs, &crypt_cs_len)) + return -1; + if(_libssh2_get_string(&buf, &crypt_sc, &crypt_sc_len)) + return -1; + if(_libssh2_get_string(&buf, &mac_cs, &mac_cs_len)) + return -1; + if(_libssh2_get_string(&buf, &mac_sc, &mac_sc_len)) + return -1; + if(_libssh2_get_string(&buf, &comp_cs, &comp_cs_len)) + return -1; + if(_libssh2_get_string(&buf, &comp_sc, &comp_sc_len)) + return -1; + + /* If the server sent an optimistic packet, assume that it guessed wrong. + * If the guess is determined to be right (by kex_agree_kex_hostkey) + * This flag will be reset to zero so that it's not ignored */ + if(_libssh2_check_length(&buf, 1)) { + session->burn_optimistic_kexinit = *(buf.dataptr++); + } + else { + return -1; + } + + /* Next uint32 in packet is all zeros (reserved) */ + + if(kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) { + return -1; + } + + if(kex_agree_crypt(session, &session->local, crypt_cs, crypt_cs_len) + || kex_agree_crypt(session, &session->remote, crypt_sc, + crypt_sc_len)) { + return -1; + } + + if(kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) || + kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) { + return -1; + } + + if(kex_agree_comp(session, &session->local, comp_cs, comp_cs_len) || + kex_agree_comp(session, &session->remote, comp_sc, comp_sc_len)) { + return -1; + } + +#if 0 + if(libssh2_kex_agree_lang(session, &session->local, lang_cs, lang_cs_len) + || libssh2_kex_agree_lang(session, &session->remote, lang_sc, + lang_sc_len)) { + return -1; + } +#endif + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on KEX method: %s", + session->kex->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on HOSTKEY method: %s", + session->hostkey->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_CS method: %s", + session->local.crypt->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_SC method: %s", + session->remote.crypt->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_CS method: %s", + session->local.mac->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_SC method: %s", + session->remote.mac->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_CS method: %s", + session->local.comp->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_SC method: %s", + session->remote.comp->name); + + return 0; +} + + + +/* _libssh2_kex_exchange + * Exchange keys + * Returns 0 on success, non-zero on failure + * + * Returns some errors without _libssh2_error() + */ +int +_libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, + key_exchange_state_t * key_state) +{ + int rc = 0; + int retcode; + + session->state |= LIBSSH2_STATE_KEX_ACTIVE; + + if(key_state->state == libssh2_NB_state_idle) { + /* Prevent loop in packet_add() */ + session->state |= LIBSSH2_STATE_EXCHANGING_KEYS; + + if(reexchange) { + session->kex = NULL; + + if(session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, + &session->server_hostkey_abstract); + } + session->hostkey = NULL; + } + + key_state->state = libssh2_NB_state_created; + } + + if(!session->kex || !session->hostkey) { + if(key_state->state == libssh2_NB_state_created) { + /* Preserve in case of failure */ + key_state->oldlocal = session->local.kexinit; + key_state->oldlocal_len = session->local.kexinit_len; + + session->local.kexinit = NULL; + + key_state->state = libssh2_NB_state_sent; + } + + if(key_state->state == libssh2_NB_state_sent) { + retcode = kexinit(session); + if(retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } + else if(retcode) { + session->local.kexinit = key_state->oldlocal; + session->local.kexinit_len = key_state->oldlocal_len; + key_state->state = libssh2_NB_state_idle; + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + return -1; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if(key_state->state == libssh2_NB_state_sent1) { + retcode = + _libssh2_packet_require(session, SSH_MSG_KEXINIT, + &key_state->data, + &key_state->data_len, 0, NULL, 0, + &key_state->req_state); + if(retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } + else if(retcode) { + if(session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + session->local.kexinit = key_state->oldlocal; + session->local.kexinit_len = key_state->oldlocal_len; + key_state->state = libssh2_NB_state_idle; + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + return -1; + } + + if(session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + } + session->remote.kexinit = key_state->data; + session->remote.kexinit_len = key_state->data_len; + + if(kex_agree_methods(session, key_state->data, + key_state->data_len)) + rc = LIBSSH2_ERROR_KEX_FAILURE; + + key_state->state = libssh2_NB_state_sent2; + } + } + else { + key_state->state = libssh2_NB_state_sent2; + } + + if(rc == 0 && session->kex) { + if(key_state->state == libssh2_NB_state_sent2) { + retcode = session->kex->exchange_keys(session, + &key_state->key_state_low); + if(retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } + else if(retcode) { + rc = _libssh2_error(session, + LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE, + "Unrecoverable error exchanging keys"); + } + } + } + + /* Done with kexinit buffers */ + if(session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + session->local.kexinit = NULL; + } + if(session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + session->remote.kexinit = NULL; + } + + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + + key_state->state = libssh2_NB_state_idle; + + return rc; +} + + + +/* libssh2_session_method_pref + * Set preferred method + */ +LIBSSH2_API int +libssh2_session_method_pref(LIBSSH2_SESSION * session, int method_type, + const char *prefs) +{ + char **prefvar, *s, *newprefs; + int prefs_len = strlen(prefs); + const LIBSSH2_COMMON_METHOD **mlist; + + switch(method_type) { + case LIBSSH2_METHOD_KEX: + prefvar = &session->kex_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods; + break; + + case LIBSSH2_METHOD_HOSTKEY: + prefvar = &session->hostkey_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_CS: + prefvar = &session->local.crypt_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_SC: + prefvar = &session->remote.crypt_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_MAC_CS: + prefvar = &session->local.mac_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_MAC_SC: + prefvar = &session->remote.mac_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_COMP_CS: + prefvar = &session->local.comp_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) + _libssh2_comp_methods(session); + break; + + case LIBSSH2_METHOD_COMP_SC: + prefvar = &session->remote.comp_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) + _libssh2_comp_methods(session); + break; + + case LIBSSH2_METHOD_LANG_CS: + prefvar = &session->local.lang_prefs; + mlist = NULL; + break; + + case LIBSSH2_METHOD_LANG_SC: + prefvar = &session->remote.lang_prefs; + mlist = NULL; + break; + + case LIBSSH2_METHOD_SIGN_ALGO: + prefvar = &session->sign_algo_prefs; + mlist = NULL; + break; + + default: + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "Invalid parameter specified for method_type"); + } + + s = newprefs = LIBSSH2_ALLOC(session, prefs_len + 1); + if(!newprefs) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Error allocated space for method preferences"); + } + memcpy(s, prefs, prefs_len + 1); + + while(s && *s && mlist) { + char *p = strchr(s, ','); + int method_len = p ? (p - s) : (int) strlen(s); + + if(!kex_get_method_by_name(s, method_len, mlist)) { + /* Strip out unsupported method */ + if(p) { + memcpy(s, p + 1, strlen(s) - method_len); + } + else { + if(s > newprefs) { + *(--s) = '\0'; + } + else { + *s = '\0'; + } + } + } + else { + s = p ? (p + 1) : NULL; + } + } + + if(!*newprefs) { + LIBSSH2_FREE(session, newprefs); + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "The requested method(s) are not currently " + "supported"); + } + + if(*prefvar) { + LIBSSH2_FREE(session, *prefvar); + } + *prefvar = newprefs; + + return 0; +} + +/* + * libssh2_session_supported_algs() + * returns a number of returned algorithms (a positive number) on success, + * a negative number on failure + */ + +LIBSSH2_API int libssh2_session_supported_algs(LIBSSH2_SESSION* session, + int method_type, + const char ***algs) +{ + unsigned int i; + unsigned int j; + unsigned int ialg; + const LIBSSH2_COMMON_METHOD **mlist; + + /* to prevent coredumps due to dereferencing of NULL */ + if(NULL == algs) + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "algs must not be NULL"); + + switch(method_type) { + case LIBSSH2_METHOD_KEX: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods; + break; + + case LIBSSH2_METHOD_HOSTKEY: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_CS: + case LIBSSH2_METHOD_CRYPT_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_MAC_CS: + case LIBSSH2_METHOD_MAC_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_COMP_CS: + case LIBSSH2_METHOD_COMP_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) + _libssh2_comp_methods(session); + break; + + case LIBSSH2_METHOD_SIGN_ALGO: + /* no built-in supported list due to backend support */ + mlist = NULL; + break; + + default: + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unknown method type"); + } /* switch */ + + /* weird situation */ + if(NULL == mlist) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "No algorithm found"); + + /* + mlist is looped through twice. The first time to find the number od + supported algorithms (needed to allocate the proper size of array) and + the second time to actually copy the pointers. Typically this function + will not be called often (typically at the beginning of a session) and + the number of algorithms (i.e. number of iterations in one loop) will + not be high (typically it will not exceed 20) for quite a long time. + + So double looping really shouldn't be an issue and it is definitely a + better solution than reallocation several times. + */ + + /* count the number of supported algorithms */ + for(i = 0, ialg = 0; NULL != mlist[i]; i++) { + /* do not count fields with NULL name */ + if(mlist[i]->name) + ialg++; + } + + /* weird situation, no algorithm found */ + if(0 == ialg) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "No algorithm found"); + + /* allocate buffer */ + *algs = (const char **) LIBSSH2_ALLOC(session, ialg*sizeof(const char *)); + if(NULL == *algs) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Memory allocation failed"); + } + /* Past this point *algs must be deallocated in case of an error!! */ + + /* copy non-NULL pointers only */ + for(i = 0, j = 0; NULL != mlist[i] && j < ialg; i++) { + if(NULL == mlist[i]->name) { + /* maybe a weird situation but if it occurs, do not include NULL + pointers */ + continue; + } + + /* note that [] has higher priority than * (dereferencing) */ + (*algs)[j++] = mlist[i]->name; + } + + /* correct number of pointers copied? (test the code above) */ + if(j != ialg) { + /* deallocate buffer */ + LIBSSH2_FREE(session, (void *)*algs); + *algs = NULL; + + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "Internal error"); + } + + return ialg; +} +#endif diff --git a/lib/libssh2/libgcrypt.h b/lib/libssh2/libgcrypt.h new file mode 100644 index 0000000..32fe004 --- /dev/null +++ b/lib/libssh2/libgcrypt.h @@ -0,0 +1,240 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_LIBGCRYPT_H +#define __LIBSSH2_LIBGCRYPT_H +/* + * Copyright (C) 2008, 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007, The Written Word, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include + +#define LIBSSH2_MD5 1 + +#define LIBSSH2_HMAC_RIPEMD 1 +#define LIBSSH2_HMAC_SHA256 1 +#define LIBSSH2_HMAC_SHA512 1 + +#define LIBSSH2_AES 1 +#define LIBSSH2_AES_CTR 1 +#define LIBSSH2_BLOWFISH 1 +#define LIBSSH2_RC4 1 +#define LIBSSH2_CAST 1 +#define LIBSSH2_3DES 1 + +#define LIBSSH2_RSA 1 +#define LIBSSH2_RSA_SHA2 0 +#define LIBSSH2_DSA 1 +#define LIBSSH2_ECDSA 0 +#define LIBSSH2_ED25519 0 + +#define MD5_DIGEST_LENGTH 16 +#define SHA_DIGEST_LENGTH 20 +#define SHA256_DIGEST_LENGTH 32 +#define SHA384_DIGEST_LENGTH 48 +#define SHA512_DIGEST_LENGTH 64 + +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) + +#define _libssh2_random(buf, len) \ + (gcry_randomize ((buf), (len), GCRY_STRONG_RANDOM), 0) + +#define libssh2_prepare_iovec(vec, len) /* Empty. */ + +#define libssh2_sha1_ctx gcry_md_hd_t + +/* returns 0 in case of failure */ +#define libssh2_sha1_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open(ctx, GCRY_MD_SHA1, 0)) +#define libssh2_sha1_update(ctx, data, len) \ + gcry_md_write(ctx, (unsigned char *) data, len) +#define libssh2_sha1_final(ctx, out) \ + memcpy(out, gcry_md_read(ctx, 0), SHA_DIGEST_LENGTH), gcry_md_close(ctx) +#define libssh2_sha1(message, len, out) \ + gcry_md_hash_buffer(GCRY_MD_SHA1, out, message, len) + +#define libssh2_sha256_ctx gcry_md_hd_t + +#define libssh2_sha256_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open(ctx, GCRY_MD_SHA256, 0)) +#define libssh2_sha256_update(ctx, data, len) \ + gcry_md_write(ctx, (unsigned char *) data, len) +#define libssh2_sha256_final(ctx, out) \ + memcpy(out, gcry_md_read(ctx, 0), SHA256_DIGEST_LENGTH), gcry_md_close(ctx) +#define libssh2_sha256(message, len, out) \ + gcry_md_hash_buffer(GCRY_MD_SHA256, out, message, len) + +#define libssh2_sha384_ctx gcry_md_hd_t + +#define libssh2_sha384_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open(ctx, GCRY_MD_SHA384, 0)) +#define libssh2_sha384_update(ctx, data, len) \ + gcry_md_write(ctx, (unsigned char *) data, len) +#define libssh2_sha384_final(ctx, out) \ + memcpy(out, gcry_md_read(ctx, 0), SHA384_DIGEST_LENGTH), gcry_md_close(ctx) +#define libssh2_sha384(message, len, out) \ + gcry_md_hash_buffer(GCRY_MD_SHA384, out, message, len) + +#define libssh2_sha512_ctx gcry_md_hd_t + +#define libssh2_sha512_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open(ctx, GCRY_MD_SHA512, 0)) +#define libssh2_sha512_update(ctx, data, len) \ + gcry_md_write(ctx, (unsigned char *) data, len) +#define libssh2_sha512_final(ctx, out) \ + memcpy(out, gcry_md_read(ctx, 0), SHA512_DIGEST_LENGTH), gcry_md_close(ctx) +#define libssh2_sha512(message, len, out) \ + gcry_md_hash_buffer(GCRY_MD_SHA512, out, message, len) + +#define libssh2_md5_ctx gcry_md_hd_t + +/* returns 0 in case of failure */ +#define libssh2_md5_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open(ctx, GCRY_MD_MD5, 0)) + +#define libssh2_md5_update(ctx, data, len) \ + gcry_md_write(ctx, (unsigned char *) data, len) +#define libssh2_md5_final(ctx, out) \ + memcpy(out, gcry_md_read(ctx, 0), MD5_DIGEST_LENGTH), gcry_md_close(ctx) +#define libssh2_md5(message, len, out) \ + gcry_md_hash_buffer(GCRY_MD_MD5, out, message, len) + +#define libssh2_hmac_ctx gcry_md_hd_t +#define libssh2_hmac_ctx_init(ctx) +#define libssh2_hmac_sha1_init(ctx, key, keylen) \ + gcry_md_open(ctx, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey(*ctx, key, keylen) +#define libssh2_hmac_md5_init(ctx, key, keylen) \ + gcry_md_open(ctx, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey(*ctx, key, keylen) +#define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ + gcry_md_open(ctx, GCRY_MD_RMD160, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey(*ctx, key, keylen) +#define libssh2_hmac_sha256_init(ctx, key, keylen) \ + gcry_md_open(ctx, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey(*ctx, key, keylen) +#define libssh2_hmac_sha512_init(ctx, key, keylen) \ + gcry_md_open(ctx, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey(*ctx, key, keylen) +#define libssh2_hmac_update(ctx, data, datalen) \ + gcry_md_write(ctx, (unsigned char *) data, datalen) +#define libssh2_hmac_final(ctx, data) \ + memcpy(data, gcry_md_read(ctx, 0), \ + gcry_md_get_algo_dlen(gcry_md_get_algo(ctx))) +#define libssh2_hmac_cleanup(ctx) gcry_md_close (*ctx); + +#define libssh2_crypto_init() gcry_control (GCRYCTL_DISABLE_SECMEM) +#define libssh2_crypto_exit() + +#define libssh2_rsa_ctx struct gcry_sexp + +#define _libssh2_rsa_free(rsactx) gcry_sexp_release (rsactx) + +#define libssh2_dsa_ctx struct gcry_sexp + +#define _libssh2_dsa_free(dsactx) gcry_sexp_release (dsactx) + +#if LIBSSH2_ECDSA +#else +#define _libssh2_ec_key void +#endif + +#define _libssh2_cipher_type(name) int name +#define _libssh2_cipher_ctx gcry_cipher_hd_t + +#define _libssh2_gcry_ciphermode(c,m) ((c << 8) | m) +#define _libssh2_gcry_cipher(c) (c >> 8) +#define _libssh2_gcry_mode(m) (m & 0xFF) + +#define _libssh2_cipher_aes256ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes192ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes128ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes256 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_aes192 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_aes128 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_blowfish \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_arcfour \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM) +#define _libssh2_cipher_cast5 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_3des \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC) + + +#define _libssh2_cipher_dtor(ctx) gcry_cipher_close(*(ctx)) + +#define _libssh2_bn struct gcry_mpi +#define _libssh2_bn_ctx int +#define _libssh2_bn_ctx_new() 0 +#define _libssh2_bn_ctx_free(bnctx) ((void)0) +#define _libssh2_bn_init() gcry_mpi_new(0) +#define _libssh2_bn_init_from_bin() NULL /* because gcry_mpi_scan() creates a + new bignum */ +#define _libssh2_bn_set_word(bn, val) gcry_mpi_set_ui(bn, val) +#define _libssh2_bn_from_bin(bn, len, val) \ + gcry_mpi_scan(&((bn)), GCRYMPI_FMT_USG, val, len, NULL) +#define _libssh2_bn_to_bin(bn, val) \ + gcry_mpi_print(GCRYMPI_FMT_USG, val, _libssh2_bn_bytes(bn), NULL, bn) +#define _libssh2_bn_bytes(bn) \ + (gcry_mpi_get_nbits (bn) / 8 + \ + ((gcry_mpi_get_nbits (bn) % 8 == 0) ? 0 : 1)) +#define _libssh2_bn_bits(bn) gcry_mpi_get_nbits (bn) +#define _libssh2_bn_free(bn) gcry_mpi_release(bn) + +#define _libssh2_dh_ctx struct gcry_mpi * +#define libssh2_dh_init(dhctx) _libssh2_dh_init(dhctx) +#define libssh2_dh_key_pair(dhctx, public, g, p, group_order, bnctx) \ + _libssh2_dh_key_pair(dhctx, public, g, p, group_order) +#define libssh2_dh_secret(dhctx, secret, f, p, bnctx) \ + _libssh2_dh_secret(dhctx, secret, f, p) +#define libssh2_dh_dtor(dhctx) _libssh2_dh_dtor(dhctx) +extern void _libssh2_dh_init(_libssh2_dh_ctx *dhctx); +extern int _libssh2_dh_key_pair(_libssh2_dh_ctx *dhctx, _libssh2_bn *public, + _libssh2_bn *g, _libssh2_bn *p, + int group_order); +extern int _libssh2_dh_secret(_libssh2_dh_ctx *dhctx, _libssh2_bn *secret, + _libssh2_bn *f, _libssh2_bn *p); +extern void _libssh2_dh_dtor(_libssh2_dh_ctx *dhctx); + +#endif /* __LIBSSH2_LIBGCRYPT_H */ +#endif diff --git a/lib/libssh2/libssh2.h b/lib/libssh2/libssh2.h new file mode 100644 index 0000000..f36da35 --- /dev/null +++ b/lib/libssh2/libssh2.h @@ -0,0 +1,1418 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2009, Sara Golemon + * Copyright (c) 2009-2021 Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ +#ifndef LIBSSH2_H +#define LIBSSH2_H 1 + +#define LIBSSH2_COPYRIGHT "2004-2021 The libssh2 project and its contributors." + +/* We use underscore instead of dash when appending DEV in dev versions just + to make the BANNER define (used by src/session.c) be a valid SSH + banner. Release versions have no appended strings and may of course not + have dashes either. */ +#define LIBSSH2_VERSION "1.10.1_DEV" + +/* The numeric version number is also available "in parts" by using these + defines: */ +#define LIBSSH2_VERSION_MAJOR 1 +#define LIBSSH2_VERSION_MINOR 10 +#define LIBSSH2_VERSION_PATCH 1 + +/* This is the numeric version of the libssh2 version number, meant for easier + parsing and comparions by programs. The LIBSSH2_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal (using 8 bits each). All three numbers are always represented + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + appears as "0x090b07". + + This 6-digit (24 bits) hexadecimal number does not show pre-release number, + and it is always a greater number in a more recent release. It makes + comparisons with greater than and less than work. +*/ +#define LIBSSH2_VERSION_NUM 0x010a01 + +/* + * This is the date and time when the full source package was created. The + * timestamp is not stored in the source code repo, as the timestamp is + * properly set in the tarballs by the maketgz script. + * + * The format of the date should follow this template: + * + * "Mon Feb 12 11:35:33 UTC 2007" + */ +#define LIBSSH2_TIMESTAMP "DEV" + +#ifndef RC_INVOKED + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef _WIN32 +# include +# include +#endif + +#include +#include +#include +#include + +/* Allow alternate API prefix from CFLAGS or calling app */ +#ifndef LIBSSH2_API +# ifdef WIN32 +# if defined(_WINDLL) || defined(libssh2_EXPORTS) +# ifdef LIBSSH2_LIBRARY +# define LIBSSH2_API __declspec(dllexport) +# else +# define LIBSSH2_API __declspec(dllimport) +# endif /* LIBSSH2_LIBRARY */ +# else +# define LIBSSH2_API +# endif +# else /* !WIN32 */ +# define LIBSSH2_API +# endif /* WIN32 */ +#endif /* LIBSSH2_API */ + +#ifdef HAVE_SYS_UIO_H +# include +#endif + +#if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) +# include +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef unsigned long long uint64_t; +typedef long long int64_t; +#endif + +#ifdef _MSC_VER +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef unsigned __int64 libssh2_uint64_t; +typedef __int64 libssh2_int64_t; +#if (!defined(HAVE_SSIZE_T) && !defined(ssize_t)) +typedef SSIZE_T ssize_t; +#define HAVE_SSIZE_T +#endif +#else +#include +typedef unsigned long long libssh2_uint64_t; +typedef long long libssh2_int64_t; +#endif + +#ifdef WIN32 +typedef SOCKET libssh2_socket_t; +#define LIBSSH2_INVALID_SOCKET INVALID_SOCKET +#else /* !WIN32 */ +typedef int libssh2_socket_t; +#define LIBSSH2_INVALID_SOCKET -1 +#endif /* WIN32 */ + +/* + * Determine whether there is small or large file support on windows. + */ + +#if defined(_MSC_VER) && !defined(_WIN32_WCE) +# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# define LIBSSH2_USE_WIN32_LARGE_FILES +# else +# define LIBSSH2_USE_WIN32_SMALL_FILES +# endif +#endif + +#if defined(__MINGW32__) && !defined(LIBSSH2_USE_WIN32_LARGE_FILES) +# define LIBSSH2_USE_WIN32_LARGE_FILES +#endif + +#if defined(__WATCOMC__) && !defined(LIBSSH2_USE_WIN32_LARGE_FILES) +# define LIBSSH2_USE_WIN32_LARGE_FILES +#endif + +#if defined(__POCC__) +# undef LIBSSH2_USE_WIN32_LARGE_FILES +#endif + +#if defined(_WIN32) && !defined(LIBSSH2_USE_WIN32_LARGE_FILES) && \ + !defined(LIBSSH2_USE_WIN32_SMALL_FILES) +# define LIBSSH2_USE_WIN32_SMALL_FILES +#endif + +/* + * Large file (>2Gb) support using WIN32 functions. + */ + +#ifdef LIBSSH2_USE_WIN32_LARGE_FILES +# include +# include +# include +# define LIBSSH2_STRUCT_STAT_SIZE_FORMAT "%I64d" +typedef struct _stati64 libssh2_struct_stat; +typedef __int64 libssh2_struct_stat_size; +#endif + +/* + * Small file (<2Gb) support using WIN32 functions. + */ + +#ifdef LIBSSH2_USE_WIN32_SMALL_FILES +# include +# include +# ifndef _WIN32_WCE +# define LIBSSH2_STRUCT_STAT_SIZE_FORMAT "%d" +typedef struct _stat libssh2_struct_stat; +typedef off_t libssh2_struct_stat_size; +# endif +#endif + +#ifndef LIBSSH2_STRUCT_STAT_SIZE_FORMAT +# ifdef __VMS +/* We have to roll our own format here because %z is a C99-ism we don't + have. */ +# if __USE_OFF64_T || __USING_STD_STAT +# define LIBSSH2_STRUCT_STAT_SIZE_FORMAT "%Ld" +# else +# define LIBSSH2_STRUCT_STAT_SIZE_FORMAT "%d" +# endif +# else +# define LIBSSH2_STRUCT_STAT_SIZE_FORMAT "%zd" +# endif +typedef struct stat libssh2_struct_stat; +typedef off_t libssh2_struct_stat_size; +#endif + +/* Part of every banner, user specified or not */ +#define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION + +#define LIBSSH2_SSH_DEFAULT_BANNER LIBSSH2_SSH_BANNER +#define LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF LIBSSH2_SSH_DEFAULT_BANNER "\r\n" + +/* Default generate and safe prime sizes for + diffie-hellman-group-exchange-sha1 */ +#define LIBSSH2_DH_GEX_MINGROUP 2048 +#define LIBSSH2_DH_GEX_OPTGROUP 4096 +#define LIBSSH2_DH_GEX_MAXGROUP 8192 + +#define LIBSSH2_DH_MAX_MODULUS_BITS 16384 + +/* Defaults for pty requests */ +#define LIBSSH2_TERM_WIDTH 80 +#define LIBSSH2_TERM_HEIGHT 24 +#define LIBSSH2_TERM_WIDTH_PX 0 +#define LIBSSH2_TERM_HEIGHT_PX 0 + +/* 1/4 second */ +#define LIBSSH2_SOCKET_POLL_UDELAY 250000 +/* 0.25 * 120 == 30 seconds */ +#define LIBSSH2_SOCKET_POLL_MAXLOOPS 120 + +/* Maximum size to allow a payload to compress to, plays it safe by falling + short of spec limits */ +#define LIBSSH2_PACKET_MAXCOMP 32000 + +/* Maximum size to allow a payload to deccompress to, plays it safe by + allowing more than spec requires */ +#define LIBSSH2_PACKET_MAXDECOMP 40000 + +/* Maximum size for an inbound compressed payload, plays it safe by + overshooting spec limits */ +#define LIBSSH2_PACKET_MAXPAYLOAD 40000 + +/* Malloc callbacks */ +#define LIBSSH2_ALLOC_FUNC(name) void *name(size_t count, void **abstract) +#define LIBSSH2_REALLOC_FUNC(name) void *name(void *ptr, size_t count, \ + void **abstract) +#define LIBSSH2_FREE_FUNC(name) void name(void *ptr, void **abstract) + +typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT +{ + unsigned char *text; + size_t length; + unsigned char echo; +} LIBSSH2_USERAUTH_KBDINT_PROMPT; + +typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE +{ + char *text; + unsigned int length; +} LIBSSH2_USERAUTH_KBDINT_RESPONSE; + +typedef struct _LIBSSH2_SK_SIG_INFO { + uint8_t flags; + uint32_t counter; + unsigned char *sig_r; + size_t sig_r_len; + unsigned char *sig_s; + size_t sig_s_len; +} LIBSSH2_SK_SIG_INFO; + +/* 'publickey' authentication callback */ +#define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \ + int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \ + const unsigned char *data, size_t data_len, void **abstract) + +/* 'keyboard-interactive' authentication callback */ +#define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) \ + void name_(const char *name, int name_len, const char *instruction, \ + int instruction_len, int num_prompts, \ + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, \ + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) + +/* SK authentication callback */ +#define LIBSSH2_USERAUTH_SK_SIGN_FUNC(name) \ +int name(LIBSSH2_SESSION *session, LIBSSH2_SK_SIG_INFO *sig_info, \ +const unsigned char *data, size_t data_len, int algorithm, uint8_t flags, \ +const char *application, const unsigned char *key_handle, size_t handle_len, \ +void **abstract) + +/* Flags for SK authentication */ +#define LIBSSH2_SK_PRESENCE_REQUIRED 0x01 +#define LIBSSH2_SK_VERIFICATION_REQUIRED 0x04 + +/* Callbacks for special SSH packets */ +#define LIBSSH2_IGNORE_FUNC(name) \ + void name(LIBSSH2_SESSION *session, const char *message, int message_len, \ + void **abstract) + +#define LIBSSH2_DEBUG_FUNC(name) \ + void name(LIBSSH2_SESSION *session, int always_display, const char *message, \ + int message_len, const char *language, int language_len, \ + void **abstract) + +#define LIBSSH2_DISCONNECT_FUNC(name) \ + void name(LIBSSH2_SESSION *session, int reason, const char *message, \ + int message_len, const char *language, int language_len, \ + void **abstract) + +#define LIBSSH2_PASSWD_CHANGEREQ_FUNC(name) \ + void name(LIBSSH2_SESSION *session, char **newpw, int *newpw_len, \ + void **abstract) + +#define LIBSSH2_MACERROR_FUNC(name) \ + int name(LIBSSH2_SESSION *session, const char *packet, int packet_len, \ + void **abstract) + +#define LIBSSH2_X11_OPEN_FUNC(name) \ + void name(LIBSSH2_SESSION *session, LIBSSH2_CHANNEL *channel, \ + const char *shost, int sport, void **abstract) + +#define LIBSSH2_CHANNEL_CLOSE_FUNC(name) \ + void name(LIBSSH2_SESSION *session, void **session_abstract, \ + LIBSSH2_CHANNEL *channel, void **channel_abstract) + +/* I/O callbacks */ +#define LIBSSH2_RECV_FUNC(name) \ + ssize_t name(libssh2_socket_t socket, \ + void *buffer, size_t length, \ + int flags, void **abstract) +#define LIBSSH2_SEND_FUNC(name) \ + ssize_t name(libssh2_socket_t socket, \ + const void *buffer, size_t length, \ + int flags, void **abstract) + +/* libssh2_session_callback_set() constants */ +#define LIBSSH2_CALLBACK_IGNORE 0 +#define LIBSSH2_CALLBACK_DEBUG 1 +#define LIBSSH2_CALLBACK_DISCONNECT 2 +#define LIBSSH2_CALLBACK_MACERROR 3 +#define LIBSSH2_CALLBACK_X11 4 +#define LIBSSH2_CALLBACK_SEND 5 +#define LIBSSH2_CALLBACK_RECV 6 + +/* libssh2_session_method_pref() constants */ +#define LIBSSH2_METHOD_KEX 0 +#define LIBSSH2_METHOD_HOSTKEY 1 +#define LIBSSH2_METHOD_CRYPT_CS 2 +#define LIBSSH2_METHOD_CRYPT_SC 3 +#define LIBSSH2_METHOD_MAC_CS 4 +#define LIBSSH2_METHOD_MAC_SC 5 +#define LIBSSH2_METHOD_COMP_CS 6 +#define LIBSSH2_METHOD_COMP_SC 7 +#define LIBSSH2_METHOD_LANG_CS 8 +#define LIBSSH2_METHOD_LANG_SC 9 +#define LIBSSH2_METHOD_SIGN_ALGO 10 + +/* flags */ +#define LIBSSH2_FLAG_SIGPIPE 1 +#define LIBSSH2_FLAG_COMPRESS 2 + +typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; +typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; +typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; +typedef struct _LIBSSH2_KNOWNHOSTS LIBSSH2_KNOWNHOSTS; +typedef struct _LIBSSH2_AGENT LIBSSH2_AGENT; + +/* SK signature callback */ +typedef struct _LIBSSH2_PRIVKEY_SK { + int algorithm; + uint8_t flags; + const char *application; + const unsigned char *key_handle; + size_t handle_len; + LIBSSH2_USERAUTH_SK_SIGN_FUNC((*sign_callback)); + void **orig_abstract; +} LIBSSH2_PRIVKEY_SK; + +int +libssh2_sign_sk(LIBSSH2_SESSION *session, + unsigned char **sig, + size_t *sig_len, + const unsigned char *data, + size_t data_len, + void **abstract); + +typedef struct _LIBSSH2_POLLFD { + unsigned char type; /* LIBSSH2_POLLFD_* below */ + + union { + libssh2_socket_t socket; /* File descriptors -- examined with + system select() call */ + LIBSSH2_CHANNEL *channel; /* Examined by checking internal state */ + LIBSSH2_LISTENER *listener; /* Read polls only -- are inbound + connections waiting to be accepted? */ + } fd; + + unsigned long events; /* Requested Events */ + unsigned long revents; /* Returned Events */ +} LIBSSH2_POLLFD; + +/* Poll FD Descriptor Types */ +#define LIBSSH2_POLLFD_SOCKET 1 +#define LIBSSH2_POLLFD_CHANNEL 2 +#define LIBSSH2_POLLFD_LISTENER 3 + +/* Note: Win32 Doesn't actually have a poll() implementation, so some of these + values are faked with select() data */ +/* Poll FD events/revents -- Match sys/poll.h where possible */ +#define LIBSSH2_POLLFD_POLLIN 0x0001 /* Data available to be read or + connection available -- + All */ +#define LIBSSH2_POLLFD_POLLPRI 0x0002 /* Priority data available to + be read -- Socket only */ +#define LIBSSH2_POLLFD_POLLEXT 0x0002 /* Extended data available to + be read -- Channel only */ +#define LIBSSH2_POLLFD_POLLOUT 0x0004 /* Can may be written -- + Socket/Channel */ +/* revents only */ +#define LIBSSH2_POLLFD_POLLERR 0x0008 /* Error Condition -- Socket */ +#define LIBSSH2_POLLFD_POLLHUP 0x0010 /* HangUp/EOF -- Socket */ +#define LIBSSH2_POLLFD_SESSION_CLOSED 0x0010 /* Session Disconnect */ +#define LIBSSH2_POLLFD_POLLNVAL 0x0020 /* Invalid request -- Socket + Only */ +#define LIBSSH2_POLLFD_POLLEX 0x0040 /* Exception Condition -- + Socket/Win32 */ +#define LIBSSH2_POLLFD_CHANNEL_CLOSED 0x0080 /* Channel Disconnect */ +#define LIBSSH2_POLLFD_LISTENER_CLOSED 0x0080 /* Listener Disconnect */ + +#define HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION +/* Block Direction Types */ +#define LIBSSH2_SESSION_BLOCK_INBOUND 0x0001 +#define LIBSSH2_SESSION_BLOCK_OUTBOUND 0x0002 + +/* Hash Types */ +#define LIBSSH2_HOSTKEY_HASH_MD5 1 +#define LIBSSH2_HOSTKEY_HASH_SHA1 2 +#define LIBSSH2_HOSTKEY_HASH_SHA256 3 + +/* Hostkey Types */ +#define LIBSSH2_HOSTKEY_TYPE_UNKNOWN 0 +#define LIBSSH2_HOSTKEY_TYPE_RSA 1 +#define LIBSSH2_HOSTKEY_TYPE_DSS 2 +#define LIBSSH2_HOSTKEY_TYPE_ECDSA_256 3 +#define LIBSSH2_HOSTKEY_TYPE_ECDSA_384 4 +#define LIBSSH2_HOSTKEY_TYPE_ECDSA_521 5 +#define LIBSSH2_HOSTKEY_TYPE_ED25519 6 + +/* Disconnect Codes (defined by SSH protocol) */ +#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH_DISCONNECT_RESERVED 4 +#define SSH_DISCONNECT_MAC_ERROR 5 +#define SSH_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH_DISCONNECT_CONNECTION_LOST 10 +#define SSH_DISCONNECT_BY_APPLICATION 11 +#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 + +/* Error Codes (defined by libssh2) */ +#define LIBSSH2_ERROR_NONE 0 + +/* The library once used -1 as a generic error return value on numerous places + through the code, which subsequently was converted to + LIBSSH2_ERROR_SOCKET_NONE uses over time. As this is a generic error code, + the goal is to never ever return this code but instead make sure that a + more accurate and descriptive error code is used. */ +#define LIBSSH2_ERROR_SOCKET_NONE -1 + +#define LIBSSH2_ERROR_BANNER_RECV -2 +#define LIBSSH2_ERROR_BANNER_SEND -3 +#define LIBSSH2_ERROR_INVALID_MAC -4 +#define LIBSSH2_ERROR_KEX_FAILURE -5 +#define LIBSSH2_ERROR_ALLOC -6 +#define LIBSSH2_ERROR_SOCKET_SEND -7 +#define LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE -8 +#define LIBSSH2_ERROR_TIMEOUT -9 +#define LIBSSH2_ERROR_HOSTKEY_INIT -10 +#define LIBSSH2_ERROR_HOSTKEY_SIGN -11 +#define LIBSSH2_ERROR_DECRYPT -12 +#define LIBSSH2_ERROR_SOCKET_DISCONNECT -13 +#define LIBSSH2_ERROR_PROTO -14 +#define LIBSSH2_ERROR_PASSWORD_EXPIRED -15 +#define LIBSSH2_ERROR_FILE -16 +#define LIBSSH2_ERROR_METHOD_NONE -17 +#define LIBSSH2_ERROR_AUTHENTICATION_FAILED -18 +#define LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED \ + LIBSSH2_ERROR_AUTHENTICATION_FAILED +#define LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED -19 +#define LIBSSH2_ERROR_CHANNEL_OUTOFORDER -20 +#define LIBSSH2_ERROR_CHANNEL_FAILURE -21 +#define LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED -22 +#define LIBSSH2_ERROR_CHANNEL_UNKNOWN -23 +#define LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED -24 +#define LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED -25 +#define LIBSSH2_ERROR_CHANNEL_CLOSED -26 +#define LIBSSH2_ERROR_CHANNEL_EOF_SENT -27 +#define LIBSSH2_ERROR_SCP_PROTOCOL -28 +#define LIBSSH2_ERROR_ZLIB -29 +#define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 +#define LIBSSH2_ERROR_SFTP_PROTOCOL -31 +#define LIBSSH2_ERROR_REQUEST_DENIED -32 +#define LIBSSH2_ERROR_METHOD_NOT_SUPPORTED -33 +#define LIBSSH2_ERROR_INVAL -34 +#define LIBSSH2_ERROR_INVALID_POLL_TYPE -35 +#define LIBSSH2_ERROR_PUBLICKEY_PROTOCOL -36 +#define LIBSSH2_ERROR_EAGAIN -37 +#define LIBSSH2_ERROR_BUFFER_TOO_SMALL -38 +#define LIBSSH2_ERROR_BAD_USE -39 +#define LIBSSH2_ERROR_COMPRESS -40 +#define LIBSSH2_ERROR_OUT_OF_BOUNDARY -41 +#define LIBSSH2_ERROR_AGENT_PROTOCOL -42 +#define LIBSSH2_ERROR_SOCKET_RECV -43 +#define LIBSSH2_ERROR_ENCRYPT -44 +#define LIBSSH2_ERROR_BAD_SOCKET -45 +#define LIBSSH2_ERROR_KNOWN_HOSTS -46 +#define LIBSSH2_ERROR_CHANNEL_WINDOW_FULL -47 +#define LIBSSH2_ERROR_KEYFILE_AUTH_FAILED -48 +#define LIBSSH2_ERROR_RANDGEN -49 +#define LIBSSH2_ERROR_MISSING_USERAUTH_BANNER -50 +#define LIBSSH2_ERROR_ALGO_UNSUPPORTED -51 + +/* this is a define to provide the old (<= 1.2.7) name */ +#define LIBSSH2_ERROR_BANNER_NONE LIBSSH2_ERROR_BANNER_RECV + +/* Global API */ +#define LIBSSH2_INIT_NO_CRYPTO 0x0001 + +/* + * libssh2_init() + * + * Initialize the libssh2 functions. This typically initialize the + * crypto library. It uses a global state, and is not thread safe -- + * you must make sure this function is not called concurrently. + * + * Flags can be: + * 0: Normal initialize + * LIBSSH2_INIT_NO_CRYPTO: Do not initialize the crypto library (ie. + * OPENSSL_add_cipher_algoritms() for OpenSSL + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int libssh2_init(int flags); + +/* + * libssh2_exit() + * + * Exit the libssh2 functions and free's all memory used internal. + */ +LIBSSH2_API void libssh2_exit(void); + +/* + * libssh2_free() + * + * Deallocate memory allocated by earlier call to libssh2 functions. + */ +LIBSSH2_API void libssh2_free(LIBSSH2_SESSION *session, void *ptr); + +/* + * libssh2_session_supported_algs() + * + * Fills algs with a list of supported acryptographic algorithms. Returns a + * non-negative number (number of supported algorithms) on success or a + * negative number (an error code) on failure. + * + * NOTE: on success, algs must be deallocated (by calling libssh2_free) when + * not needed anymore + */ +LIBSSH2_API int libssh2_session_supported_algs(LIBSSH2_SESSION* session, + int method_type, + const char ***algs); + +/* Session API */ +LIBSSH2_API LIBSSH2_SESSION * +libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), + LIBSSH2_FREE_FUNC((*my_free)), + LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); +#define libssh2_session_init() libssh2_session_init_ex(NULL, NULL, NULL, NULL) + +LIBSSH2_API void **libssh2_session_abstract(LIBSSH2_SESSION *session); + +LIBSSH2_API void *libssh2_session_callback_set(LIBSSH2_SESSION *session, + int cbtype, void *callback); +LIBSSH2_API int libssh2_session_banner_set(LIBSSH2_SESSION *session, + const char *banner); +LIBSSH2_API int libssh2_banner_set(LIBSSH2_SESSION *session, + const char *banner); + +LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int sock); +LIBSSH2_API int libssh2_session_handshake(LIBSSH2_SESSION *session, + libssh2_socket_t sock); +LIBSSH2_API int libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, + int reason, + const char *description, + const char *lang); +#define libssh2_session_disconnect(session, description) \ + libssh2_session_disconnect_ex((session), SSH_DISCONNECT_BY_APPLICATION, \ + (description), "") + +LIBSSH2_API int libssh2_session_free(LIBSSH2_SESSION *session); + +LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, + int hash_type); + +LIBSSH2_API const char *libssh2_session_hostkey(LIBSSH2_SESSION *session, + size_t *len, int *type); + +LIBSSH2_API int libssh2_session_method_pref(LIBSSH2_SESSION *session, + int method_type, + const char *prefs); +LIBSSH2_API const char *libssh2_session_methods(LIBSSH2_SESSION *session, + int method_type); +LIBSSH2_API int libssh2_session_last_error(LIBSSH2_SESSION *session, + char **errmsg, + int *errmsg_len, int want_buf); +LIBSSH2_API int libssh2_session_last_errno(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_session_set_last_error(LIBSSH2_SESSION* session, + int errcode, + const char *errmsg); +LIBSSH2_API int libssh2_session_block_directions(LIBSSH2_SESSION *session); + +LIBSSH2_API int libssh2_session_flag(LIBSSH2_SESSION *session, int flag, + int value); +LIBSSH2_API const char *libssh2_session_banner_get(LIBSSH2_SESSION *session); + +/* Userauth API */ +LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len); +LIBSSH2_API int libssh2_userauth_banner(LIBSSH2_SESSION *session, + char **banner); +LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session); + +LIBSSH2_API int +libssh2_userauth_password_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *password, + unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC + ((*passwd_change_cb))); + +#define libssh2_userauth_password(session, username, password) \ + libssh2_userauth_password_ex((session), (username), \ + (unsigned int)strlen(username), \ + (password), (unsigned int)strlen(password), NULL) + +LIBSSH2_API int +libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickey, + const char *privatekey, + const char *passphrase); + +#define libssh2_userauth_publickey_fromfile(session, username, publickey, \ + privatekey, passphrase) \ + libssh2_userauth_publickey_fromfile_ex((session), (username), \ + (unsigned int)strlen(username), \ + (publickey), \ + (privatekey), (passphrase)) + +LIBSSH2_API int +libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + const unsigned char *pubkeydata, + size_t pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC + ((*sign_callback)), + void **abstract); + +LIBSSH2_API int +libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickey, + const char *privatekey, + const char *passphrase, + const char *hostname, + unsigned int hostname_len, + const char *local_username, + unsigned int local_username_len); + +#define libssh2_userauth_hostbased_fromfile(session, username, publickey, \ + privatekey, passphrase, hostname) \ + libssh2_userauth_hostbased_fromfile_ex((session), (username), \ + (unsigned int)strlen(username), \ + (publickey), \ + (privatekey), (passphrase), \ + (hostname), \ + (unsigned int)strlen(hostname), \ + (username), \ + (unsigned int)strlen(username)) + +LIBSSH2_API int +libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickeyfiledata, + size_t publickeyfiledata_len, + const char *privatekeyfiledata, + size_t privatekeyfiledata_len, + const char *passphrase); + +/* + * response_callback is provided with filled by library prompts array, + * but client must allocate and fill individual responses. Responses + * array is already allocated. Responses data will be freed by libssh2 + * after callback return, but before subsequent callback invocation. + */ +LIBSSH2_API int +libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, + const char *username, + unsigned int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC( + (*response_callback))); + +#define libssh2_userauth_keyboard_interactive(session, username, \ + response_callback) \ + libssh2_userauth_keyboard_interactive_ex((session), (username), \ + (unsigned int)strlen(username), \ + (response_callback)) + +LIBSSH2_API int +libssh2_userauth_publickey_sk(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase, + LIBSSH2_USERAUTH_SK_SIGN_FUNC + ((*sign_callback)), + void **abstract); + +LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, + long timeout); + +/* Channel API */ +#define LIBSSH2_CHANNEL_WINDOW_DEFAULT (2*1024*1024) +#define LIBSSH2_CHANNEL_PACKET_DEFAULT 32768 +#define LIBSSH2_CHANNEL_MINADJUST 1024 + +/* Extended Data Handling */ +#define LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL 0 +#define LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE 1 +#define LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE 2 + +#define SSH_EXTENDED_DATA_STDERR 1 + +/* Returned by any function that would block during a read/write operation */ +#define LIBSSH2CHANNEL_EAGAIN LIBSSH2_ERROR_EAGAIN + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *channel_type, + unsigned int channel_type_len, + unsigned int window_size, unsigned int packet_size, + const char *message, unsigned int message_len); + +#define libssh2_channel_open_session(session) \ + libssh2_channel_open_ex((session), "session", sizeof("session") - 1, \ + LIBSSH2_CHANNEL_WINDOW_DEFAULT, \ + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0) + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, const char *host, + int port, const char *shost, int sport); +#define libssh2_channel_direct_tcpip(session, host, port) \ + libssh2_channel_direct_tcpip_ex((session), (host), (port), "127.0.0.1", 22) + +LIBSSH2_API LIBSSH2_LISTENER * +libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, const char *host, + int port, int *bound_port, + int queue_maxsize); +#define libssh2_channel_forward_listen(session, port) \ + libssh2_channel_forward_listen_ex((session), NULL, (port), NULL, 16) + +LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener); + +LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, + const char *varname, + unsigned int varname_len, + const char *value, + unsigned int value_len); + +#define libssh2_channel_setenv(channel, varname, value) \ + libssh2_channel_setenv_ex((channel), (varname), \ + (unsigned int)strlen(varname), (value), \ + (unsigned int)strlen(value)) + +LIBSSH2_API int libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel); + +LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, + const char *term, + unsigned int term_len, + const char *modes, + unsigned int modes_len, + int width, int height, + int width_px, int height_px); +#define libssh2_channel_request_pty(channel, term) \ + libssh2_channel_request_pty_ex((channel), (term), \ + (unsigned int)strlen(term), \ + NULL, 0, \ + LIBSSH2_TERM_WIDTH, \ + LIBSSH2_TERM_HEIGHT, \ + LIBSSH2_TERM_WIDTH_PX, \ + LIBSSH2_TERM_HEIGHT_PX) + +LIBSSH2_API int libssh2_channel_request_pty_size_ex(LIBSSH2_CHANNEL *channel, + int width, int height, + int width_px, + int height_px); +#define libssh2_channel_request_pty_size(channel, width, height) \ + libssh2_channel_request_pty_size_ex((channel), (width), (height), 0, 0) + +LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, + int single_connection, + const char *auth_proto, + const char *auth_cookie, + int screen_number); +#define libssh2_channel_x11_req(channel, screen_number) \ + libssh2_channel_x11_req_ex((channel), 0, NULL, NULL, (screen_number)) + +LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, + unsigned int request_len, + const char *message, + unsigned int message_len); +#define libssh2_channel_shell(channel) \ + libssh2_channel_process_startup((channel), "shell", sizeof("shell") - 1, \ + NULL, 0) +#define libssh2_channel_exec(channel, command) \ + libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, \ + (command), (unsigned int)strlen(command)) +#define libssh2_channel_subsystem(channel, subsystem) \ + libssh2_channel_process_startup((channel), "subsystem", \ + sizeof("subsystem") - 1, (subsystem), \ + (unsigned int)strlen(subsystem)) + +LIBSSH2_API ssize_t libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, + int stream_id, char *buf, + size_t buflen); +#define libssh2_channel_read(channel, buf, buflen) \ + libssh2_channel_read_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_read_stderr(channel, buf, buflen) \ + libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + +LIBSSH2_API int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, + int extended); + +LIBSSH2_API unsigned long +libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, + unsigned long *read_avail, + unsigned long *window_size_initial); +#define libssh2_channel_window_read(channel) \ + libssh2_channel_window_read_ex((channel), NULL, NULL) + +/* libssh2_channel_receive_window_adjust is DEPRECATED, do not use! */ +LIBSSH2_API unsigned long +libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, + unsigned long adjustment, + unsigned char force); + +LIBSSH2_API int +libssh2_channel_receive_window_adjust2(LIBSSH2_CHANNEL *channel, + unsigned long adjustment, + unsigned char force, + unsigned int *storewindow); + +LIBSSH2_API ssize_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, + int stream_id, const char *buf, + size_t buflen); + +#define libssh2_channel_write(channel, buf, buflen) \ + libssh2_channel_write_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_write_stderr(channel, buf, buflen) \ + libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, \ + (buf), (buflen)) + +LIBSSH2_API unsigned long +libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, + unsigned long *window_size_initial); +#define libssh2_channel_window_write(channel) \ + libssh2_channel_window_write_ex((channel), NULL) + +LIBSSH2_API void libssh2_session_set_blocking(LIBSSH2_SESSION* session, + int blocking); +LIBSSH2_API int libssh2_session_get_blocking(LIBSSH2_SESSION* session); + +LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, + int blocking); + +LIBSSH2_API void libssh2_session_set_timeout(LIBSSH2_SESSION* session, + long timeout); +LIBSSH2_API long libssh2_session_get_timeout(LIBSSH2_SESSION* session); + +/* libssh2_channel_handle_extended_data is DEPRECATED, do not use! */ +LIBSSH2_API void libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, + int ignore_mode); +LIBSSH2_API int libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, + int ignore_mode); + +/* libssh2_channel_ignore_extended_data() is defined below for BC with version + * 0.1 + * + * Future uses should use libssh2_channel_handle_extended_data() directly if + * LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE is passed, extended data will be read + * (FIFO) from the standard data channel + */ +/* DEPRECATED */ +#define libssh2_channel_ignore_extended_data(channel, ignore) \ + libssh2_channel_handle_extended_data((channel), \ + (ignore) ? \ + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE : \ + LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL) + +#define LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA -1 +#define LIBSSH2_CHANNEL_FLUSH_ALL -2 +LIBSSH2_API int libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, + int streamid); +#define libssh2_channel_flush(channel) libssh2_channel_flush_ex((channel), 0) +#define libssh2_channel_flush_stderr(channel) \ + libssh2_channel_flush_ex((channel), SSH_EXTENDED_DATA_STDERR) + +LIBSSH2_API int libssh2_channel_get_exit_status(LIBSSH2_CHANNEL* channel); +LIBSSH2_API int libssh2_channel_get_exit_signal(LIBSSH2_CHANNEL* channel, + char **exitsignal, + size_t *exitsignal_len, + char **errmsg, + size_t *errmsg_len, + char **langtag, + size_t *langtag_len); +LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel); + +/* libssh2_scp_recv is DEPRECATED, do not use! */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, + const char *path, + struct stat *sb); +/* Use libssh2_scp_recv2 for large (> 2GB) file support on windows */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv2(LIBSSH2_SESSION *session, + const char *path, + libssh2_struct_stat *sb); +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, + const char *path, int mode, + size_t size, long mtime, + long atime); +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send64(LIBSSH2_SESSION *session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime); + +#define libssh2_scp_send(session, path, mode, size) \ + libssh2_scp_send_ex((session), (path), (mode), (size), 0, 0) + +LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **dest, + unsigned int *dest_len, + const char *src, unsigned int src_len); + +LIBSSH2_API +const char *libssh2_version(int req_version_num); + +typedef enum { + libssh2_no_crypto = 0, + libssh2_openssl, + libssh2_gcrypt, + libssh2_mbedtls, + libssh2_wincng +} libssh2_crypto_engine_t; + +LIBSSH2_API +libssh2_crypto_engine_t libssh2_crypto_engine(void); + +#define HAVE_LIBSSH2_KNOWNHOST_API 0x010101 /* since 1.1.1 */ +#define HAVE_LIBSSH2_VERSION_API 0x010100 /* libssh2_version since 1.1 */ +#define HAVE_LIBSSH2_CRYPTOENGINE_API 0x011100 /* libssh2_crypto_engine + since 1.11 */ + +struct libssh2_knownhost { + unsigned int magic; /* magic stored by the library */ + void *node; /* handle to the internal representation of this host */ + char *name; /* this is NULL if no plain text host name exists */ + char *key; /* key in base64/printable format */ + int typemask; +}; + +/* + * libssh2_knownhost_init + * + * Init a collection of known hosts. Returns the pointer to a collection. + * + */ +LIBSSH2_API LIBSSH2_KNOWNHOSTS * +libssh2_knownhost_init(LIBSSH2_SESSION *session); + +/* + * libssh2_knownhost_add + * + * Add a host and its associated key to the collection of known hosts. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +/* host format (2 bits) */ +#define LIBSSH2_KNOWNHOST_TYPE_MASK 0xffff +#define LIBSSH2_KNOWNHOST_TYPE_PLAIN 1 +#define LIBSSH2_KNOWNHOST_TYPE_SHA1 2 /* always base64 encoded */ +#define LIBSSH2_KNOWNHOST_TYPE_CUSTOM 3 + +/* key format (2 bits) */ +#define LIBSSH2_KNOWNHOST_KEYENC_MASK (3<<16) +#define LIBSSH2_KNOWNHOST_KEYENC_RAW (1<<16) +#define LIBSSH2_KNOWNHOST_KEYENC_BASE64 (2<<16) + +/* type of key (4 bits) */ +#define LIBSSH2_KNOWNHOST_KEY_MASK (15<<18) +#define LIBSSH2_KNOWNHOST_KEY_SHIFT 18 +#define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHRSA (2<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHDSS (3<<18) +#define LIBSSH2_KNOWNHOST_KEY_ECDSA_256 (4<<18) +#define LIBSSH2_KNOWNHOST_KEY_ECDSA_384 (5<<18) +#define LIBSSH2_KNOWNHOST_KEY_ECDSA_521 (6<<18) +#define LIBSSH2_KNOWNHOST_KEY_ED25519 (7<<18) +#define LIBSSH2_KNOWNHOST_KEY_UNKNOWN (15<<18) + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, + const char *salt, + const char *key, size_t keylen, int typemask, + struct libssh2_knownhost **store); + +/* + * libssh2_knownhost_addc + * + * Add a host and its associated key to the collection of known hosts. + * + * Takes a comment argument that may be NULL. A NULL comment indicates + * there is no comment and the entry will end directly after the key + * when written out to a file. An empty string "" comment will indicate an + * empty comment which will cause a single space to be written after the key. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +LIBSSH2_API int +libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, + const char *salt, + const char *key, size_t keylen, + const char *comment, size_t commentlen, int typemask, + struct libssh2_knownhost **store); + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The type is the type/format of the given host name. + * + * plain - ascii "hostname.domain.tld" + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * + * 'knownhost' may be set to NULL if you don't care about that info. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_* values, see below + * + */ + +#define LIBSSH2_KNOWNHOST_CHECK_MATCH 0 +#define LIBSSH2_KNOWNHOST_CHECK_MISMATCH 1 +#define LIBSSH2_KNOWNHOST_CHECK_NOTFOUND 2 +#define LIBSSH2_KNOWNHOST_CHECK_FAILURE 3 + +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **knownhost); + +/* this function is identital to the above one, but also takes a port + argument that allows libssh2 to do a better check */ +LIBSSH2_API int +libssh2_knownhost_checkp(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, int port, + const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **knownhost); + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. The 'entry' struct is + * retrieved by a call to libssh2_knownhost_check(). + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry); + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts); + +/* + * libssh2_knownhost_readline() + * + * Pass in a line of a file of 'type'. It makes libssh2 read this line. + * + * LIBSSH2_KNOWNHOST_FILE_OPENSSH is the only supported type. + * + */ +LIBSSH2_API int +libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS *hosts, + const char *line, size_t len, int type); + +/* + * libssh2_knownhost_readfile + * + * Add hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + */ + +#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 + +LIBSSH2_API int +libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type); + +/* + * libssh2_knownhost_writeline() + * + * Ask libssh2 to convert a known host to an output line for storage. + * + * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given + * output buffer is too small to hold the desired output. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + * + */ +LIBSSH2_API int +libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *known, + char *buffer, size_t buflen, + size_t *outlen, /* the amount of written data */ + int type); + +/* + * libssh2_knownhost_writefile + * + * Write hosts+key pairs to a given file. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + */ + +LIBSSH2_API int +libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type); + +/* + * libssh2_knownhost_get() + * + * Traverse the internal list of known hosts. Pass NULL to 'prev' to get + * the first one. Or pass a pointer to the previously returned one to get the + * next. + * + * Returns: + * 0 if a fine host was stored in 'store' + * 1 if end of hosts + * [negative] on errors + */ +LIBSSH2_API int +libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost **store, + struct libssh2_knownhost *prev); + +#define HAVE_LIBSSH2_AGENT_API 0x010202 /* since 1.2.2 */ + +struct libssh2_agent_publickey { + unsigned int magic; /* magic stored by the library */ + void *node; /* handle to the internal representation of key */ + unsigned char *blob; /* public key blob */ + size_t blob_len; /* length of the public key blob */ + char *comment; /* comment in printable format */ +}; + +/* + * libssh2_agent_init + * + * Init an ssh-agent handle. Returns the pointer to the handle. + * + */ +LIBSSH2_API LIBSSH2_AGENT * +libssh2_agent_init(LIBSSH2_SESSION *session); + +/* + * libssh2_agent_connect() + * + * Connect to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_connect(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_list_identities() + * + * Request an ssh-agent to list identities. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_list_identities(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_get_identity() + * + * Traverse the internal list of public keys. Pass NULL to 'prev' to get + * the first one. Or pass a pointer to the previously returned one to get the + * next. + * + * Returns: + * 0 if a fine public key was stored in 'store' + * 1 if end of public keys + * [negative] on errors + */ +LIBSSH2_API int +libssh2_agent_get_identity(LIBSSH2_AGENT *agent, + struct libssh2_agent_publickey **store, + struct libssh2_agent_publickey *prev); + +/* + * libssh2_agent_userauth() + * + * Do publickey user authentication with the help of ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_userauth(LIBSSH2_AGENT *agent, + const char *username, + struct libssh2_agent_publickey *identity); + +/* + * libssh2_agent_disconnect() + * + * Close a connection to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_disconnect(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_free() + * + * Free an ssh-agent handle. This function also frees the internal + * collection of public keys. + */ +LIBSSH2_API void +libssh2_agent_free(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_set_identity_path() + * + * Allows a custom agent identity socket path beyond SSH_AUTH_SOCK env + * + */ +LIBSSH2_API void +libssh2_agent_set_identity_path(LIBSSH2_AGENT *agent, + const char *path); + +/* + * libssh2_agent_get_identity_path() + * + * Returns the custom agent identity socket path if set + * + */ +LIBSSH2_API const char * +libssh2_agent_get_identity_path(LIBSSH2_AGENT *agent); + +/* + * libssh2_keepalive_config() + * + * Set how often keepalive messages should be sent. WANT_REPLY + * indicates whether the keepalive messages should request a response + * from the server. INTERVAL is number of seconds that can pass + * without any I/O, use 0 (the default) to disable keepalives. To + * avoid some busy-loop corner-cases, if you specify an interval of 1 + * it will be treated as 2. + * + * Note that non-blocking applications are responsible for sending the + * keepalive messages using libssh2_keepalive_send(). + */ +LIBSSH2_API void libssh2_keepalive_config(LIBSSH2_SESSION *session, + int want_reply, + unsigned interval); + +/* + * libssh2_keepalive_send() + * + * Send a keepalive message if needed. SECONDS_TO_NEXT indicates how + * many seconds you can sleep after this call before you need to call + * it again. Returns 0 on success, or LIBSSH2_ERROR_SOCKET_SEND on + * I/O errors. + */ +LIBSSH2_API int libssh2_keepalive_send(LIBSSH2_SESSION *session, + int *seconds_to_next); + +/* NOTE NOTE NOTE + libssh2_trace() has no function in builds that aren't built with debug + enabled + */ +LIBSSH2_API int libssh2_trace(LIBSSH2_SESSION *session, int bitmask); +#define LIBSSH2_TRACE_TRANS (1<<1) +#define LIBSSH2_TRACE_KEX (1<<2) +#define LIBSSH2_TRACE_AUTH (1<<3) +#define LIBSSH2_TRACE_CONN (1<<4) +#define LIBSSH2_TRACE_SCP (1<<5) +#define LIBSSH2_TRACE_SFTP (1<<6) +#define LIBSSH2_TRACE_ERROR (1<<7) +#define LIBSSH2_TRACE_PUBLICKEY (1<<8) +#define LIBSSH2_TRACE_SOCKET (1<<9) + +typedef void (*libssh2_trace_handler_func)(LIBSSH2_SESSION*, + void *, + const char *, + size_t); +LIBSSH2_API int libssh2_trace_sethandler(LIBSSH2_SESSION *session, + void *context, + libssh2_trace_handler_func callback); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !RC_INVOKED */ + +#endif /* LIBSSH2_H */ +#endif diff --git a/lib/libssh2/libssh2_config.h b/lib/libssh2/libssh2_config.h new file mode 100644 index 0000000..1f4faab --- /dev/null +++ b/lib/libssh2/libssh2_config.h @@ -0,0 +1,75 @@ +#if defined(ESP32) +/* Copyright (c) 2014 Alexander Lamaison + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* Headers */ +#define LIBSSH2_MBEDTLS +#define HAVE_O_NONBLOCK +#define HAVE_UNISTD_H +#define HAVE_INTTYPES_H +#define HAVE_STDLIB_H +#define HAVE_SYS_SELECT_H +#define HAVE_SYS_SOCKET_H +#define HAVE_SYS_TIME_H +#define HAVE_ARPA_INET_H +#define HAVE_NETINET_IN_H + +/* Functions */ +#define HAVE_STRCASECMP +#define HAVE__STRICMP +#define HAVE_SNPRINTF +#define HAVE__SNPRINTF + +/* Workaround for platforms without POSIX strcasecmp (e.g. Windows) */ +#ifndef HAVE_STRCASECMP +# ifdef HAVE__STRICMP +# define strcasecmp _stricmp +# define HAVE_STRCASECMP +# endif +#endif + +/* Symbols */ +#define HAVE___FUNC__ +#define HAVE___FUNCTION__ + +/* Workaround for platforms without C90 __func__ */ +#ifndef HAVE___FUNC__ +# ifdef HAVE___FUNCTION__ +# define __func__ __FUNCTION__ +# define HAVE___FUNC__ +# endif +#endif +#endif diff --git a/lib/libssh2/libssh2_priv.h b/lib/libssh2/libssh2_priv.h new file mode 100644 index 0000000..b3030b3 --- /dev/null +++ b/lib/libssh2/libssh2_priv.h @@ -0,0 +1,1172 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_PRIV_H +#define __LIBSSH2_PRIV_H +/* Copyright (c) 2004-2008, 2010, Sara Golemon + * Copyright (c) 2009-2014 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#define LIBSSH2_LIBRARY +#include "libssh2_config.h" + +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#undef WIN32_LEAN_AND_MEAN + +/* Detect Windows App environment which has a restricted access + to the Win32 APIs. */ +# if (defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0602)) || \ + defined(WINAPI_FAMILY) +# include +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ + !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define LIBSSH2_WINDOWS_APP +# endif +# endif + +/* TODO: Enable this unconditionally for all platforms. + Also delete autotools logic that enables it only for mbedTLS. + And CMake logic which already enabled it unconditionally. + The actual memory clearing logic uses SecureZeroMemory(), + memset_s() or plain memset(), whichever is available, and + does not depend on any crypto backend function. */ +#ifndef LIBSSH2_CLEAR_MEMORY +#define LIBSSH2_CLEAR_MEMORY +#endif + +#endif + +#ifdef HAVE_WS2TCPIP_H +#include +#endif + +#include +#include + +/* The following CPP block should really only be in session.c and packet.c. + However, AIX have #define's for 'events' and 'revents' and we are using + those names in libssh2.h, so we need to include the AIX headers first, to + make sure all code is compiled with consistent names of these fields. + While arguable the best would to change libssh2.h to use other names, that + would break backwards compatibility. +*/ +#ifdef HAVE_POLL +# include +#else +# if defined(HAVE_SELECT) && !defined(WIN32) +# ifdef HAVE_SYS_SELECT_H +# include +# else +# include +# include +# endif +# endif +#endif + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif + +#include "libssh2.h" +#include "libssh2_publickey.h" +#include "libssh2_sftp.h" +#include "misc.h" /* for the linked list stuff */ + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#ifdef _MSC_VER +/* "inline" keyword is valid only with C++ engine! */ +#define inline __inline +#endif + +/* 3DS doesn't seem to have iovec */ +#if defined(WIN32) || defined(_3DS) + +struct iovec { + size_t iov_len; + void *iov_base; +}; + +#endif + +#ifdef __OS400__ +/* Force parameter type. */ +#define send(s, b, l, f) send((s), (unsigned char *) (b), (l), (f)) +#endif + +#include "crypto.h" + +#ifdef HAVE_WINSOCK2_H + +#include +#include + +#endif + +#ifndef SIZE_MAX +#if _WIN64 +#define SIZE_MAX 0xFFFFFFFFFFFFFFFF +#else +#define SIZE_MAX 0xFFFFFFFF +#endif +#endif + +#ifndef UINT_MAX +#define UINT_MAX 0xFFFFFFFF +#endif + +/* RFC4253 section 6.1 Maximum Packet Length says: + * + * "All implementations MUST be able to process packets with + * uncompressed payload length of 32768 bytes or less and + * total packet size of 35000 bytes or less (including length, + * padding length, payload, padding, and MAC.)." + */ +#define MAX_SSH_PACKET_LEN 35000 +#define MAX_SHA_DIGEST_LEN SHA512_DIGEST_LENGTH + +#define LIBSSH2_ALLOC(session, count) \ + session->alloc((count), &(session)->abstract) +#define LIBSSH2_CALLOC(session, count) _libssh2_calloc(session, count) +#define LIBSSH2_REALLOC(session, ptr, count) \ + ((ptr) ? session->realloc((ptr), (count), &(session)->abstract) : \ + session->alloc((count), &(session)->abstract)) +#define LIBSSH2_FREE(session, ptr) \ + session->free((ptr), &(session)->abstract) +#define LIBSSH2_IGNORE(session, data, datalen) \ + session->ssh_msg_ignore((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_DEBUG(session, always_display, message, message_len, \ + language, language_len) \ + session->ssh_msg_debug((session), (always_display), (message), \ + (message_len), (language), (language_len), \ + &(session)->abstract) +#define LIBSSH2_DISCONNECT(session, reason, message, message_len, \ + language, language_len) \ + session->ssh_msg_disconnect((session), (reason), (message), \ + (message_len), (language), (language_len), \ + &(session)->abstract) + +#define LIBSSH2_MACERROR(session, data, datalen) \ + session->macerror((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_X11_OPEN(channel, shost, sport) \ + channel->session->x11(((channel)->session), (channel), \ + (shost), (sport), (&(channel)->session->abstract)) + +#define LIBSSH2_CHANNEL_CLOSE(session, channel) \ + channel->close_cb((session), &(session)->abstract, \ + (channel), &(channel)->abstract) + +#define LIBSSH2_SEND_FD(session, fd, buffer, length, flags) \ + (session->send)(fd, buffer, length, flags, &session->abstract) +#define LIBSSH2_RECV_FD(session, fd, buffer, length, flags) \ + (session->recv)(fd, buffer, length, flags, &session->abstract) + +#define LIBSSH2_SEND(session, buffer, length, flags) \ + LIBSSH2_SEND_FD(session, session->socket_fd, buffer, length, flags) +#define LIBSSH2_RECV(session, buffer, length, flags) \ + LIBSSH2_RECV_FD(session, session->socket_fd, buffer, length, flags) + +typedef struct _LIBSSH2_KEX_METHOD LIBSSH2_KEX_METHOD; +typedef struct _LIBSSH2_HOSTKEY_METHOD LIBSSH2_HOSTKEY_METHOD; +typedef struct _LIBSSH2_CRYPT_METHOD LIBSSH2_CRYPT_METHOD; +typedef struct _LIBSSH2_COMP_METHOD LIBSSH2_COMP_METHOD; + +typedef struct _LIBSSH2_PACKET LIBSSH2_PACKET; + +typedef enum +{ + libssh2_NB_state_idle = 0, + libssh2_NB_state_allocated, + libssh2_NB_state_created, + libssh2_NB_state_sent, + libssh2_NB_state_sent1, + libssh2_NB_state_sent2, + libssh2_NB_state_sent3, + libssh2_NB_state_sent4, + libssh2_NB_state_sent5, + libssh2_NB_state_sent6, + libssh2_NB_state_sent7, + libssh2_NB_state_jump1, + libssh2_NB_state_jump2, + libssh2_NB_state_jump3, + libssh2_NB_state_jump4, + libssh2_NB_state_jump5, + libssh2_NB_state_end +} libssh2_nonblocking_states; + +typedef struct packet_require_state_t +{ + libssh2_nonblocking_states state; + time_t start; +} packet_require_state_t; + +typedef struct packet_requirev_state_t +{ + time_t start; +} packet_requirev_state_t; + +typedef struct kmdhgGPshakex_state_t +{ + libssh2_nonblocking_states state; + unsigned char *e_packet; + unsigned char *s_packet; + unsigned char *tmp; + unsigned char h_sig_comp[MAX_SHA_DIGEST_LEN]; + unsigned char c; + size_t e_packet_len; + size_t s_packet_len; + size_t tmp_len; + _libssh2_bn_ctx *ctx; + _libssh2_dh_ctx x; + _libssh2_bn *e; + _libssh2_bn *f; + _libssh2_bn *k; + unsigned char *f_value; + unsigned char *k_value; + unsigned char *h_sig; + size_t f_value_len; + size_t k_value_len; + size_t h_sig_len; + void *exchange_hash; + packet_require_state_t req_state; + libssh2_nonblocking_states burn_state; +} kmdhgGPshakex_state_t; + +typedef struct key_exchange_state_low_t +{ + libssh2_nonblocking_states state; + packet_require_state_t req_state; + kmdhgGPshakex_state_t exchange_state; + _libssh2_bn *p; /* SSH2 defined value (p_value) */ + _libssh2_bn *g; /* SSH2 defined value (2) */ + unsigned char request[256]; /* Must fit EC_MAX_POINT_LEN + data */ + unsigned char *data; + size_t request_len; + size_t data_len; + _libssh2_ec_key *private_key; /* SSH2 ecdh private key */ + unsigned char *public_key_oct; /* SSH2 ecdh public key octal value */ + size_t public_key_oct_len; /* SSH2 ecdh public key octal value + length */ + unsigned char *curve25519_public_key; /* curve25519 public key, 32 + bytes */ + unsigned char *curve25519_private_key; /* curve25519 private key, 32 + bytes */ +} key_exchange_state_low_t; + +typedef struct key_exchange_state_t +{ + libssh2_nonblocking_states state; + packet_require_state_t req_state; + key_exchange_state_low_t key_state_low; + unsigned char *data; + size_t data_len; + unsigned char *oldlocal; + size_t oldlocal_len; +} key_exchange_state_t; + +#define FwdNotReq "Forward not requested" + +typedef struct packet_queue_listener_state_t +{ + libssh2_nonblocking_states state; + unsigned char packet[17 + (sizeof(FwdNotReq) - 1)]; + unsigned char *host; + unsigned char *shost; + uint32_t sender_channel; + uint32_t initial_window_size; + uint32_t packet_size; + uint32_t port; + uint32_t sport; + uint32_t host_len; + uint32_t shost_len; + LIBSSH2_CHANNEL *channel; +} packet_queue_listener_state_t; + +#define X11FwdUnAvil "X11 Forward Unavailable" + +typedef struct packet_x11_open_state_t +{ + libssh2_nonblocking_states state; + unsigned char packet[17 + (sizeof(X11FwdUnAvil) - 1)]; + unsigned char *shost; + uint32_t sender_channel; + uint32_t initial_window_size; + uint32_t packet_size; + uint32_t sport; + uint32_t shost_len; + LIBSSH2_CHANNEL *channel; +} packet_x11_open_state_t; + +struct _LIBSSH2_PACKET +{ + struct list_node node; /* linked list header */ + + /* the raw unencrypted payload */ + unsigned char *data; + size_t data_len; + + /* Where to start reading data from, + * used for channel data that's been partially consumed */ + size_t data_head; +}; + +typedef struct _libssh2_channel_data +{ + /* Identifier */ + uint32_t id; + + /* Limits and restrictions */ + uint32_t window_size_initial, window_size, packet_size; + + /* Set to 1 when CHANNEL_CLOSE / CHANNEL_EOF sent/received */ + char close, eof, extended_data_ignore_mode; +} libssh2_channel_data; + +struct _LIBSSH2_CHANNEL +{ + struct list_node node; + + unsigned char *channel_type; + unsigned channel_type_len; + + /* channel's program exit status */ + int exit_status; + + /* channel's program exit signal (without the SIG prefix) */ + char *exit_signal; + + libssh2_channel_data local, remote; + /* Amount of bytes to be refunded to receive window (but not yet sent) */ + uint32_t adjust_queue; + /* Data immediately available for reading */ + uint32_t read_avail; + + LIBSSH2_SESSION *session; + + void *abstract; + LIBSSH2_CHANNEL_CLOSE_FUNC((*close_cb)); + + /* State variables used in libssh2_channel_setenv_ex() */ + libssh2_nonblocking_states setenv_state; + unsigned char *setenv_packet; + size_t setenv_packet_len; + unsigned char setenv_local_channel[4]; + packet_requirev_state_t setenv_packet_requirev_state; + + /* State variables used in libssh2_channel_request_pty_ex() + libssh2_channel_request_pty_size_ex() */ + libssh2_nonblocking_states reqPTY_state; + unsigned char reqPTY_packet[41 + 256]; + size_t reqPTY_packet_len; + unsigned char reqPTY_local_channel[4]; + packet_requirev_state_t reqPTY_packet_requirev_state; + + /* State variables used in libssh2_channel_x11_req_ex() */ + libssh2_nonblocking_states reqX11_state; + unsigned char *reqX11_packet; + size_t reqX11_packet_len; + unsigned char reqX11_local_channel[4]; + packet_requirev_state_t reqX11_packet_requirev_state; + + /* State variables used in libssh2_channel_process_startup() */ + libssh2_nonblocking_states process_state; + unsigned char *process_packet; + size_t process_packet_len; + unsigned char process_local_channel[4]; + packet_requirev_state_t process_packet_requirev_state; + + /* State variables used in libssh2_channel_flush_ex() */ + libssh2_nonblocking_states flush_state; + size_t flush_refund_bytes; + size_t flush_flush_bytes; + + /* State variables used in libssh2_channel_receive_window_adjust() */ + libssh2_nonblocking_states adjust_state; + unsigned char adjust_adjust[9]; /* packet_type(1) + channel(4) + + adjustment(4) */ + + /* State variables used in libssh2_channel_read_ex() */ + libssh2_nonblocking_states read_state; + + uint32_t read_local_id; + + /* State variables used in libssh2_channel_write_ex() */ + libssh2_nonblocking_states write_state; + unsigned char write_packet[13]; + size_t write_packet_len; + size_t write_bufwrite; + + /* State variables used in libssh2_channel_close() */ + libssh2_nonblocking_states close_state; + unsigned char close_packet[5]; + + /* State variables used in libssh2_channel_wait_closedeof() */ + libssh2_nonblocking_states wait_eof_state; + + /* State variables used in libssh2_channel_wait_closed() */ + libssh2_nonblocking_states wait_closed_state; + + /* State variables used in libssh2_channel_free() */ + libssh2_nonblocking_states free_state; + + /* State variables used in libssh2_channel_handle_extended_data2() */ + libssh2_nonblocking_states extData2_state; + + /* State variables used in libssh2_channel_request_auth_agent() */ + libssh2_nonblocking_states req_auth_agent_try_state; + libssh2_nonblocking_states req_auth_agent_state; + unsigned char req_auth_agent_packet[36]; + size_t req_auth_agent_packet_len; + unsigned char req_auth_agent_local_channel[4]; + packet_requirev_state_t req_auth_agent_requirev_state; +}; + +struct _LIBSSH2_LISTENER +{ + struct list_node node; /* linked list header */ + + LIBSSH2_SESSION *session; + + char *host; + int port; + + /* a list of CHANNELs for this listener */ + struct list_head queue; + + int queue_size; + int queue_maxsize; + + /* State variables used in libssh2_channel_forward_cancel() */ + libssh2_nonblocking_states chanFwdCncl_state; + unsigned char *chanFwdCncl_data; + size_t chanFwdCncl_data_len; +}; + +typedef struct _libssh2_endpoint_data +{ + unsigned char *banner; + + unsigned char *kexinit; + size_t kexinit_len; + + const LIBSSH2_CRYPT_METHOD *crypt; + void *crypt_abstract; + + const struct _LIBSSH2_MAC_METHOD *mac; + uint32_t seqno; + void *mac_abstract; + + const LIBSSH2_COMP_METHOD *comp; + void *comp_abstract; + + /* Method Preferences -- NULL yields "load order" */ + char *crypt_prefs; + char *mac_prefs; + char *comp_prefs; + char *lang_prefs; +} libssh2_endpoint_data; + +#define PACKETBUFSIZE (1024*16) + +struct transportpacket +{ + /* ------------- for incoming data --------------- */ + unsigned char buf[PACKETBUFSIZE]; + unsigned char init[5]; /* first 5 bytes of the incoming data stream, + still encrypted */ + size_t writeidx; /* at what array index we do the next write into + the buffer */ + size_t readidx; /* at what array index we do the next read from + the buffer */ + uint32_t packet_length; /* the most recent packet_length as read from the + network data */ + uint8_t padding_length; /* the most recent padding_length as read from the + network data */ + size_t data_num; /* How much of the total package that has been read + so far. */ + size_t total_num; /* How much a total package is supposed to be, in + number of bytes. A full package is + packet_length + padding_length + 4 + + mac_length. */ + unsigned char *payload; /* this is a pointer to a LIBSSH2_ALLOC() + area to which we write decrypted data */ + unsigned char *wptr; /* write pointer into the payload to where we + are currently writing decrypted data */ + + /* ------------- for outgoing data --------------- */ + unsigned char outbuf[MAX_SSH_PACKET_LEN]; /* area for the outgoing data */ + + int ototal_num; /* size of outbuf in number of bytes */ + const unsigned char *odata; /* original pointer to the data */ + size_t olen; /* original size of the data we stored in + outbuf */ + size_t osent; /* number of bytes already sent */ +}; + +struct _LIBSSH2_PUBLICKEY +{ + LIBSSH2_CHANNEL *channel; + uint32_t version; + + /* State variables used in libssh2_publickey_packet_receive() */ + libssh2_nonblocking_states receive_state; + unsigned char *receive_packet; + size_t receive_packet_len; + + /* State variables used in libssh2_publickey_add_ex() */ + libssh2_nonblocking_states add_state; + unsigned char *add_packet; + unsigned char *add_s; + + /* State variables used in libssh2_publickey_remove_ex() */ + libssh2_nonblocking_states remove_state; + unsigned char *remove_packet; + unsigned char *remove_s; + + /* State variables used in libssh2_publickey_list_fetch() */ + libssh2_nonblocking_states listFetch_state; + unsigned char *listFetch_s; + unsigned char listFetch_buffer[12]; + unsigned char *listFetch_data; + size_t listFetch_data_len; +}; + +#define LIBSSH2_SCP_RESPONSE_BUFLEN 256 + +struct flags { + int sigpipe; /* LIBSSH2_FLAG_SIGPIPE */ + int compress; /* LIBSSH2_FLAG_COMPRESS */ +}; + +struct _LIBSSH2_SESSION +{ + /* Memory management callbacks */ + void *abstract; + LIBSSH2_ALLOC_FUNC((*alloc)); + LIBSSH2_REALLOC_FUNC((*realloc)); + LIBSSH2_FREE_FUNC((*free)); + + /* Other callbacks */ + LIBSSH2_IGNORE_FUNC((*ssh_msg_ignore)); + LIBSSH2_DEBUG_FUNC((*ssh_msg_debug)); + LIBSSH2_DISCONNECT_FUNC((*ssh_msg_disconnect)); + LIBSSH2_MACERROR_FUNC((*macerror)); + LIBSSH2_X11_OPEN_FUNC((*x11)); + LIBSSH2_SEND_FUNC((*send)); + LIBSSH2_RECV_FUNC((*recv)); + + /* Method preferences -- NULL yields "load order" */ + char *kex_prefs; + char *hostkey_prefs; + + int state; + + /* Flag options */ + struct flags flag; + + /* Agreed Key Exchange Method */ + const LIBSSH2_KEX_METHOD *kex; + unsigned int burn_optimistic_kexinit:1; + + unsigned char *session_id; + uint32_t session_id_len; + + /* this is set to TRUE if a blocking API behavior is requested */ + int api_block_mode; + + /* Timeout used when blocking API behavior is active */ + long api_timeout; + + /* Server's public key */ + const LIBSSH2_HOSTKEY_METHOD *hostkey; + void *server_hostkey_abstract; + + /* Either set with libssh2_session_hostkey() (for server mode) + * Or read from server in (eg) KEXDH_INIT (for client mode) + */ + unsigned char *server_hostkey; + uint32_t server_hostkey_len; +#if LIBSSH2_MD5 + unsigned char server_hostkey_md5[MD5_DIGEST_LENGTH]; + int server_hostkey_md5_valid; +#endif /* ! LIBSSH2_MD5 */ + unsigned char server_hostkey_sha1[SHA_DIGEST_LENGTH]; + int server_hostkey_sha1_valid; + + unsigned char server_hostkey_sha256[SHA256_DIGEST_LENGTH]; + int server_hostkey_sha256_valid; + + /* public key algorithms accepted as comma separated list */ + char *server_sign_algorithms; + + /* key signing algorithm preferences -- NULL yields server order */ + char *sign_algo_prefs; + + /* (remote as source of data -- packet_read ) */ + libssh2_endpoint_data remote; + + /* (local as source of data -- packet_write ) */ + libssh2_endpoint_data local; + + /* Inbound Data linked list -- Sometimes the packet that comes in isn't the + packet we're ready for */ + struct list_head packets; + + /* Active connection channels */ + struct list_head channels; + + uint32_t next_channel; + + struct list_head listeners; /* list of LIBSSH2_LISTENER structs */ + + /* Actual I/O socket */ + libssh2_socket_t socket_fd; + int socket_state; + int socket_block_directions; + int socket_prev_blockstate; /* stores the state of the socket blockiness + when libssh2_session_startup() is called */ + + /* Error tracking */ + const char *err_msg; + int err_code; + int err_flags; + + /* struct members for packet-level reading */ + struct transportpacket packet; +#ifdef LIBSSH2DEBUG + int showmask; /* what debug/trace messages to display */ + libssh2_trace_handler_func tracehandler; /* callback to display trace + messages */ + void *tracehandler_context; /* context for the trace handler */ +#endif + + /* State variables used in libssh2_banner_send() */ + libssh2_nonblocking_states banner_TxRx_state; + char banner_TxRx_banner[256]; + ssize_t banner_TxRx_total_send; + + /* State variables used in libssh2_kexinit() */ + libssh2_nonblocking_states kexinit_state; + unsigned char *kexinit_data; + size_t kexinit_data_len; + + /* State variables used in libssh2_session_startup() */ + libssh2_nonblocking_states startup_state; + unsigned char *startup_data; + size_t startup_data_len; + unsigned char startup_service[sizeof("ssh-userauth") + 5 - 1]; + size_t startup_service_length; + packet_require_state_t startup_req_state; + key_exchange_state_t startup_key_state; + + /* State variables used in libssh2_session_free() */ + libssh2_nonblocking_states free_state; + + /* State variables used in libssh2_session_disconnect_ex() */ + libssh2_nonblocking_states disconnect_state; + unsigned char disconnect_data[256 + 13]; + size_t disconnect_data_len; + + /* State variables used in libssh2_packet_read() */ + libssh2_nonblocking_states readPack_state; + int readPack_encrypted; + + /* State variables used in libssh2_userauth_list() */ + libssh2_nonblocking_states userauth_list_state; + unsigned char *userauth_list_data; + size_t userauth_list_data_len; + char *userauth_banner; + packet_requirev_state_t userauth_list_packet_requirev_state; + + /* State variables used in libssh2_userauth_password_ex() */ + libssh2_nonblocking_states userauth_pswd_state; + unsigned char *userauth_pswd_data; + unsigned char userauth_pswd_data0; + size_t userauth_pswd_data_len; + char *userauth_pswd_newpw; + int userauth_pswd_newpw_len; + packet_requirev_state_t userauth_pswd_packet_requirev_state; + + /* State variables used in libssh2_userauth_hostbased_fromfile_ex() */ + libssh2_nonblocking_states userauth_host_state; + unsigned char *userauth_host_data; + size_t userauth_host_data_len; + unsigned char *userauth_host_packet; + size_t userauth_host_packet_len; + unsigned char *userauth_host_method; + size_t userauth_host_method_len; + unsigned char *userauth_host_s; + packet_requirev_state_t userauth_host_packet_requirev_state; + + /* State variables used in libssh2_userauth_publickey_fromfile_ex() */ + libssh2_nonblocking_states userauth_pblc_state; + unsigned char *userauth_pblc_data; + size_t userauth_pblc_data_len; + unsigned char *userauth_pblc_packet; + size_t userauth_pblc_packet_len; + unsigned char *userauth_pblc_method; + size_t userauth_pblc_method_len; + unsigned char *userauth_pblc_s; + unsigned char *userauth_pblc_b; + packet_requirev_state_t userauth_pblc_packet_requirev_state; + + /* State variables used in libssh2_userauth_keyboard_interactive_ex() */ + libssh2_nonblocking_states userauth_kybd_state; + unsigned char *userauth_kybd_data; + size_t userauth_kybd_data_len; + unsigned char *userauth_kybd_packet; + size_t userauth_kybd_packet_len; + size_t userauth_kybd_auth_name_len; + unsigned char *userauth_kybd_auth_name; + size_t userauth_kybd_auth_instruction_len; + unsigned char *userauth_kybd_auth_instruction; + unsigned int userauth_kybd_num_prompts; + int userauth_kybd_auth_failure; + LIBSSH2_USERAUTH_KBDINT_PROMPT *userauth_kybd_prompts; + LIBSSH2_USERAUTH_KBDINT_RESPONSE *userauth_kybd_responses; + packet_requirev_state_t userauth_kybd_packet_requirev_state; + + /* State variables used in libssh2_channel_open_ex() */ + libssh2_nonblocking_states open_state; + packet_requirev_state_t open_packet_requirev_state; + LIBSSH2_CHANNEL *open_channel; + unsigned char *open_packet; + size_t open_packet_len; + unsigned char *open_data; + size_t open_data_len; + uint32_t open_local_channel; + + /* State variables used in libssh2_channel_direct_tcpip_ex() */ + libssh2_nonblocking_states direct_state; + unsigned char *direct_message; + size_t direct_host_len; + size_t direct_shost_len; + size_t direct_message_len; + + /* State variables used in libssh2_channel_forward_listen_ex() */ + libssh2_nonblocking_states fwdLstn_state; + unsigned char *fwdLstn_packet; + uint32_t fwdLstn_host_len; + uint32_t fwdLstn_packet_len; + packet_requirev_state_t fwdLstn_packet_requirev_state; + + /* State variables used in libssh2_publickey_init() */ + libssh2_nonblocking_states pkeyInit_state; + LIBSSH2_PUBLICKEY *pkeyInit_pkey; + LIBSSH2_CHANNEL *pkeyInit_channel; + unsigned char *pkeyInit_data; + size_t pkeyInit_data_len; + /* 19 = packet_len(4) + version_len(4) + "version"(7) + version_num(4) */ + unsigned char pkeyInit_buffer[19]; + size_t pkeyInit_buffer_sent; /* how much of buffer that has been sent */ + + /* State variables used in libssh2_packet_add() */ + libssh2_nonblocking_states packAdd_state; + LIBSSH2_CHANNEL *packAdd_channelp; /* keeper of the channel during EAGAIN + states */ + packet_queue_listener_state_t packAdd_Qlstn_state; + packet_x11_open_state_t packAdd_x11open_state; + + /* State variables used in fullpacket() */ + libssh2_nonblocking_states fullpacket_state; + int fullpacket_macstate; + size_t fullpacket_payload_len; + int fullpacket_packet_type; + + /* State variables used in libssh2_sftp_init() */ + libssh2_nonblocking_states sftpInit_state; + LIBSSH2_SFTP *sftpInit_sftp; + LIBSSH2_CHANNEL *sftpInit_channel; + unsigned char sftpInit_buffer[9]; /* sftp_header(5){excludes request_id} + + version_id(4) */ + int sftpInit_sent; /* number of bytes from the buffer that have been + sent */ + + /* State variables used in libssh2_scp_recv() / libssh_scp_recv2() */ + libssh2_nonblocking_states scpRecv_state; + unsigned char *scpRecv_command; + size_t scpRecv_command_len; + unsigned char scpRecv_response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + size_t scpRecv_response_len; + long scpRecv_mode; +#if defined(HAVE_LONGLONG) && defined(HAVE_STRTOLL) + /* we have the type and we can parse such numbers */ + long long scpRecv_size; +#define scpsize_strtol strtoll +#elif defined(HAVE_STRTOI64) + __int64 scpRecv_size; +#define scpsize_strtol _strtoi64 +#else + long scpRecv_size; +#define scpsize_strtol strtol +#endif + long scpRecv_mtime; + long scpRecv_atime; + LIBSSH2_CHANNEL *scpRecv_channel; + + /* State variables used in libssh2_scp_send_ex() */ + libssh2_nonblocking_states scpSend_state; + unsigned char *scpSend_command; + size_t scpSend_command_len; + unsigned char scpSend_response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + size_t scpSend_response_len; + LIBSSH2_CHANNEL *scpSend_channel; + + /* Keepalive variables used by keepalive.c. */ + int keepalive_interval; + int keepalive_want_reply; + time_t keepalive_last_sent; +}; + +/* session.state bits */ +#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001 +#define LIBSSH2_STATE_NEWKEYS 0x00000002 +#define LIBSSH2_STATE_AUTHENTICATED 0x00000004 +#define LIBSSH2_STATE_KEX_ACTIVE 0x00000008 + +/* session.flag helpers */ +#ifdef MSG_NOSIGNAL +#define LIBSSH2_SOCKET_SEND_FLAGS(session) \ + (((session)->flag.sigpipe) ? 0 : MSG_NOSIGNAL) +#define LIBSSH2_SOCKET_RECV_FLAGS(session) \ + (((session)->flag.sigpipe) ? 0 : MSG_NOSIGNAL) +#else +/* If MSG_NOSIGNAL isn't defined we're SOL on blocking SIGPIPE */ +#define LIBSSH2_SOCKET_SEND_FLAGS(session) 0 +#define LIBSSH2_SOCKET_RECV_FLAGS(session) 0 +#endif + +/* --------- */ + +/* libssh2 extensible ssh api, ultimately I'd like to allow loading additional + methods via .so/.dll */ + +struct _LIBSSH2_KEX_METHOD +{ + const char *name; + + /* Key exchange, populates session->* and returns 0 on success, non-0 on + error */ + int (*exchange_keys) (LIBSSH2_SESSION * session, + key_exchange_state_low_t * key_state); + + long flags; +}; + +struct _LIBSSH2_HOSTKEY_METHOD +{ + const char *name; + unsigned long hash_len; + + int (*init) (LIBSSH2_SESSION * session, const unsigned char *hostkey_data, + size_t hostkey_data_len, void **abstract); + int (*initPEM) (LIBSSH2_SESSION * session, const char *privkeyfile, + unsigned const char *passphrase, void **abstract); + int (*initPEMFromMemory) (LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract); + int (*sig_verify) (LIBSSH2_SESSION * session, const unsigned char *sig, + size_t sig_len, const unsigned char *m, + size_t m_len, void **abstract); + int (*signv) (LIBSSH2_SESSION * session, unsigned char **signature, + size_t *signature_len, int veccount, + const struct iovec datavec[], void **abstract); + int (*encrypt) (LIBSSH2_SESSION * session, unsigned char **dst, + size_t *dst_len, const unsigned char *src, + size_t src_len, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); +}; + +struct _LIBSSH2_CRYPT_METHOD +{ + const char *name; + const char *pem_annotation; + + int blocksize; + + /* iv and key sizes (-1 for variable length) */ + int iv_len; + int secret_len; + + long flags; + + int (*init) (LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, unsigned char *iv, + int *free_iv, unsigned char *secret, int *free_secret, + int encrypt, void **abstract); + int (*crypt) (LIBSSH2_SESSION * session, unsigned char *block, + size_t blocksize, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); + + _libssh2_cipher_type(algo); +}; + +struct _LIBSSH2_COMP_METHOD +{ + const char *name; + int compress; /* 1 if it does compress, 0 if it doesn't */ + int use_in_auth; /* 1 if compression should be used in userauth */ + int (*init) (LIBSSH2_SESSION *session, int compress, void **abstract); + int (*comp) (LIBSSH2_SESSION *session, + unsigned char *dest, + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract); + int (*decomp) (LIBSSH2_SESSION *session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, + void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, int compress, void **abstract); +}; + +#ifdef LIBSSH2DEBUG +void _libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, + ...); +#else +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__GNUC__) +/* C99 supported and also by older GCC */ +#define _libssh2_debug(x,y,...) do {} while (0) +#else +/* no gcc and not C99, do static and hopefully inline */ +static inline void +_libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, ...) +{ + (void)session; + (void)context; + (void)format; +} +#endif +#endif + +#define LIBSSH2_SOCKET_UNKNOWN 1 +#define LIBSSH2_SOCKET_CONNECTED 0 +#define LIBSSH2_SOCKET_DISCONNECTED -1 + +/* Initial packet state, prior to MAC check */ +#define LIBSSH2_MAC_UNCONFIRMED 1 +/* When MAC type is "none" (proto initiation phase) all packets are deemed + "confirmed" */ +#define LIBSSH2_MAC_CONFIRMED 0 +/* Something very bad is going on */ +#define LIBSSH2_MAC_INVALID -1 + +/* Flags for _libssh2_error_flags */ +/* Error message is allocated on the heap */ +#define LIBSSH2_ERR_FLAG_DUP 1 + +/* SSH Packet Types -- Defined by internet draft */ +/* Transport Layer */ +#define SSH_MSG_DISCONNECT 1 +#define SSH_MSG_IGNORE 2 +#define SSH_MSG_UNIMPLEMENTED 3 +#define SSH_MSG_DEBUG 4 +#define SSH_MSG_SERVICE_REQUEST 5 +#define SSH_MSG_SERVICE_ACCEPT 6 +#define SSH_MSG_EXT_INFO 7 + +#define SSH_MSG_KEXINIT 20 +#define SSH_MSG_NEWKEYS 21 + +/* diffie-hellman-group1-sha1 */ +#define SSH_MSG_KEXDH_INIT 30 +#define SSH_MSG_KEXDH_REPLY 31 + +/* diffie-hellman-group-exchange-sha1 and + diffie-hellman-group-exchange-sha256 */ +#define SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH_MSG_KEX_DH_GEX_GROUP 31 +#define SSH_MSG_KEX_DH_GEX_INIT 32 +#define SSH_MSG_KEX_DH_GEX_REPLY 33 + +/* ecdh */ +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 + +/* User Authentication */ +#define SSH_MSG_USERAUTH_REQUEST 50 +#define SSH_MSG_USERAUTH_FAILURE 51 +#define SSH_MSG_USERAUTH_SUCCESS 52 +#define SSH_MSG_USERAUTH_BANNER 53 + +/* "public key" method */ +#define SSH_MSG_USERAUTH_PK_OK 60 +/* "password" method */ +#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +/* "keyboard-interactive" method */ +#define SSH_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH_MSG_USERAUTH_INFO_RESPONSE 61 + +/* Channels */ +#define SSH_MSG_GLOBAL_REQUEST 80 +#define SSH_MSG_REQUEST_SUCCESS 81 +#define SSH_MSG_REQUEST_FAILURE 82 + +#define SSH_MSG_CHANNEL_OPEN 90 +#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH_MSG_CHANNEL_DATA 94 +#define SSH_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH_MSG_CHANNEL_EOF 96 +#define SSH_MSG_CHANNEL_CLOSE 97 +#define SSH_MSG_CHANNEL_REQUEST 98 +#define SSH_MSG_CHANNEL_SUCCESS 99 +#define SSH_MSG_CHANNEL_FAILURE 100 + +/* Error codes returned in SSH_MSG_CHANNEL_OPEN_FAILURE message + (see RFC4254) */ +#define SSH_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH_OPEN_CONNECT_FAILED 2 +#define SSH_OPEN_UNKNOWN_CHANNELTYPE 3 +#define SSH_OPEN_RESOURCE_SHORTAGE 4 + +ssize_t _libssh2_recv(libssh2_socket_t socket, void *buffer, + size_t length, int flags, void **abstract); +ssize_t _libssh2_send(libssh2_socket_t socket, const void *buffer, + size_t length, int flags, void **abstract); + +#define LIBSSH2_READ_TIMEOUT 60 /* generic timeout in seconds used when + waiting for more data to arrive */ + + +int _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, + key_exchange_state_t * state); + +/* Let crypt.c/hostkey.c expose their method structs */ +const LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); +const LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); + +/* misc.c */ +int _libssh2_bcrypt_pbkdf(const char *pass, + size_t passlen, + const uint8_t *salt, + size_t saltlen, + uint8_t *key, + size_t keylen, + unsigned int rounds); + +/* pem.c */ +int _libssh2_pem_parse(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + const unsigned char *passphrase, + FILE * fp, unsigned char **data, unsigned int *datalen); +int _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + const char *filedata, size_t filedata_len, + unsigned char **data, unsigned int *datalen); + /* OpenSSL keys */ +int +_libssh2_openssh_pem_parse(LIBSSH2_SESSION * session, + const unsigned char *passphrase, + FILE * fp, struct string_buf **decrypted_buf); +int +_libssh2_openssh_pem_parse_memory(LIBSSH2_SESSION * session, + const unsigned char *passphrase, + const char *filedata, size_t filedata_len, + struct string_buf **decrypted_buf); + +int _libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen); +int _libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, + unsigned char **i, unsigned int *ilen); + +/* global.c */ +void _libssh2_init_if_needed(void); + + +#define ARRAY_SIZE(a) (sizeof ((a)) / sizeof ((a)[0])) + +/* define to output the libssh2_int64_t type in a *printf() */ +#if defined(__BORLANDC__) || defined(_MSC_VER) || defined(__MINGW32__) +#define LIBSSH2_INT64_T_FORMAT "I64d" +#else +#define LIBSSH2_INT64_T_FORMAT "lld" +#endif + +/* In Windows the default file mode is text but an application can override it. +Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 +*/ +#if defined(WIN32) || defined(MSDOS) +#define FOPEN_READTEXT "rt" +#define FOPEN_WRITETEXT "wt" +#define FOPEN_APPENDTEXT "at" +#elif defined(__CYGWIN__) +/* Cygwin has specific behavior we need to address when WIN32 is not defined. +https://cygwin.com/cygwin-ug-net/using-textbinary.html +For write we want our output to have line endings of LF and be compatible with +other Cygwin utilities. For read we want to handle input that may have line +endings either CRLF or LF so 't' is appropriate. +*/ +#define FOPEN_READTEXT "rt" +#define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" +#else +#define FOPEN_READTEXT "r" +#define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" +#endif + +#endif /* __LIBSSH2_PRIV_H */ +#endif diff --git a/lib/libssh2/libssh2_publickey.h b/lib/libssh2/libssh2_publickey.h new file mode 100644 index 0000000..4c68eaf --- /dev/null +++ b/lib/libssh2/libssh2_publickey.h @@ -0,0 +1,124 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2006, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* Note: This include file is only needed for using the + * publickey SUBSYSTEM which is not the same as publickey + * authentication. For authentication you only need libssh2.h + * + * For more information on the publickey subsystem, + * refer to IETF draft: secsh-publickey + */ + +#ifndef LIBSSH2_PUBLICKEY_H +#define LIBSSH2_PUBLICKEY_H 1 + +#include "libssh2.h" + +typedef struct _LIBSSH2_PUBLICKEY LIBSSH2_PUBLICKEY; + +typedef struct _libssh2_publickey_attribute { + const char *name; + unsigned long name_len; + const char *value; + unsigned long value_len; + char mandatory; +} libssh2_publickey_attribute; + +typedef struct _libssh2_publickey_list { + unsigned char *packet; /* For freeing */ + + const unsigned char *name; + unsigned long name_len; + const unsigned char *blob; + unsigned long blob_len; + unsigned long num_attrs; + libssh2_publickey_attribute *attrs; /* free me */ +} libssh2_publickey_list; + +/* Generally use the first macro here, but if both name and value are string + literals, you can use _fast() to take advantage of preprocessing */ +#define libssh2_publickey_attribute(name, value, mandatory) \ + { (name), strlen(name), (value), strlen(value), (mandatory) }, +#define libssh2_publickey_attribute_fast(name, value, mandatory) \ + { (name), sizeof(name) - 1, (value), sizeof(value) - 1, (mandatory) }, + +#ifdef __cplusplus +extern "C" { +#endif + +/* Publickey Subsystem */ +LIBSSH2_API LIBSSH2_PUBLICKEY * +libssh2_publickey_init(LIBSSH2_SESSION *session); + +LIBSSH2_API int +libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, + const unsigned char *name, + unsigned long name_len, + const unsigned char *blob, + unsigned long blob_len, char overwrite, + unsigned long num_attrs, + const libssh2_publickey_attribute attrs[]); +#define libssh2_publickey_add(pkey, name, blob, blob_len, overwrite, \ + num_attrs, attrs) \ + libssh2_publickey_add_ex((pkey), (name), strlen(name), (blob), (blob_len), \ + (overwrite), (num_attrs), (attrs)) + +LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, + const unsigned char *name, + unsigned long name_len, + const unsigned char *blob, + unsigned long blob_len); +#define libssh2_publickey_remove(pkey, name, blob, blob_len) \ + libssh2_publickey_remove_ex((pkey), (name), strlen(name), (blob), (blob_len)) + +LIBSSH2_API int +libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, + unsigned long *num_keys, + libssh2_publickey_list **pkey_list); +LIBSSH2_API void +libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, + libssh2_publickey_list *pkey_list); + +LIBSSH2_API int libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ifndef: LIBSSH2_PUBLICKEY_H */ +#endif diff --git a/lib/libssh2/libssh2_sftp.h b/lib/libssh2/libssh2_sftp.h new file mode 100644 index 0000000..32970be --- /dev/null +++ b/lib/libssh2/libssh2_sftp.h @@ -0,0 +1,353 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2008, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef LIBSSH2_SFTP_H +#define LIBSSH2_SFTP_H 1 + +#include "libssh2.h" + +#ifndef WIN32 +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Note: Version 6 was documented at the time of writing + * However it was marked as "DO NOT IMPLEMENT" due to pending changes + * + * Let's start with Version 3 (The version found in OpenSSH) and go from there + */ +#define LIBSSH2_SFTP_VERSION 3 + +typedef struct _LIBSSH2_SFTP LIBSSH2_SFTP; +typedef struct _LIBSSH2_SFTP_HANDLE LIBSSH2_SFTP_HANDLE; +typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; +typedef struct _LIBSSH2_SFTP_STATVFS LIBSSH2_SFTP_STATVFS; + +/* Flags for open_ex() */ +#define LIBSSH2_SFTP_OPENFILE 0 +#define LIBSSH2_SFTP_OPENDIR 1 + +/* Flags for rename_ex() */ +#define LIBSSH2_SFTP_RENAME_OVERWRITE 0x00000001 +#define LIBSSH2_SFTP_RENAME_ATOMIC 0x00000002 +#define LIBSSH2_SFTP_RENAME_NATIVE 0x00000004 + +/* Flags for stat_ex() */ +#define LIBSSH2_SFTP_STAT 0 +#define LIBSSH2_SFTP_LSTAT 1 +#define LIBSSH2_SFTP_SETSTAT 2 + +/* Flags for symlink_ex() */ +#define LIBSSH2_SFTP_SYMLINK 0 +#define LIBSSH2_SFTP_READLINK 1 +#define LIBSSH2_SFTP_REALPATH 2 + +/* Flags for sftp_mkdir() */ +#define LIBSSH2_SFTP_DEFAULT_MODE -1 + +/* SFTP attribute flag bits */ +#define LIBSSH2_SFTP_ATTR_SIZE 0x00000001 +#define LIBSSH2_SFTP_ATTR_UIDGID 0x00000002 +#define LIBSSH2_SFTP_ATTR_PERMISSIONS 0x00000004 +#define LIBSSH2_SFTP_ATTR_ACMODTIME 0x00000008 +#define LIBSSH2_SFTP_ATTR_EXTENDED 0x80000000 + +/* SFTP statvfs flag bits */ +#define LIBSSH2_SFTP_ST_RDONLY 0x00000001 +#define LIBSSH2_SFTP_ST_NOSUID 0x00000002 + +struct _LIBSSH2_SFTP_ATTRIBUTES { + /* If flags & ATTR_* bit is set, then the value in this struct will be + * meaningful Otherwise it should be ignored + */ + unsigned long flags; + + libssh2_uint64_t filesize; + unsigned long uid, gid; + unsigned long permissions; + unsigned long atime, mtime; +}; + +struct _LIBSSH2_SFTP_STATVFS { + libssh2_uint64_t f_bsize; /* file system block size */ + libssh2_uint64_t f_frsize; /* fragment size */ + libssh2_uint64_t f_blocks; /* size of fs in f_frsize units */ + libssh2_uint64_t f_bfree; /* # free blocks */ + libssh2_uint64_t f_bavail; /* # free blocks for non-root */ + libssh2_uint64_t f_files; /* # inodes */ + libssh2_uint64_t f_ffree; /* # free inodes */ + libssh2_uint64_t f_favail; /* # free inodes for non-root */ + libssh2_uint64_t f_fsid; /* file system ID */ + libssh2_uint64_t f_flag; /* mount flags */ + libssh2_uint64_t f_namemax; /* maximum filename length */ +}; + +/* SFTP filetypes */ +#define LIBSSH2_SFTP_TYPE_REGULAR 1 +#define LIBSSH2_SFTP_TYPE_DIRECTORY 2 +#define LIBSSH2_SFTP_TYPE_SYMLINK 3 +#define LIBSSH2_SFTP_TYPE_SPECIAL 4 +#define LIBSSH2_SFTP_TYPE_UNKNOWN 5 +#define LIBSSH2_SFTP_TYPE_SOCKET 6 +#define LIBSSH2_SFTP_TYPE_CHAR_DEVICE 7 +#define LIBSSH2_SFTP_TYPE_BLOCK_DEVICE 8 +#define LIBSSH2_SFTP_TYPE_FIFO 9 + +/* + * Reproduce the POSIX file modes here for systems that are not POSIX + * compliant. + * + * These is used in "permissions" of "struct _LIBSSH2_SFTP_ATTRIBUTES" + */ +/* File type */ +#define LIBSSH2_SFTP_S_IFMT 0170000 /* type of file mask */ +#define LIBSSH2_SFTP_S_IFIFO 0010000 /* named pipe (fifo) */ +#define LIBSSH2_SFTP_S_IFCHR 0020000 /* character special */ +#define LIBSSH2_SFTP_S_IFDIR 0040000 /* directory */ +#define LIBSSH2_SFTP_S_IFBLK 0060000 /* block special */ +#define LIBSSH2_SFTP_S_IFREG 0100000 /* regular */ +#define LIBSSH2_SFTP_S_IFLNK 0120000 /* symbolic link */ +#define LIBSSH2_SFTP_S_IFSOCK 0140000 /* socket */ + +/* File mode */ +/* Read, write, execute/search by owner */ +#define LIBSSH2_SFTP_S_IRWXU 0000700 /* RWX mask for owner */ +#define LIBSSH2_SFTP_S_IRUSR 0000400 /* R for owner */ +#define LIBSSH2_SFTP_S_IWUSR 0000200 /* W for owner */ +#define LIBSSH2_SFTP_S_IXUSR 0000100 /* X for owner */ +/* Read, write, execute/search by group */ +#define LIBSSH2_SFTP_S_IRWXG 0000070 /* RWX mask for group */ +#define LIBSSH2_SFTP_S_IRGRP 0000040 /* R for group */ +#define LIBSSH2_SFTP_S_IWGRP 0000020 /* W for group */ +#define LIBSSH2_SFTP_S_IXGRP 0000010 /* X for group */ +/* Read, write, execute/search by others */ +#define LIBSSH2_SFTP_S_IRWXO 0000007 /* RWX mask for other */ +#define LIBSSH2_SFTP_S_IROTH 0000004 /* R for other */ +#define LIBSSH2_SFTP_S_IWOTH 0000002 /* W for other */ +#define LIBSSH2_SFTP_S_IXOTH 0000001 /* X for other */ + +/* macros to check for specific file types, added in 1.2.5 */ +#define LIBSSH2_SFTP_S_ISLNK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFLNK) +#define LIBSSH2_SFTP_S_ISREG(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFREG) +#define LIBSSH2_SFTP_S_ISDIR(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFDIR) +#define LIBSSH2_SFTP_S_ISCHR(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFCHR) +#define LIBSSH2_SFTP_S_ISBLK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFBLK) +#define LIBSSH2_SFTP_S_ISFIFO(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFIFO) +#define LIBSSH2_SFTP_S_ISSOCK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFSOCK) + +/* SFTP File Transfer Flags -- (e.g. flags parameter to sftp_open()) + * Danger will robinson... APPEND doesn't have any effect on OpenSSH servers */ +#define LIBSSH2_FXF_READ 0x00000001 +#define LIBSSH2_FXF_WRITE 0x00000002 +#define LIBSSH2_FXF_APPEND 0x00000004 +#define LIBSSH2_FXF_CREAT 0x00000008 +#define LIBSSH2_FXF_TRUNC 0x00000010 +#define LIBSSH2_FXF_EXCL 0x00000020 + +/* SFTP Status Codes (returned by libssh2_sftp_last_error() ) */ +#define LIBSSH2_FX_OK 0UL +#define LIBSSH2_FX_EOF 1UL +#define LIBSSH2_FX_NO_SUCH_FILE 2UL +#define LIBSSH2_FX_PERMISSION_DENIED 3UL +#define LIBSSH2_FX_FAILURE 4UL +#define LIBSSH2_FX_BAD_MESSAGE 5UL +#define LIBSSH2_FX_NO_CONNECTION 6UL +#define LIBSSH2_FX_CONNECTION_LOST 7UL +#define LIBSSH2_FX_OP_UNSUPPORTED 8UL +#define LIBSSH2_FX_INVALID_HANDLE 9UL +#define LIBSSH2_FX_NO_SUCH_PATH 10UL +#define LIBSSH2_FX_FILE_ALREADY_EXISTS 11UL +#define LIBSSH2_FX_WRITE_PROTECT 12UL +#define LIBSSH2_FX_NO_MEDIA 13UL +#define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14UL +#define LIBSSH2_FX_QUOTA_EXCEEDED 15UL +#define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16UL /* Initial mis-spelling */ +#define LIBSSH2_FX_UNKNOWN_PRINCIPAL 16UL +#define LIBSSH2_FX_LOCK_CONFlICT 17UL /* Initial mis-spelling */ +#define LIBSSH2_FX_LOCK_CONFLICT 17UL +#define LIBSSH2_FX_DIR_NOT_EMPTY 18UL +#define LIBSSH2_FX_NOT_A_DIRECTORY 19UL +#define LIBSSH2_FX_INVALID_FILENAME 20UL +#define LIBSSH2_FX_LINK_LOOP 21UL + +/* Returned by any function that would block during a read/write operation */ +#define LIBSSH2SFTP_EAGAIN LIBSSH2_ERROR_EAGAIN + +/* SFTP API */ +LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp); +LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp); +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_sftp_get_channel(LIBSSH2_SFTP *sftp); + +/* File / Directory Ops */ +LIBSSH2_API LIBSSH2_SFTP_HANDLE * +libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, + const char *filename, + unsigned int filename_len, + unsigned long flags, + long mode, int open_type); +#define libssh2_sftp_open(sftp, filename, flags, mode) \ + libssh2_sftp_open_ex((sftp), (filename), strlen(filename), (flags), \ + (mode), LIBSSH2_SFTP_OPENFILE) +#define libssh2_sftp_opendir(sftp, path) \ + libssh2_sftp_open_ex((sftp), (path), strlen(path), 0, 0, \ + LIBSSH2_SFTP_OPENDIR) + +LIBSSH2_API ssize_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, + char *buffer, size_t buffer_maxlen); + +LIBSSH2_API int libssh2_sftp_readdir_ex(LIBSSH2_SFTP_HANDLE *handle, \ + char *buffer, size_t buffer_maxlen, + char *longentry, + size_t longentry_maxlen, + LIBSSH2_SFTP_ATTRIBUTES *attrs); +#define libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs) \ + libssh2_sftp_readdir_ex((handle), (buffer), (buffer_maxlen), NULL, 0, \ + (attrs)) + +LIBSSH2_API ssize_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, + const char *buffer, size_t count); +LIBSSH2_API int libssh2_sftp_fsync(LIBSSH2_SFTP_HANDLE *handle); + +LIBSSH2_API int libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); +#define libssh2_sftp_close(handle) libssh2_sftp_close_handle(handle) +#define libssh2_sftp_closedir(handle) libssh2_sftp_close_handle(handle) + +LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset); +LIBSSH2_API void libssh2_sftp_seek64(LIBSSH2_SFTP_HANDLE *handle, + libssh2_uint64_t offset); +#define libssh2_sftp_rewind(handle) libssh2_sftp_seek64((handle), 0) + +LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle); +LIBSSH2_API libssh2_uint64_t libssh2_sftp_tell64(LIBSSH2_SFTP_HANDLE *handle); + +LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_ATTRIBUTES *attrs, + int setstat); +#define libssh2_sftp_fstat(handle, attrs) \ + libssh2_sftp_fstat_ex((handle), (attrs), 0) +#define libssh2_sftp_fsetstat(handle, attrs) \ + libssh2_sftp_fstat_ex((handle), (attrs), 1) + +/* Miscellaneous Ops */ +LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, + const char *source_filename, + unsigned int srouce_filename_len, + const char *dest_filename, + unsigned int dest_filename_len, + long flags); +#define libssh2_sftp_rename(sftp, sourcefile, destfile) \ + libssh2_sftp_rename_ex((sftp), (sourcefile), strlen(sourcefile), \ + (destfile), strlen(destfile), \ + LIBSSH2_SFTP_RENAME_OVERWRITE | \ + LIBSSH2_SFTP_RENAME_ATOMIC | \ + LIBSSH2_SFTP_RENAME_NATIVE) + +LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, + const char *filename, + unsigned int filename_len); +#define libssh2_sftp_unlink(sftp, filename) \ + libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) + +LIBSSH2_API int libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_STATVFS *st); + +LIBSSH2_API int libssh2_sftp_statvfs(LIBSSH2_SFTP *sftp, + const char *path, + size_t path_len, + LIBSSH2_SFTP_STATVFS *st); + +LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, long mode); +#define libssh2_sftp_mkdir(sftp, path, mode) \ + libssh2_sftp_mkdir_ex((sftp), (path), strlen(path), (mode)) + +LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len); +#define libssh2_sftp_rmdir(sftp, path) \ + libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) + +LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, + int stat_type, + LIBSSH2_SFTP_ATTRIBUTES *attrs); +#define libssh2_sftp_stat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_STAT, \ + (attrs)) +#define libssh2_sftp_lstat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_LSTAT, \ + (attrs)) +#define libssh2_sftp_setstat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_SETSTAT, \ + (attrs)) + +LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, + char *target, + unsigned int target_len, + int link_type); +#define libssh2_sftp_symlink(sftp, orig, linkpath) \ + libssh2_sftp_symlink_ex((sftp), (orig), strlen(orig), (linkpath), \ + strlen(linkpath), LIBSSH2_SFTP_SYMLINK) +#define libssh2_sftp_readlink(sftp, path, target, maxlen) \ + libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), \ + LIBSSH2_SFTP_READLINK) +#define libssh2_sftp_realpath(sftp, path, target, maxlen) \ + libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), \ + LIBSSH2_SFTP_REALPATH) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSSH2_SFTP_H */ +#endif diff --git a/lib/libssh2/mac.c b/lib/libssh2/mac.c new file mode 100644 index 0000000..22327fa --- /dev/null +++ b/lib/libssh2/mac.c @@ -0,0 +1,416 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "mac.h" + +#ifdef LIBSSH2_MAC_NONE +/* mac_none_MAC + * Minimalist MAC: No MAC + */ +static int +mac_none_MAC(LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, const unsigned char *packet, + uint32_t packet_len, const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + return 0; +} + + + + +static LIBSSH2_MAC_METHOD mac_method_none = { + "none", + 0, + 0, + NULL, + mac_none_MAC, + NULL +}; +#endif /* LIBSSH2_MAC_NONE */ + +/* mac_method_common_init + * Initialize simple mac methods + */ +static int +mac_method_common_init(LIBSSH2_SESSION * session, unsigned char *key, + int *free_key, void **abstract) +{ + *abstract = key; + *free_key = 0; + (void) session; + + return 0; +} + + + +/* mac_method_common_dtor + * Cleanup simple mac methods + */ +static int +mac_method_common_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + if(*abstract) { + LIBSSH2_FREE(session, *abstract); + } + *abstract = NULL; + + return 0; +} + + + +#if LIBSSH2_HMAC_SHA512 +/* mac_method_hmac_sha512_hash + * Calculate hash using full sha512 value + */ +static int +mac_method_hmac_sha2_512_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ctx_init(ctx); + libssh2_hmac_sha512_init(&ctx, *abstract, 64); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if(addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_512 = { + "hmac-sha2-512", + 64, + 64, + mac_method_common_init, + mac_method_hmac_sha2_512_hash, + mac_method_common_dtor, +}; +#endif + + + +#if LIBSSH2_HMAC_SHA256 +/* mac_method_hmac_sha256_hash + * Calculate hash using full sha256 value + */ +static int +mac_method_hmac_sha2_256_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ctx_init(ctx); + libssh2_hmac_sha256_init(&ctx, *abstract, 32); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if(addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_256 = { + "hmac-sha2-256", + 32, + 32, + mac_method_common_init, + mac_method_hmac_sha2_256_hash, + mac_method_common_dtor, +}; +#endif + + + + +/* mac_method_hmac_sha1_hash + * Calculate hash using full sha1 value + */ +static int +mac_method_hmac_sha1_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ctx_init(ctx); + libssh2_hmac_sha1_init(&ctx, *abstract, 20); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if(addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1 = { + "hmac-sha1", + 20, + 20, + mac_method_common_init, + mac_method_hmac_sha1_hash, + mac_method_common_dtor, +}; + +/* mac_method_hmac_sha1_96_hash + * Calculate hash using first 96 bits of sha1 value + */ +static int +mac_method_hmac_sha1_96_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + unsigned char temp[SHA_DIGEST_LENGTH]; + + mac_method_hmac_sha1_hash(session, temp, seqno, packet, packet_len, + addtl, addtl_len, abstract); + memcpy(buf, (char *) temp, 96 / 8); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1_96 = { + "hmac-sha1-96", + 12, + 20, + mac_method_common_init, + mac_method_hmac_sha1_96_hash, + mac_method_common_dtor, +}; + +#if LIBSSH2_MD5 +/* mac_method_hmac_md5_hash + * Calculate hash using full md5 value + */ +static int +mac_method_hmac_md5_hash(LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ctx_init(ctx); + libssh2_hmac_md5_init(&ctx, *abstract, 16); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if(addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_md5 = { + "hmac-md5", + 16, + 16, + mac_method_common_init, + mac_method_hmac_md5_hash, + mac_method_common_dtor, +}; + +/* mac_method_hmac_md5_96_hash + * Calculate hash using first 96 bits of md5 value + */ +static int +mac_method_hmac_md5_96_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + unsigned char temp[MD5_DIGEST_LENGTH]; + mac_method_hmac_md5_hash(session, temp, seqno, packet, packet_len, + addtl, addtl_len, abstract); + memcpy(buf, (char *) temp, 96 / 8); + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_md5_96 = { + "hmac-md5-96", + 12, + 16, + mac_method_common_init, + mac_method_hmac_md5_96_hash, + mac_method_common_dtor, +}; +#endif /* LIBSSH2_MD5 */ + +#if LIBSSH2_HMAC_RIPEMD +/* mac_method_hmac_ripemd160_hash + * Calculate hash using ripemd160 value + */ +static int +mac_method_hmac_ripemd160_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, + void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ctx_init(ctx); + libssh2_hmac_ripemd160_init(&ctx, *abstract, 20); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if(addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160 = { + "hmac-ripemd160", + 20, + 20, + mac_method_common_init, + mac_method_hmac_ripemd160_hash, + mac_method_common_dtor, +}; + +static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160_openssh_com = { + "hmac-ripemd160@openssh.com", + 20, + 20, + mac_method_common_init, + mac_method_hmac_ripemd160_hash, + mac_method_common_dtor, +}; +#endif /* LIBSSH2_HMAC_RIPEMD */ + +static const LIBSSH2_MAC_METHOD *mac_methods[] = { +#if LIBSSH2_HMAC_SHA256 + &mac_method_hmac_sha2_256, +#endif +#if LIBSSH2_HMAC_SHA512 + &mac_method_hmac_sha2_512, +#endif + &mac_method_hmac_sha1, + &mac_method_hmac_sha1_96, +#if LIBSSH2_MD5 + &mac_method_hmac_md5, + &mac_method_hmac_md5_96, +#endif +#if LIBSSH2_HMAC_RIPEMD + &mac_method_hmac_ripemd160, + &mac_method_hmac_ripemd160_openssh_com, +#endif /* LIBSSH2_HMAC_RIPEMD */ +#ifdef LIBSSH2_MAC_NONE + &mac_method_none, +#endif /* LIBSSH2_MAC_NONE */ + NULL +}; + +const LIBSSH2_MAC_METHOD ** +_libssh2_mac_methods(void) +{ + return mac_methods; +} +#endif diff --git a/lib/libssh2/mac.h b/lib/libssh2/mac.h new file mode 100644 index 0000000..3441ad0 --- /dev/null +++ b/lib/libssh2/mac.h @@ -0,0 +1,68 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_MAC_H +#define __LIBSSH2_MAC_H +/* Copyright (C) 2009-2010 by Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" + +struct _LIBSSH2_MAC_METHOD +{ + const char *name; + + /* The length of a given MAC packet */ + int mac_len; + + /* integrity key length */ + int key_len; + + /* Message Authentication Code Hashing algo */ + int (*init) (LIBSSH2_SESSION * session, unsigned char *key, int *free_key, + void **abstract); + int (*hash) (LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, const unsigned char *packet, + uint32_t packet_len, const unsigned char *addtl, + uint32_t addtl_len, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); +}; + +typedef struct _LIBSSH2_MAC_METHOD LIBSSH2_MAC_METHOD; + +const LIBSSH2_MAC_METHOD **_libssh2_mac_methods(void); + +#endif /* __LIBSSH2_MAC_H */ +#endif diff --git a/lib/libssh2/mbedtls.c b/lib/libssh2/mbedtls.c new file mode 100644 index 0000000..27e75fc --- /dev/null +++ b/lib/libssh2/mbedtls.c @@ -0,0 +1,1445 @@ +#if defined(ESP32) +/* Copyright (c) 2016, Art + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifdef LIBSSH2_MBEDTLS /* compile only if we build with mbedtls */ + +#if MBEDTLS_VERSION_NUMBER < 0x03000000 +#define mbedtls_cipher_info_get_key_bitlen(c) (c->key_bitlen) +#define mbedtls_cipher_info_get_iv_size(c) (c->iv_size) +#define mbedtls_rsa_get_len(rsa) (rsa->len) + +#define MBEDTLS_PRIVATE(m) m +#endif + +/*******************************************************************/ +/* + * mbedTLS backend: Global context handles + */ + +static mbedtls_entropy_context _libssh2_mbedtls_entropy; +static mbedtls_ctr_drbg_context _libssh2_mbedtls_ctr_drbg; + +/*******************************************************************/ +/* + * mbedTLS backend: Generic functions + */ + +void +_libssh2_mbedtls_init(void) +{ + int ret; + + mbedtls_entropy_init(&_libssh2_mbedtls_entropy); + mbedtls_ctr_drbg_init(&_libssh2_mbedtls_ctr_drbg); + + ret = mbedtls_ctr_drbg_seed(&_libssh2_mbedtls_ctr_drbg, + mbedtls_entropy_func, + &_libssh2_mbedtls_entropy, NULL, 0); + if(ret != 0) + mbedtls_ctr_drbg_free(&_libssh2_mbedtls_ctr_drbg); +} + +void +_libssh2_mbedtls_free(void) +{ + mbedtls_ctr_drbg_free(&_libssh2_mbedtls_ctr_drbg); + mbedtls_entropy_free(&_libssh2_mbedtls_entropy); +} + +int +_libssh2_mbedtls_random(unsigned char *buf, int len) +{ + int ret; + ret = mbedtls_ctr_drbg_random(&_libssh2_mbedtls_ctr_drbg, buf, len); + return ret == 0 ? 0 : -1; +} + +static void +_libssh2_mbedtls_safe_free(void *buf, int len) +{ + if(!buf) + return; + +#ifdef LIBSSH2_CLEAR_MEMORY + if(len > 0) + _libssh2_explicit_zero(buf, len); +#else + (void)len; +#endif + + mbedtls_free(buf); +} + +int +_libssh2_mbedtls_cipher_init(_libssh2_cipher_ctx *ctx, + _libssh2_cipher_type(algo), + unsigned char *iv, + unsigned char *secret, + int encrypt) +{ + const mbedtls_cipher_info_t *cipher_info; + int ret, op; + + if(!ctx) + return -1; + + op = encrypt == 0 ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT; + + cipher_info = mbedtls_cipher_info_from_type(algo); + if(!cipher_info) + return -1; + + mbedtls_cipher_init(ctx); + ret = mbedtls_cipher_setup(ctx, cipher_info); + if(!ret) + ret = mbedtls_cipher_setkey(ctx, + secret, + mbedtls_cipher_info_get_key_bitlen(cipher_info), + op); + + if(!ret) + ret = mbedtls_cipher_set_iv(ctx, iv, + mbedtls_cipher_info_get_iv_size(cipher_info)); + + return ret == 0 ? 0 : -1; +} + +int +_libssh2_mbedtls_cipher_crypt(_libssh2_cipher_ctx *ctx, + _libssh2_cipher_type(algo), + int encrypt, + unsigned char *block, + size_t blocklen) +{ + int ret; + unsigned char *output; + size_t osize, olen, finish_olen; + + (void) encrypt; + (void) algo; + + osize = blocklen + mbedtls_cipher_get_block_size(ctx); + + output = (unsigned char *)mbedtls_calloc(osize, sizeof(char)); + if(output) { + ret = mbedtls_cipher_reset(ctx); + + if(!ret) + ret = mbedtls_cipher_update(ctx, block, blocklen, output, &olen); + + if(!ret) + ret = mbedtls_cipher_finish(ctx, output + olen, &finish_olen); + + if(!ret) { + olen += finish_olen; + memcpy(block, output, olen); + } + + _libssh2_mbedtls_safe_free(output, osize); + } + else + ret = -1; + + return ret == 0 ? 0 : -1; +} + +void +_libssh2_mbedtls_cipher_dtor(_libssh2_cipher_ctx *ctx) +{ + mbedtls_cipher_free(ctx); +} + + +int +_libssh2_mbedtls_hash_init(mbedtls_md_context_t *ctx, + mbedtls_md_type_t mdtype, + const unsigned char *key, unsigned long keylen) +{ + const mbedtls_md_info_t *md_info; + int ret, hmac; + + md_info = mbedtls_md_info_from_type(mdtype); + if(!md_info) + return 0; + + hmac = key == NULL ? 0 : 1; + + mbedtls_md_init(ctx); + ret = mbedtls_md_setup(ctx, md_info, hmac); + if(!ret) { + if(hmac) + ret = mbedtls_md_hmac_starts(ctx, key, keylen); + else + ret = mbedtls_md_starts(ctx); + } + + return ret == 0 ? 1 : 0; +} + +int +_libssh2_mbedtls_hash_final(mbedtls_md_context_t *ctx, unsigned char *hash) +{ + int ret; + + ret = mbedtls_md_finish(ctx, hash); + mbedtls_md_free(ctx); + + return ret == 0 ? 0 : -1; +} + +int +_libssh2_mbedtls_hash(const unsigned char *data, unsigned long datalen, + mbedtls_md_type_t mdtype, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info; + int ret; + + md_info = mbedtls_md_info_from_type(mdtype); + if(!md_info) + return 0; + + ret = mbedtls_md(md_info, data, datalen, hash); + + return ret == 0 ? 0 : -1; +} + +/*******************************************************************/ +/* + * mbedTLS backend: BigNumber functions + */ + +_libssh2_bn * +_libssh2_mbedtls_bignum_init(void) +{ + _libssh2_bn *bignum; + + bignum = (_libssh2_bn *)mbedtls_calloc(1, sizeof(_libssh2_bn)); + if(bignum) { + mbedtls_mpi_init(bignum); + } + + return bignum; +} + +void +_libssh2_mbedtls_bignum_free(_libssh2_bn *bn) +{ + if(bn) { + mbedtls_mpi_free(bn); + mbedtls_free(bn); + } +} + +static int +_libssh2_mbedtls_bignum_random(_libssh2_bn *bn, int bits, int top, int bottom) +{ + size_t len; + int err; + int i; + + if(!bn || bits <= 0) + return -1; + + len = (bits + 7) >> 3; + err = mbedtls_mpi_fill_random(bn, len, mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg); + if(err) + return -1; + + /* Zero unused bits above the most significant bit*/ + for(i = len*8 - 1; bits <= i; --i) { + err = mbedtls_mpi_set_bit(bn, i, 0); + if(err) + return -1; + } + + /* If `top` is -1, the most significant bit of the random number can be + zero. If top is 0, the most significant bit of the random number is + set to 1, and if top is 1, the two most significant bits of the number + will be set to 1, so that the product of two such random numbers will + always have 2*bits length. + */ + for(i = 0; i <= top; ++i) { + err = mbedtls_mpi_set_bit(bn, bits-i-1, 1); + if(err) + return -1; + } + + /* make odd by setting first bit in least significant byte */ + if(bottom) { + err = mbedtls_mpi_set_bit(bn, 0, 1); + if(err) + return -1; + } + + return 0; +} + + +/*******************************************************************/ +/* + * mbedTLS backend: RSA functions + */ + +int +_libssh2_mbedtls_rsa_new(libssh2_rsa_ctx **rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, + unsigned long coefflen) +{ + int ret; + libssh2_rsa_ctx *ctx; + + ctx = (libssh2_rsa_ctx *) mbedtls_calloc(1, sizeof(libssh2_rsa_ctx)); + if(ctx != NULL) { +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + mbedtls_rsa_init(ctx); +#else + mbedtls_rsa_init(ctx, MBEDTLS_RSA_PKCS_V15, 0); +#endif + } + else + return -1; + + /* !checksrc! disable ASSIGNWITHINCONDITION 1 */ + if((ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(E)), + edata, elen) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(N)), + ndata, nlen) ) != 0) { + ret = -1; + } + + if(!ret) { + ctx->MBEDTLS_PRIVATE(len) = + mbedtls_mpi_size(&(ctx->MBEDTLS_PRIVATE(N))); + } + + if(!ret && ddata) { + /* !checksrc! disable ASSIGNWITHINCONDITION 1 */ + if((ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(D)), + ddata, dlen) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(P)), + pdata, plen) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(Q)), + qdata, qlen) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(DP)), + e1data, e1len) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(DQ)), + e2data, e2len) ) != 0 || + (ret = mbedtls_mpi_read_binary(&(ctx->MBEDTLS_PRIVATE(QP)), + coeffdata, coefflen) ) + != 0) { + ret = -1; + } + ret = mbedtls_rsa_check_privkey(ctx); + } + else if(!ret) { + ret = mbedtls_rsa_check_pubkey(ctx); + } + + if(ret && ctx) { + _libssh2_mbedtls_rsa_free(ctx); + ctx = NULL; + } + *rsa = ctx; + return ret; +} + +int +_libssh2_mbedtls_rsa_new_private(libssh2_rsa_ctx **rsa, + LIBSSH2_SESSION *session, + const char *filename, + const unsigned char *passphrase) +{ + int ret; + mbedtls_pk_context pkey; + mbedtls_rsa_context *pk_rsa; + + *rsa = (libssh2_rsa_ctx *) LIBSSH2_ALLOC(session, sizeof(libssh2_rsa_ctx)); + if(*rsa == NULL) + return -1; + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + mbedtls_rsa_init(*rsa); +#else + mbedtls_rsa_init(*rsa, MBEDTLS_RSA_PKCS_V15, 0); +#endif + mbedtls_pk_init(&pkey); + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_keyfile(&pkey, filename, (char *)passphrase, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg); +#else + ret = mbedtls_pk_parse_keyfile(&pkey, filename, (char *)passphrase); +#endif + if(ret != 0 || mbedtls_pk_get_type(&pkey) != MBEDTLS_PK_RSA) { + mbedtls_pk_free(&pkey); + mbedtls_rsa_free(*rsa); + LIBSSH2_FREE(session, *rsa); + *rsa = NULL; + return -1; + } + + pk_rsa = mbedtls_pk_rsa(pkey); + mbedtls_rsa_copy(*rsa, pk_rsa); + mbedtls_pk_free(&pkey); + + return 0; +} + +int +_libssh2_mbedtls_rsa_new_private_frommemory(libssh2_rsa_ctx **rsa, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase) +{ + int ret; + mbedtls_pk_context pkey; + mbedtls_rsa_context *pk_rsa; + void *filedata_nullterm; + size_t pwd_len; + + *rsa = (libssh2_rsa_ctx *) mbedtls_calloc(1, sizeof(libssh2_rsa_ctx)); + if(*rsa == NULL) + return -1; + + /* + mbedtls checks in "mbedtls/pkparse.c:1184" if "key[keylen - 1] != '\0'" + private-key from memory will fail if the last byte is not a null byte + */ + filedata_nullterm = mbedtls_calloc(filedata_len + 1, 1); + if(filedata_nullterm == NULL) { + return -1; + } + memcpy(filedata_nullterm, filedata, filedata_len); + + mbedtls_pk_init(&pkey); + + pwd_len = passphrase != NULL ? strlen((const char *)passphrase) : 0; +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_key(&pkey, (unsigned char *)filedata_nullterm, + filedata_len + 1, + passphrase, pwd_len, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg); +#else + ret = mbedtls_pk_parse_key(&pkey, (unsigned char *)filedata_nullterm, + filedata_len + 1, + passphrase, pwd_len); +#endif + _libssh2_mbedtls_safe_free(filedata_nullterm, filedata_len); + + if(ret != 0 || mbedtls_pk_get_type(&pkey) != MBEDTLS_PK_RSA) { + mbedtls_pk_free(&pkey); + mbedtls_rsa_free(*rsa); + LIBSSH2_FREE(session, *rsa); + *rsa = NULL; + return -1; + } + + pk_rsa = mbedtls_pk_rsa(pkey); + mbedtls_rsa_copy(*rsa, pk_rsa); + mbedtls_pk_free(&pkey); + + return 0; +} + +int +_libssh2_mbedtls_rsa_sha2_verify(libssh2_rsa_ctx * rsactx, + size_t hash_len, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len) +{ + int ret; + int md_type; + unsigned char *hash = malloc(hash_len); + if(hash == NULL) + return -1; + + if(hash_len == SHA_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA1; + } + else if(hash_len == SHA256_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA256; + } + else if(hash_len == SHA512_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA512; + } + else{ + free(hash); + return -1; /* unsupported digest */ + } + ret = _libssh2_mbedtls_hash(m, m_len, md_type, hash); + + if(ret != 0) { + free(hash); + return -1; /* failure */ + } + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_rsa_pkcs1_verify(rsactx, + md_type, hash_len, + hash, sig); +#else + ret = mbedtls_rsa_pkcs1_verify(rsactx, NULL, NULL, MBEDTLS_RSA_PUBLIC, + md_type, hash_len, + hash, sig); +#endif + free(hash); + + return (ret == 0) ? 0 : -1; +} + +int +_libssh2_mbedtls_rsa_sha1_verify(libssh2_rsa_ctx * rsactx, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len) +{ + return _libssh2_mbedtls_rsa_sha2_verify(rsactx, SHA_DIGEST_LENGTH, + sig, sig_len, m, m_len); +} + +int +_libssh2_mbedtls_rsa_sha2_sign(LIBSSH2_SESSION *session, + libssh2_rsa_ctx *rsa, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, + size_t *signature_len) +{ + int ret; + unsigned char *sig; + unsigned int sig_len; + int md_type; + (void)hash_len; + + sig_len = mbedtls_rsa_get_len(rsa); + sig = LIBSSH2_ALLOC(session, sig_len); + if(!sig) { + return -1; + } + ret = 0; + if(hash_len == SHA_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA1; + } + else if(hash_len == SHA256_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA256; + } + else if(hash_len == SHA512_DIGEST_LENGTH) { + md_type = MBEDTLS_MD_SHA512; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unsupported hash digest length"); + ret = -1; + } + if(ret == 0) { +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_rsa_pkcs1_sign(rsa, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg, + md_type, hash_len, + hash, sig); +#else + ret = mbedtls_rsa_pkcs1_sign(rsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, + md_type, hash_len, + hash, sig); +#endif + } + if(ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return (ret == 0) ? 0 : -1; +} + +int +_libssh2_mbedtls_rsa_sha1_sign(LIBSSH2_SESSION * session, + libssh2_rsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, size_t *signature_len) +{ + return _libssh2_mbedtls_rsa_sha2_sign(session, rsactx, hash, hash_len, + signature, signature_len); +} + +void +_libssh2_mbedtls_rsa_free(libssh2_rsa_ctx *ctx) +{ + mbedtls_rsa_free(ctx); + mbedtls_free(ctx); +} + +static unsigned char * +gen_publickey_from_rsa(LIBSSH2_SESSION *session, + mbedtls_rsa_context *rsa, + size_t *keylen) +{ + int e_bytes, n_bytes; + unsigned long len; + unsigned char *key; + unsigned char *p; + + e_bytes = mbedtls_mpi_size(&rsa->MBEDTLS_PRIVATE(E)); + n_bytes = mbedtls_mpi_size(&rsa->MBEDTLS_PRIVATE(N)); + + /* Key form is "ssh-rsa" + e + n. */ + len = 4 + 7 + 4 + e_bytes + 4 + n_bytes; + + key = LIBSSH2_ALLOC(session, len); + if(!key) { + return NULL; + } + + /* Process key encoding. */ + p = key; + + _libssh2_htonu32(p, 7); /* Key type. */ + p += 4; + memcpy(p, "ssh-rsa", 7); + p += 7; + + _libssh2_htonu32(p, e_bytes); + p += 4; + mbedtls_mpi_write_binary(&rsa->MBEDTLS_PRIVATE(E), p, e_bytes); + + _libssh2_htonu32(p, n_bytes); + p += 4; + mbedtls_mpi_write_binary(&rsa->MBEDTLS_PRIVATE(N), p, n_bytes); + + *keylen = (size_t)(p - key); + return key; +} + +static int +_libssh2_mbedtls_pub_priv_key(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + mbedtls_pk_context *pkey) +{ + unsigned char *key = NULL, *mth = NULL; + size_t keylen = 0, mthlen = 0; + int ret; + mbedtls_rsa_context *rsa; + + if(mbedtls_pk_get_type(pkey) != MBEDTLS_PK_RSA) { + mbedtls_pk_free(pkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Key type not supported"); + } + + /* write method */ + mthlen = 7; + mth = LIBSSH2_ALLOC(session, mthlen); + if(mth) { + memcpy(mth, "ssh-rsa", mthlen); + } + else { + ret = -1; + } + + rsa = mbedtls_pk_rsa(*pkey); + key = gen_publickey_from_rsa(session, rsa, &keylen); + if(key == NULL) { + ret = -1; + } + + /* write output */ + if(ret) { + if(mth) + LIBSSH2_FREE(session, mth); + if(key) + LIBSSH2_FREE(session, key); + } + else { + *method = mth; + *method_len = mthlen; + *pubkeydata = key; + *pubkeydata_len = keylen; + } + + return ret; +} + +int +_libssh2_mbedtls_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase) +{ + mbedtls_pk_context pkey; + char buf[1024]; + int ret; + + mbedtls_pk_init(&pkey); +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_keyfile(&pkey, privatekey, passphrase, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg); +#else + ret = mbedtls_pk_parse_keyfile(&pkey, privatekey, passphrase); +#endif + if(ret != 0) { + mbedtls_strerror(ret, (char *)buf, sizeof(buf)); + mbedtls_pk_free(&pkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, buf); + } + + ret = _libssh2_mbedtls_pub_priv_key(session, method, method_len, + pubkeydata, pubkeydata_len, &pkey); + + mbedtls_pk_free(&pkey); + + return ret; +} + +int +_libssh2_mbedtls_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + mbedtls_pk_context pkey; + char buf[1024]; + int ret; + void *privatekeydata_nullterm; + size_t pwd_len; + + /* + mbedtls checks in "mbedtls/pkparse.c:1184" if "key[keylen - 1] != '\0'" + private-key from memory will fail if the last byte is not a null byte + */ + privatekeydata_nullterm = mbedtls_calloc(privatekeydata_len + 1, 1); + if(privatekeydata_nullterm == NULL) { + return -1; + } + memcpy(privatekeydata_nullterm, privatekeydata, privatekeydata_len); + + mbedtls_pk_init(&pkey); + + pwd_len = passphrase != NULL ? strlen((const char *)passphrase) : 0; +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_key(&pkey, + (unsigned char *)privatekeydata_nullterm, + privatekeydata_len + 1, + (const unsigned char *)passphrase, pwd_len, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg); +#else + ret = mbedtls_pk_parse_key(&pkey, + (unsigned char *)privatekeydata_nullterm, + privatekeydata_len + 1, + (const unsigned char *)passphrase, pwd_len); +#endif + _libssh2_mbedtls_safe_free(privatekeydata_nullterm, privatekeydata_len); + + if(ret != 0) { + mbedtls_strerror(ret, (char *)buf, sizeof(buf)); + mbedtls_pk_free(&pkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, buf); + } + + ret = _libssh2_mbedtls_pub_priv_key(session, method, method_len, + pubkeydata, pubkeydata_len, &pkey); + + mbedtls_pk_free(&pkey); + + return ret; +} + +int +_libssh2_mbedtls_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public SK key from private key file: " + "Method unimplemented in mbedTLS backend"); +} + +void _libssh2_init_aes_ctr(void) +{ + /* no implementation */ +} + + +/*******************************************************************/ +/* + * mbedTLS backend: Diffie-Hellman functions + */ + +void +_libssh2_dh_init(_libssh2_dh_ctx *dhctx) +{ + *dhctx = _libssh2_mbedtls_bignum_init(); /* Random from client */ +} + +int +_libssh2_dh_key_pair(_libssh2_dh_ctx *dhctx, _libssh2_bn *public, + _libssh2_bn *g, _libssh2_bn *p, int group_order) +{ + /* Generate x and e */ + _libssh2_mbedtls_bignum_random(*dhctx, group_order * 8 - 1, 0, -1); + mbedtls_mpi_exp_mod(public, g, *dhctx, p, NULL); + return 0; +} + +int +_libssh2_dh_secret(_libssh2_dh_ctx *dhctx, _libssh2_bn *secret, + _libssh2_bn *f, _libssh2_bn *p) +{ + /* Compute the shared secret */ + mbedtls_mpi_exp_mod(secret, f, *dhctx, p, NULL); + return 0; +} + +void +_libssh2_dh_dtor(_libssh2_dh_ctx *dhctx) +{ + _libssh2_mbedtls_bignum_free(*dhctx); + *dhctx = NULL; +} + +#if LIBSSH2_ECDSA + +/*******************************************************************/ +/* + * mbedTLS backend: ECDSA functions + */ + +/* + * _libssh2_ecdsa_create_key + * + * Creates a local private key based on input curve + * and returns octal value and octal length + * + */ + +int +_libssh2_mbedtls_ecdsa_create_key(LIBSSH2_SESSION *session, + _libssh2_ec_key **privkey, + unsigned char **pubkey_oct, + size_t *pubkey_oct_len, + libssh2_curve_type curve) +{ + size_t plen = 0; + + *privkey = LIBSSH2_ALLOC(session, sizeof(mbedtls_ecp_keypair)); + + if(*privkey == NULL) + goto failed; + + mbedtls_ecdsa_init(*privkey); + + if(mbedtls_ecdsa_genkey(*privkey, (mbedtls_ecp_group_id)curve, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg) != 0) + goto failed; + + plen = 2 * mbedtls_mpi_size(&(*privkey)->MBEDTLS_PRIVATE(grp).P) + 1; + *pubkey_oct = LIBSSH2_ALLOC(session, plen); + + if(*pubkey_oct == NULL) + goto failed; + + if(mbedtls_ecp_point_write_binary(&(*privkey)->MBEDTLS_PRIVATE(grp), + &(*privkey)->MBEDTLS_PRIVATE(Q), + MBEDTLS_ECP_PF_UNCOMPRESSED, + pubkey_oct_len, *pubkey_oct, plen) == 0) + return 0; + +failed: + + _libssh2_mbedtls_ecdsa_free(*privkey); + _libssh2_mbedtls_safe_free(*pubkey_oct, plen); + *privkey = NULL; + + return -1; +} + +/* _libssh2_ecdsa_curve_name_with_octal_new + * + * Creates a new public key given an octal string, length and type + * + */ + +int +_libssh2_mbedtls_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx **ctx, + const unsigned char *k, + size_t k_len, + libssh2_curve_type curve) +{ + *ctx = mbedtls_calloc(1, sizeof(mbedtls_ecp_keypair)); + + if(*ctx == NULL) + goto failed; + + mbedtls_ecdsa_init(*ctx); + + if(mbedtls_ecp_group_load(&(*ctx)->MBEDTLS_PRIVATE(grp), + (mbedtls_ecp_group_id)curve) != 0) + goto failed; + + if(mbedtls_ecp_point_read_binary(&(*ctx)->MBEDTLS_PRIVATE(grp), + &(*ctx)->MBEDTLS_PRIVATE(Q), + k, k_len) != 0) + goto failed; + + if(mbedtls_ecp_check_pubkey(&(*ctx)->MBEDTLS_PRIVATE(grp), + &(*ctx)->MBEDTLS_PRIVATE(Q)) == 0) + return 0; + +failed: + + _libssh2_mbedtls_ecdsa_free(*ctx); + *ctx = NULL; + + return -1; +} + +/* _libssh2_ecdh_gen_k + * + * Computes the shared secret K given a local private key, + * remote public key and length + */ + +int +_libssh2_mbedtls_ecdh_gen_k(_libssh2_bn **k, + _libssh2_ec_key *privkey, + const unsigned char *server_pubkey, + size_t server_pubkey_len) +{ + mbedtls_ecp_point pubkey; + int rc = 0; + + if(*k == NULL) + return -1; + + mbedtls_ecp_point_init(&pubkey); + + if(mbedtls_ecp_point_read_binary(&privkey->MBEDTLS_PRIVATE(grp), + &pubkey, + server_pubkey, server_pubkey_len) != 0) { + rc = -1; + goto cleanup; + } + + if(mbedtls_ecdh_compute_shared(&privkey->MBEDTLS_PRIVATE(grp), *k, + &pubkey, + &privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg) != 0) { + rc = -1; + goto cleanup; + } + + if(mbedtls_ecp_check_privkey(&privkey->MBEDTLS_PRIVATE(grp), *k) != 0) + rc = -1; + +cleanup: + + mbedtls_ecp_point_free(&pubkey); + + return rc; +} + +#define LIBSSH2_MBEDTLS_ECDSA_VERIFY(digest_type) \ +{ \ + unsigned char hsh[SHA##digest_type##_DIGEST_LENGTH]; \ + \ + if(libssh2_sha##digest_type(m, m_len, hsh) == 0) { \ + rc = mbedtls_ecdsa_verify(&ctx->MBEDTLS_PRIVATE(grp), hsh, \ + SHA##digest_type##_DIGEST_LENGTH, \ + &ctx->MBEDTLS_PRIVATE(Q), &pr, &ps); \ + } \ + \ +} + +/* _libssh2_ecdsa_sign + * + * Verifies the ECDSA signature of a hashed message + * + */ + +int +_libssh2_mbedtls_ecdsa_verify(libssh2_ecdsa_ctx *ctx, + const unsigned char *r, size_t r_len, + const unsigned char *s, size_t s_len, + const unsigned char *m, size_t m_len) +{ + mbedtls_mpi pr, ps; + int rc = -1; + + mbedtls_mpi_init(&pr); + mbedtls_mpi_init(&ps); + + if(mbedtls_mpi_read_binary(&pr, r, r_len) != 0) + goto cleanup; + + if(mbedtls_mpi_read_binary(&ps, s, s_len) != 0) + goto cleanup; + + switch(_libssh2_ecdsa_get_curve_type(ctx)) { + case LIBSSH2_EC_CURVE_NISTP256: + LIBSSH2_MBEDTLS_ECDSA_VERIFY(256); + break; + case LIBSSH2_EC_CURVE_NISTP384: + LIBSSH2_MBEDTLS_ECDSA_VERIFY(384); + break; + case LIBSSH2_EC_CURVE_NISTP521: + LIBSSH2_MBEDTLS_ECDSA_VERIFY(512); + break; + default: + rc = -1; + } + +cleanup: + + mbedtls_mpi_free(&pr); + mbedtls_mpi_free(&ps); + + return (rc == 0) ? 0 : -1; +} + +static int +_libssh2_mbedtls_parse_eckey(libssh2_ecdsa_ctx **ctx, + mbedtls_pk_context *pkey, + LIBSSH2_SESSION *session, + const unsigned char *data, + size_t data_len, + const unsigned char *pwd) +{ + size_t pwd_len; + + pwd_len = pwd ? strlen((const char *) pwd) : 0; + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + if(mbedtls_pk_parse_key(pkey, data, data_len, pwd, pwd_len, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg) != 0) + + goto failed; +#else + if(mbedtls_pk_parse_key(pkey, data, data_len, pwd, pwd_len) != 0) + goto failed; +#endif + + if(mbedtls_pk_get_type(pkey) != MBEDTLS_PK_ECKEY) + goto failed; + + *ctx = LIBSSH2_ALLOC(session, sizeof(libssh2_ecdsa_ctx)); + + if(*ctx == NULL) + goto failed; + + mbedtls_ecdsa_init(*ctx); + + if(mbedtls_ecdsa_from_keypair(*ctx, mbedtls_pk_ec(*pkey)) == 0) + return 0; + +failed: + + _libssh2_mbedtls_ecdsa_free(*ctx); + *ctx = NULL; + + return -1; +} + +static int +_libssh2_mbedtls_parse_openssh_key(libssh2_ecdsa_ctx **ctx, + LIBSSH2_SESSION *session, + const unsigned char *data, + size_t data_len, + const unsigned char *pwd) +{ + libssh2_curve_type type; + unsigned char *name = NULL; + struct string_buf *decrypted = NULL; + size_t curvelen, exponentlen, pointlen; + unsigned char *curve, *exponent, *point_buf; + + if(_libssh2_openssh_pem_parse_memory(session, pwd, + (const char *)data, data_len, + &decrypted) != 0) + goto failed; + + if(_libssh2_get_string(decrypted, &name, NULL) != 0) + goto failed; + + if(_libssh2_mbedtls_ecdsa_curve_type_from_name((const char *)name, + &type) != 0) + goto failed; + + if(_libssh2_get_string(decrypted, &curve, &curvelen) != 0) + goto failed; + + if(_libssh2_get_string(decrypted, &point_buf, &pointlen) != 0) + goto failed; + + if(_libssh2_get_bignum_bytes(decrypted, &exponent, &exponentlen) != 0) + goto failed; + + *ctx = LIBSSH2_ALLOC(session, sizeof(libssh2_ecdsa_ctx)); + + if(*ctx == NULL) + goto failed; + + mbedtls_ecdsa_init(*ctx); + + if(mbedtls_ecp_group_load(&(*ctx)->MBEDTLS_PRIVATE(grp), + (mbedtls_ecp_group_id)type) != 0) + goto failed; + + if(mbedtls_mpi_read_binary(&(*ctx)->MBEDTLS_PRIVATE(d), + exponent, exponentlen) != 0) + goto failed; + + if(mbedtls_ecp_mul(&(*ctx)->MBEDTLS_PRIVATE(grp), + &(*ctx)->MBEDTLS_PRIVATE(Q), + &(*ctx)->MBEDTLS_PRIVATE(d), + &(*ctx)->MBEDTLS_PRIVATE(grp).G, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg) != 0) + goto failed; + + if(mbedtls_ecp_check_privkey(&(*ctx)->MBEDTLS_PRIVATE(grp), + &(*ctx)->MBEDTLS_PRIVATE(d)) == 0) + goto cleanup; + +failed: + + _libssh2_mbedtls_ecdsa_free(*ctx); + *ctx = NULL; + +cleanup: + + if(decrypted) { + _libssh2_string_buf_free(session, decrypted); + } + + return (*ctx == NULL) ? -1 : 0; +} + +/* _libssh2_ecdsa_new_private + * + * Creates a new private key given a file path and password + * + */ + +int +_libssh2_mbedtls_ecdsa_new_private(libssh2_ecdsa_ctx **ctx, + LIBSSH2_SESSION *session, + const char *filename, + const unsigned char *pwd) +{ + mbedtls_pk_context pkey; + unsigned char *data; + size_t data_len; + + if(mbedtls_pk_load_file(filename, &data, &data_len) != 0) + goto cleanup; + + mbedtls_pk_init(&pkey); + + if(_libssh2_mbedtls_parse_eckey(ctx, &pkey, session, + data, data_len, pwd) == 0) + goto cleanup; + + _libssh2_mbedtls_parse_openssh_key(ctx, session, data, data_len, pwd); + +cleanup: + + mbedtls_pk_free(&pkey); + + _libssh2_mbedtls_safe_free(data, data_len); + + return (*ctx == NULL) ? -1 : 0; +} + +/* _libssh2_ecdsa_new_private + * + * Creates a new private key given a file data and password + * + */ + +int +_libssh2_mbedtls_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx **ctx, + LIBSSH2_SESSION *session, + const char *data, + size_t data_len, + const unsigned char *pwd) +{ + unsigned char *ntdata; + mbedtls_pk_context pkey; + + mbedtls_pk_init(&pkey); + + ntdata = LIBSSH2_ALLOC(session, data_len + 1); + + if(ntdata == NULL) + goto cleanup; + + memcpy(ntdata, data, data_len); + + if(_libssh2_mbedtls_parse_eckey(ctx, &pkey, session, + ntdata, data_len + 1, pwd) == 0) + goto cleanup; + + _libssh2_mbedtls_parse_openssh_key(ctx, session, + ntdata, data_len + 1, pwd); + +cleanup: + + mbedtls_pk_free(&pkey); + + _libssh2_mbedtls_safe_free(ntdata, data_len); + + return (*ctx == NULL) ? -1 : 0; +} + +static unsigned char * +_libssh2_mbedtls_mpi_write_binary(unsigned char *buf, + const mbedtls_mpi *mpi, + size_t bytes) +{ + unsigned char *p = buf; + + if(sizeof(&p) / sizeof(p[0]) < 4) { + goto done; + } + + p += 4; + *p = 0; + + if(bytes > 0) { + mbedtls_mpi_write_binary(mpi, p + 1, bytes - 1); + } + + if(bytes > 0 && !(*(p + 1) & 0x80)) { + memmove(p, p + 1, --bytes); + } + + _libssh2_htonu32(p - 4, bytes); + +done: + + return p + bytes; +} + +/* _libssh2_ecdsa_sign + * + * Computes the ECDSA signature of a previously-hashed message + * + */ + +int +_libssh2_mbedtls_ecdsa_sign(LIBSSH2_SESSION *session, + libssh2_ecdsa_ctx *ctx, + const unsigned char *hash, + unsigned long hash_len, + unsigned char **sign, + size_t *sign_len) +{ + size_t r_len, s_len, tmp_sign_len = 0; + unsigned char *sp, *tmp_sign = NULL; + mbedtls_mpi pr, ps; + + mbedtls_mpi_init(&pr); + mbedtls_mpi_init(&ps); + + if(mbedtls_ecdsa_sign(&ctx->MBEDTLS_PRIVATE(grp), &pr, &ps, + &ctx->MBEDTLS_PRIVATE(d), + hash, hash_len, + mbedtls_ctr_drbg_random, + &_libssh2_mbedtls_ctr_drbg) != 0) + goto cleanup; + + r_len = mbedtls_mpi_size(&pr) + 1; + s_len = mbedtls_mpi_size(&ps) + 1; + tmp_sign_len = r_len + s_len + 8; + + tmp_sign = LIBSSH2_CALLOC(session, tmp_sign_len); + + if(tmp_sign == NULL) + goto cleanup; + + sp = tmp_sign; + sp = _libssh2_mbedtls_mpi_write_binary(sp, &pr, r_len); + sp = _libssh2_mbedtls_mpi_write_binary(sp, &ps, s_len); + + *sign_len = (size_t)(sp - tmp_sign); + + *sign = LIBSSH2_CALLOC(session, *sign_len); + + if(*sign == NULL) + goto cleanup; + + memcpy(*sign, tmp_sign, *sign_len); + +cleanup: + + mbedtls_mpi_free(&pr); + mbedtls_mpi_free(&ps); + + _libssh2_mbedtls_safe_free(tmp_sign, tmp_sign_len); + + return (*sign == NULL) ? -1 : 0; +} + +/* _libssh2_ecdsa_get_curve_type + * + * returns key curve type that maps to libssh2_curve_type + * + */ + +libssh2_curve_type +_libssh2_mbedtls_ecdsa_get_curve_type(libssh2_ecdsa_ctx *ctx) +{ + return (libssh2_curve_type) ctx->MBEDTLS_PRIVATE(grp).id; +} + +/* _libssh2_ecdsa_curve_type_from_name + * + * returns 0 for success, key curve type that maps to libssh2_curve_type + * + */ + +int +_libssh2_mbedtls_ecdsa_curve_type_from_name(const char *name, + libssh2_curve_type *out_type) +{ + int ret = 0; + libssh2_curve_type type; + + if(name == NULL || strlen(name) != 19) + return -1; + + if(strcmp(name, "ecdsa-sha2-nistp256") == 0) + type = LIBSSH2_EC_CURVE_NISTP256; + else if(strcmp(name, "ecdsa-sha2-nistp384") == 0) + type = LIBSSH2_EC_CURVE_NISTP384; + else if(strcmp(name, "ecdsa-sha2-nistp521") == 0) + type = LIBSSH2_EC_CURVE_NISTP521; + else { + ret = -1; + } + + if(ret == 0 && out_type) { + *out_type = type; + } + + return ret; +} + +void +_libssh2_mbedtls_ecdsa_free(libssh2_ecdsa_ctx *ctx) +{ + mbedtls_ecdsa_free(ctx); + mbedtls_free(ctx); +} + + +/* _libssh2_supported_key_sign_algorithms + * + * Return supported key hash algo upgrades, see crypto.h + * + */ + +const char * +_libssh2_supported_key_sign_algorithms(LIBSSH2_SESSION *session, + unsigned char *key_method, + size_t key_method_len) +{ + (void)session; + +#if LIBSSH2_RSA_SHA2 + if(key_method_len == 7 && + memcmp(key_method, "ssh-rsa", key_method_len) == 0) { + return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + } +#endif + + return NULL; +} + +#endif /* LIBSSH2_ECDSA */ +#endif /* LIBSSH2_MBEDTLS */ +#endif diff --git a/lib/libssh2/mbedtls.h b/lib/libssh2/mbedtls.h new file mode 100644 index 0000000..fbef109 --- /dev/null +++ b/lib/libssh2/mbedtls.h @@ -0,0 +1,611 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_MBEDTLS_H +#define __LIBSSH2_MBEDTLS_H +/* Copyright (c) 2016, Art + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#ifdef MBEDTLS_ECDH_C +# include +#endif +#ifdef MBEDTLS_ECDSA_C +# include +#endif +#include +#include +#include +#include + +/* Define which features are supported. */ +#define LIBSSH2_MD5 1 + +#define LIBSSH2_HMAC_RIPEMD 1 +#define LIBSSH2_HMAC_SHA256 1 +#define LIBSSH2_HMAC_SHA512 1 + +#define LIBSSH2_AES 1 +#define LIBSSH2_AES_CTR 1 +#ifdef MBEDTLS_CIPHER_BLOWFISH_CBC +# define LIBSSH2_BLOWFISH 1 +#else +# define LIBSSH2_BLOWFISH 0 +#endif +#ifdef MBEDTLS_CIPHER_ARC4_128 +# define LIBSSH2_RC4 1 +#else +# define LIBSSH2_RC4 0 +#endif +#define LIBSSH2_CAST 0 +#define LIBSSH2_3DES 1 + +#define LIBSSH2_RSA 1 +#define LIBSSH2_RSA_SHA2 1 +#define LIBSSH2_DSA 0 +#ifdef MBEDTLS_ECDSA_C +# define LIBSSH2_ECDSA 1 +#else +# define LIBSSH2_ECDSA 0 +#endif +#define LIBSSH2_ED25519 0 + +#define MD5_DIGEST_LENGTH 16 +#define SHA_DIGEST_LENGTH 20 +#define SHA256_DIGEST_LENGTH 32 +#define SHA384_DIGEST_LENGTH 48 +#define SHA512_DIGEST_LENGTH 64 + +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) + + +/*******************************************************************/ +/* + * mbedTLS backend: Generic functions + */ + +#define libssh2_crypto_init() \ + _libssh2_mbedtls_init() +#define libssh2_crypto_exit() \ + _libssh2_mbedtls_free() + +#define _libssh2_random(buf, len) \ + _libssh2_mbedtls_random(buf, len) + +#define libssh2_prepare_iovec(vec, len) /* Empty. */ + + +/*******************************************************************/ +/* + * mbedTLS backend: HMAC functions + */ + +#define libssh2_hmac_ctx mbedtls_md_context_t + +#define libssh2_hmac_ctx_init(ctx) +#define libssh2_hmac_cleanup(pctx) \ + mbedtls_md_free(pctx) +#define libssh2_hmac_update(ctx, data, datalen) \ + mbedtls_md_hmac_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_hmac_final(ctx, hash) \ + mbedtls_md_hmac_finish(&ctx, hash) + +#define libssh2_hmac_sha1_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA1, key, keylen) +#define libssh2_hmac_md5_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_MD5, key, keylen) +#define libssh2_hmac_ripemd160_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_RIPEMD160, key, keylen) +#define libssh2_hmac_sha256_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA256, key, keylen) +#define libssh2_hmac_sha384_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA384, key, keylen) +#define libssh2_hmac_sha512_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA512, key, keylen) + + +/*******************************************************************/ +/* + * mbedTLS backend: SHA1 functions + */ + +#define libssh2_sha1_ctx mbedtls_md_context_t + +#define libssh2_sha1_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA1, NULL, 0) +#define libssh2_sha1_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_sha1_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_sha1(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA1, hash) + +/*******************************************************************/ +/* + * mbedTLS backend: SHA256 functions + */ + +#define libssh2_sha256_ctx mbedtls_md_context_t + +#define libssh2_sha256_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA256, NULL, 0) +#define libssh2_sha256_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_sha256_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_sha256(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA256, hash) + + +/*******************************************************************/ +/* + * mbedTLS backend: SHA384 functions + */ + +#define libssh2_sha384_ctx mbedtls_md_context_t + +#define libssh2_sha384_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA384, NULL, 0) +#define libssh2_sha384_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_sha384_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_sha384(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA384, hash) + + +/*******************************************************************/ +/* + * mbedTLS backend: SHA512 functions + */ + +#define libssh2_sha512_ctx mbedtls_md_context_t + +#define libssh2_sha512_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA512, NULL, 0) +#define libssh2_sha512_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_sha512_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_sha512(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA512, hash) + + +/*******************************************************************/ +/* + * mbedTLS backend: MD5 functions + */ + +#define libssh2_md5_ctx mbedtls_md_context_t + +#define libssh2_md5_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_MD5, NULL, 0) +#define libssh2_md5_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_md5_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_md5(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_MD5, hash) + + +/*******************************************************************/ +/* + * mbedTLS backend: RSA functions + */ + +#define libssh2_rsa_ctx mbedtls_rsa_context + +#define _libssh2_rsa_new(rsactx, e, e_len, n, n_len, \ + d, d_len, p, p_len, q, q_len, \ + e1, e1_len, e2, e2_len, c, c_len) \ + _libssh2_mbedtls_rsa_new(rsactx, e, e_len, n, n_len, \ + d, d_len, p, p_len, q, q_len, \ + e1, e1_len, e2, e2_len, c, c_len) + +#define _libssh2_rsa_new_private(rsactx, s, filename, passphrase) \ + _libssh2_mbedtls_rsa_new_private(rsactx, s, filename, passphrase) + +#define _libssh2_rsa_new_private_frommemory(rsactx, s, filedata, \ + filedata_len, passphrase) \ + _libssh2_mbedtls_rsa_new_private_frommemory(rsactx, s, filedata, \ + filedata_len, passphrase) + +#define _libssh2_rsa_sha1_sign(s, rsactx, hash, hash_len, sig, sig_len) \ + _libssh2_mbedtls_rsa_sha1_sign(s, rsactx, hash, hash_len, sig, sig_len) + +#define _libssh2_rsa_sha2_sign(s, rsactx, hash, hash_len, sig, sig_len) \ + _libssh2_mbedtls_rsa_sha2_sign(s, rsactx, hash, hash_len, sig, sig_len) + + +#define _libssh2_rsa_sha1_verify(rsactx, sig, sig_len, m, m_len) \ + _libssh2_mbedtls_rsa_sha1_verify(rsactx, sig, sig_len, m, m_len) + +#define _libssh2_rsa_sha2_verify(rsactx, hash_len, sig, sig_len, m, m_len) \ + _libssh2_mbedtls_rsa_sha2_verify(rsactx, hash_len, sig, sig_len, m, m_len) + +#define _libssh2_rsa_free(rsactx) \ + _libssh2_mbedtls_rsa_free(rsactx) + + +/*******************************************************************/ +/* + * mbedTLS backend: ECDSA structures + */ + +#if LIBSSH2_ECDSA + +typedef enum { +#ifdef MBEDTLS_ECP_DP_SECP256R1_ENABLED + LIBSSH2_EC_CURVE_NISTP256 = MBEDTLS_ECP_DP_SECP256R1, +#else + LIBSSH2_EC_CURVE_NISTP256 = MBEDTLS_ECP_DP_NONE, +#endif +#ifdef MBEDTLS_ECP_DP_SECP384R1_ENABLED + LIBSSH2_EC_CURVE_NISTP384 = MBEDTLS_ECP_DP_SECP384R1, +#else + LIBSSH2_EC_CURVE_NISTP384 = MBEDTLS_ECP_DP_NONE, +#endif +#ifdef MBEDTLS_ECP_DP_SECP521R1_ENABLED + LIBSSH2_EC_CURVE_NISTP521 = MBEDTLS_ECP_DP_SECP521R1 +#else + LIBSSH2_EC_CURVE_NISTP521 = MBEDTLS_ECP_DP_NONE, +#endif +} libssh2_curve_type; + +# define _libssh2_ec_key mbedtls_ecp_keypair +#else +# define _libssh2_ec_key void +#endif /* LIBSSH2_ECDSA */ + + +/*******************************************************************/ +/* + * mbedTLS backend: ECDSA functions + */ + +#if LIBSSH2_ECDSA + +#define libssh2_ecdsa_ctx mbedtls_ecdsa_context + +#define _libssh2_ecdsa_create_key(session, privkey, pubkey_octal, \ + pubkey_octal_len, curve) \ + _libssh2_mbedtls_ecdsa_create_key(session, privkey, pubkey_octal, \ + pubkey_octal_len, curve) + +#define _libssh2_ecdsa_curve_name_with_octal_new(ctx, k, k_len, curve) \ + _libssh2_mbedtls_ecdsa_curve_name_with_octal_new(ctx, k, k_len, curve) + +#define _libssh2_ecdh_gen_k(k, privkey, server_pubkey, server_pubkey_len) \ + _libssh2_mbedtls_ecdh_gen_k(k, privkey, server_pubkey, server_pubkey_len) + +#define _libssh2_ecdsa_verify(ctx, r, r_len, s, s_len, m, m_len) \ + _libssh2_mbedtls_ecdsa_verify(ctx, r, r_len, s, s_len, m, m_len) + +#define _libssh2_ecdsa_new_private(ctx, session, filename, passphrase) \ + _libssh2_mbedtls_ecdsa_new_private(ctx, session, filename, passphrase) + +#define _libssh2_ecdsa_new_private_frommemory(ctx, session, filedata, \ + filedata_len, passphrase) \ + _libssh2_mbedtls_ecdsa_new_private_frommemory(ctx, session, filedata, \ + filedata_len, passphrase) + +#define _libssh2_ecdsa_sign(session, ctx, hash, hash_len, sign, sign_len) \ + _libssh2_mbedtls_ecdsa_sign(session, ctx, hash, hash_len, sign, sign_len) + +#define _libssh2_ecdsa_get_curve_type(ctx) \ + _libssh2_mbedtls_ecdsa_get_curve_type(ctx) + +#define _libssh2_ecdsa_free(ctx) \ + _libssh2_mbedtls_ecdsa_free(ctx) + +#endif /* LIBSSH2_ECDSA */ + + +/*******************************************************************/ +/* + * mbedTLS backend: Key functions + */ + +#define _libssh2_pub_priv_keyfile(s, m, m_len, p, p_len, pk, pw) \ + _libssh2_mbedtls_pub_priv_keyfile(s, m, m_len, p, p_len, pk, pw) +#define _libssh2_pub_priv_keyfilememory(s, m, m_len, p, p_len, \ + pk, pk_len, pw) \ + _libssh2_mbedtls_pub_priv_keyfilememory(s, m, m_len, p, p_len, \ + pk, pk_len, pw) +#define _libssh2_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) \ + _libssh2_mbedtls_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) + + +/*******************************************************************/ +/* + * mbedTLS backend: Cipher Context structure + */ + +#define _libssh2_cipher_ctx mbedtls_cipher_context_t + +#define _libssh2_cipher_type(algo) mbedtls_cipher_type_t algo + +#define _libssh2_cipher_aes256ctr MBEDTLS_CIPHER_AES_256_CTR +#define _libssh2_cipher_aes192ctr MBEDTLS_CIPHER_AES_192_CTR +#define _libssh2_cipher_aes128ctr MBEDTLS_CIPHER_AES_128_CTR +#define _libssh2_cipher_aes256 MBEDTLS_CIPHER_AES_256_CBC +#define _libssh2_cipher_aes192 MBEDTLS_CIPHER_AES_192_CBC +#define _libssh2_cipher_aes128 MBEDTLS_CIPHER_AES_128_CBC +#ifdef MBEDTLS_CIPHER_BLOWFISH_CBC +#define _libssh2_cipher_blowfish MBEDTLS_CIPHER_BLOWFISH_CBC +#endif +#ifdef MBEDTLS_CIPHER_ARC4_128 +#define _libssh2_cipher_arcfour MBEDTLS_CIPHER_ARC4_128 +#endif +#define _libssh2_cipher_3des MBEDTLS_CIPHER_DES_EDE3_CBC + + +/*******************************************************************/ +/* + * mbedTLS backend: Cipher functions + */ + +#define _libssh2_cipher_init(ctx, type, iv, secret, encrypt) \ + _libssh2_mbedtls_cipher_init(ctx, type, iv, secret, encrypt) +#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen) \ + _libssh2_mbedtls_cipher_crypt(ctx, type, encrypt, block, blocklen) +#define _libssh2_cipher_dtor(ctx) \ + _libssh2_mbedtls_cipher_dtor(ctx) + + +/*******************************************************************/ +/* + * mbedTLS backend: BigNumber Support + */ + +#define _libssh2_bn_ctx int /* not used */ +#define _libssh2_bn_ctx_new() 0 /* not used */ +#define _libssh2_bn_ctx_free(bnctx) ((void)0) /* not used */ + +#define _libssh2_bn mbedtls_mpi + +#define _libssh2_bn_init() \ + _libssh2_mbedtls_bignum_init() +#define _libssh2_bn_init_from_bin() \ + _libssh2_mbedtls_bignum_init() +#define _libssh2_bn_set_word(bn, word) \ + mbedtls_mpi_lset(bn, word) +#define _libssh2_bn_from_bin(bn, len, bin) \ + mbedtls_mpi_read_binary(bn, bin, len) +#define _libssh2_bn_to_bin(bn, bin) \ + mbedtls_mpi_write_binary(bn, bin, mbedtls_mpi_size(bn)) +#define _libssh2_bn_bytes(bn) \ + mbedtls_mpi_size(bn) +#define _libssh2_bn_bits(bn) \ + mbedtls_mpi_bitlen(bn) +#define _libssh2_bn_free(bn) \ + _libssh2_mbedtls_bignum_free(bn) + + +/*******************************************************************/ +/* + * mbedTLS backend: Diffie-Hellman support. + */ + +#define _libssh2_dh_ctx mbedtls_mpi * +#define libssh2_dh_init(dhctx) _libssh2_dh_init(dhctx) +#define libssh2_dh_key_pair(dhctx, public, g, p, group_order, bnctx) \ + _libssh2_dh_key_pair(dhctx, public, g, p, group_order) +#define libssh2_dh_secret(dhctx, secret, f, p, bnctx) \ + _libssh2_dh_secret(dhctx, secret, f, p) +#define libssh2_dh_dtor(dhctx) _libssh2_dh_dtor(dhctx) + + +/*******************************************************************/ +/* + * mbedTLS backend: forward declarations + */ + +void +_libssh2_mbedtls_init(void); + +void +_libssh2_mbedtls_free(void); + +int +_libssh2_mbedtls_random(unsigned char *buf, int len); + +int +_libssh2_mbedtls_cipher_init(_libssh2_cipher_ctx *ctx, + _libssh2_cipher_type(type), + unsigned char *iv, + unsigned char *secret, + int encrypt); +int +_libssh2_mbedtls_cipher_crypt(_libssh2_cipher_ctx *ctx, + _libssh2_cipher_type(type), + int encrypt, + unsigned char *block, + size_t blocklen); +void +_libssh2_mbedtls_cipher_dtor(_libssh2_cipher_ctx *ctx); + +int +_libssh2_mbedtls_hash_init(mbedtls_md_context_t *ctx, + mbedtls_md_type_t mdtype, + const unsigned char *key, unsigned long keylen); + +int +_libssh2_mbedtls_hash_final(mbedtls_md_context_t *ctx, unsigned char *hash); +int +_libssh2_mbedtls_hash(const unsigned char *data, unsigned long datalen, + mbedtls_md_type_t mdtype, unsigned char *hash); + +_libssh2_bn * +_libssh2_mbedtls_bignum_init(void); + +void +_libssh2_mbedtls_bignum_free(_libssh2_bn *bn); + +int +_libssh2_mbedtls_rsa_new(libssh2_rsa_ctx **rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, + unsigned long coefflen); + +int +_libssh2_mbedtls_rsa_new_private(libssh2_rsa_ctx **rsa, + LIBSSH2_SESSION *session, + const char *filename, + const unsigned char *passphrase); + +int +_libssh2_mbedtls_rsa_new_private_frommemory(libssh2_rsa_ctx **rsa, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); +int +_libssh2_mbedtls_rsa_sha1_verify(libssh2_rsa_ctx *rsa, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, + unsigned long m_len); +int +_libssh2_mbedtls_rsa_sha1_sign(LIBSSH2_SESSION *session, + libssh2_rsa_ctx *rsa, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, + size_t *signature_len); +void +_libssh2_mbedtls_rsa_free(libssh2_rsa_ctx *rsa); + +int +_libssh2_mbedtls_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase); +int +_libssh2_mbedtls_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); +#if LIBSSH2_ECDSA +int +_libssh2_mbedtls_ecdsa_create_key(LIBSSH2_SESSION *session, + _libssh2_ec_key **privkey, + unsigned char **pubkey_octal, + size_t *pubkey_octal_len, + libssh2_curve_type curve); +int +_libssh2_mbedtls_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx **ctx, + const unsigned char *k, + size_t k_len, + libssh2_curve_type curve); +int +_libssh2_mbedtls_ecdh_gen_k(_libssh2_bn **k, + _libssh2_ec_key *privkey, + const unsigned char *server_pubkey, + size_t server_pubkey_len); +int +_libssh2_mbedtls_ecdsa_verify(libssh2_ecdsa_ctx *ctx, + const unsigned char *r, size_t r_len, + const unsigned char *s, size_t s_len, + const unsigned char *m, size_t m_len); +int +_libssh2_mbedtls_ecdsa_new_private(libssh2_ecdsa_ctx **ctx, + LIBSSH2_SESSION *session, + const char *filename, + const unsigned char *passphrase); +int +_libssh2_mbedtls_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx **ctx, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + const unsigned char *passphrase); +int +_libssh2_mbedtls_ecdsa_sign(LIBSSH2_SESSION *session, + libssh2_ecdsa_ctx *ctx, + const unsigned char *hash, + unsigned long hash_len, + unsigned char **signature, + size_t *signature_len); +libssh2_curve_type +_libssh2_mbedtls_ecdsa_key_get_curve_type(libssh2_ecdsa_ctx *ctx); +int +_libssh2_mbedtls_ecdsa_curve_type_from_name(const char *name, + libssh2_curve_type *type); +void +_libssh2_mbedtls_ecdsa_free(libssh2_ecdsa_ctx *ctx); +#endif /* LIBSSH2_ECDSA */ + +extern void +_libssh2_dh_init(_libssh2_dh_ctx *dhctx); +extern int +_libssh2_dh_key_pair(_libssh2_dh_ctx *dhctx, _libssh2_bn *public, + _libssh2_bn *g, _libssh2_bn *p, int group_order); +extern int +_libssh2_dh_secret(_libssh2_dh_ctx *dhctx, _libssh2_bn *secret, + _libssh2_bn *f, _libssh2_bn *p); +extern void +_libssh2_dh_dtor(_libssh2_dh_ctx *dhctx); + +#endif /* __LIBSSH2_MBEDTLS_H */ +#endif diff --git a/lib/libssh2/misc.c b/lib/libssh2/misc.c new file mode 100644 index 0000000..8dd5010 --- /dev/null +++ b/lib/libssh2/misc.c @@ -0,0 +1,932 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2019 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" +#include "blf.h" + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include + +int _libssh2_error_flags(LIBSSH2_SESSION* session, int errcode, + const char *errmsg, int errflags) +{ + if(session == NULL) { + if(errmsg != NULL) + fprintf(stderr, "Session is NULL, error: %s\n", errmsg); + return errcode; + } + + if(session->err_flags & LIBSSH2_ERR_FLAG_DUP) + LIBSSH2_FREE(session, (char *)session->err_msg); + + session->err_code = errcode; + session->err_flags = 0; + + if((errmsg != NULL) && ((errflags & LIBSSH2_ERR_FLAG_DUP) != 0)) { + size_t len = strlen(errmsg); + char *copy = LIBSSH2_ALLOC(session, len + 1); + if(copy) { + memcpy(copy, errmsg, len + 1); + session->err_flags = LIBSSH2_ERR_FLAG_DUP; + session->err_msg = copy; + } + else + /* Out of memory: this code path is very unlikely */ + session->err_msg = "former error forgotten (OOM)"; + } + else + session->err_msg = errmsg; + +#ifdef LIBSSH2DEBUG + if((errcode == LIBSSH2_ERROR_EAGAIN) && !session->api_block_mode) + /* if this is EAGAIN and we're in non-blocking mode, don't generate + a debug output for this */ + return errcode; + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, "%d - %s", session->err_code, + session->err_msg); +#endif + + return errcode; +} + +int _libssh2_error(LIBSSH2_SESSION* session, int errcode, const char *errmsg) +{ + return _libssh2_error_flags(session, errcode, errmsg, 0); +} + +#ifdef WIN32 +static int wsa2errno(void) +{ + switch(WSAGetLastError()) { + case WSAEWOULDBLOCK: + return EAGAIN; + + case WSAENOTSOCK: + return EBADF; + + case WSAEINTR: + return EINTR; + + default: + /* It is most important to ensure errno does not stay at EAGAIN + * when a different error occurs so just set errno to a generic + * error */ + return EIO; + } +} +#endif + +/* _libssh2_recv + * + * Replacement for the standard recv, return -errno on failure. + */ +ssize_t +_libssh2_recv(libssh2_socket_t sock, void *buffer, size_t length, + int flags, void **abstract) +{ + ssize_t rc; + + (void) abstract; + + rc = recv(sock, buffer, length, flags); +#ifdef WIN32 + if(rc < 0) + return -wsa2errno(); +#else + if(rc < 0) { + /* Sometimes the first recv() function call sets errno to ENOENT on + Solaris and HP-UX */ + if(errno == ENOENT) + return -EAGAIN; +#ifdef EWOULDBLOCK /* For VMS and other special unixes */ + else if(errno == EWOULDBLOCK) + return -EAGAIN; +#endif + else + return -errno; + } +#endif + return rc; +} + +/* _libssh2_send + * + * Replacement for the standard send, return -errno on failure. + */ +ssize_t +_libssh2_send(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract) +{ + ssize_t rc; + + (void) abstract; + + rc = send(sock, buffer, length, flags); +#ifdef WIN32 + if(rc < 0) + return -wsa2errno(); +#else + if(rc < 0) { +#ifdef EWOULDBLOCK /* For VMS and other special unixes */ + if(errno == EWOULDBLOCK) + return -EAGAIN; +#endif + return -errno; + } +#endif + return rc; +} + +/* libssh2_ntohu32 + */ +unsigned int +_libssh2_ntohu32(const unsigned char *buf) +{ + return (((unsigned int)buf[0] << 24) + | ((unsigned int)buf[1] << 16) + | ((unsigned int)buf[2] << 8) + | ((unsigned int)buf[3])); +} + + +/* _libssh2_ntohu64 + */ +libssh2_uint64_t +_libssh2_ntohu64(const unsigned char *buf) +{ + unsigned long msl, lsl; + + msl = ((libssh2_uint64_t)buf[0] << 24) | ((libssh2_uint64_t)buf[1] << 16) + | ((libssh2_uint64_t)buf[2] << 8) | (libssh2_uint64_t)buf[3]; + lsl = ((libssh2_uint64_t)buf[4] << 24) | ((libssh2_uint64_t)buf[5] << 16) + | ((libssh2_uint64_t)buf[6] << 8) | (libssh2_uint64_t)buf[7]; + + return ((libssh2_uint64_t)msl <<32) | lsl; +} + +/* _libssh2_htonu32 + */ +void +_libssh2_htonu32(unsigned char *buf, uint32_t value) +{ + buf[0] = (value >> 24) & 0xFF; + buf[1] = (value >> 16) & 0xFF; + buf[2] = (value >> 8) & 0xFF; + buf[3] = value & 0xFF; +} + +/* _libssh2_store_u32 + */ +void _libssh2_store_u32(unsigned char **buf, uint32_t value) +{ + _libssh2_htonu32(*buf, value); + *buf += sizeof(uint32_t); +} + +/* _libssh2_store_str + */ +void _libssh2_store_str(unsigned char **buf, const char *str, size_t len) +{ + _libssh2_store_u32(buf, (uint32_t)len); + if(len) { + memcpy(*buf, str, len); + *buf += len; + } +} + +/* _libssh2_store_bignum2_bytes + */ +void _libssh2_store_bignum2_bytes(unsigned char **buf, + const unsigned char *bytes, + size_t len) +{ + int extraByte = 0; + const unsigned char *p; + for(p = bytes; len > 0 && *p == 0; --len, ++p) {} + + extraByte = (len > 0 && (p[0] & 0x80) != 0); + _libssh2_store_u32(buf, len + extraByte); + + if(extraByte) { + *buf[0] = 0; + *buf += 1; + } + + if(len > 0) { + memcpy(*buf, p, len); + *buf += len; + } +} + +/* Base64 Conversion */ + +static const short base64_reverse_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* libssh2_base64_decode + * + * Decode a base64 chunk and store it into a newly alloc'd buffer + */ +LIBSSH2_API int +libssh2_base64_decode(LIBSSH2_SESSION *session, char **data, + unsigned int *datalen, const char *src, + unsigned int src_len) +{ + unsigned char *s, *d; + short v; + int i = 0, len = 0; + + *data = LIBSSH2_ALLOC(session, (3 * src_len / 4) + 1); + d = (unsigned char *) *data; + if(!d) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for base64 decoding"); + } + + for(s = (unsigned char *) src; ((char *) s) < (src + src_len); s++) { + v = base64_reverse_table[*s]; + if(v < 0) + continue; + switch(i % 4) { + case 0: + d[len] = (unsigned char)(v << 2); + break; + case 1: + d[len++] |= v >> 4; + d[len] = (unsigned char)(v << 4); + break; + case 2: + d[len++] |= v >> 2; + d[len] = (unsigned char)(v << 6); + break; + case 3: + d[len++] |= v; + break; + } + i++; + } + if((i % 4) == 1) { + /* Invalid -- We have a byte which belongs exclusively to a partial + octet */ + LIBSSH2_FREE(session, *data); + *data = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, "Invalid base64"); + } + + *datalen = len; + return 0; +} + +/* ---- Base64 Encoding/Decoding Table --- */ +static const char table64[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * _libssh2_base64_encode() + * + * Returns the length of the newly created base64 string. The third argument + * is a pointer to an allocated area holding the base64 data. If something + * went wrong, 0 is returned. + * + */ +size_t _libssh2_base64_encode(LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr) +{ + unsigned char ibuf[3]; + unsigned char obuf[4]; + int i; + int inputparts; + char *output; + char *base64data; + const char *indata = inp; + + *outptr = NULL; /* set to NULL in case of failure before we reach the + end */ + + if(0 == insize) + insize = strlen(indata); + + base64data = output = LIBSSH2_ALLOC(session, insize * 4 / 3 + 4); + if(NULL == output) + return 0; + + while(insize > 0) { + for(i = inputparts = 0; i < 3; i++) { + if(insize > 0) { + inputparts++; + ibuf[i] = *indata; + indata++; + insize--; + } + else + ibuf[i] = 0; + } + + obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); + obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ + ((ibuf[1] & 0xF0) >> 4)); + obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ + ((ibuf[2] & 0xC0) >> 6)); + obuf[3] = (unsigned char) (ibuf[2] & 0x3F); + + switch(inputparts) { + case 1: /* only one byte read */ + snprintf(output, 5, "%c%c==", + table64[obuf[0]], + table64[obuf[1]]); + break; + case 2: /* two bytes read */ + snprintf(output, 5, "%c%c%c=", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]]); + break; + default: + snprintf(output, 5, "%c%c%c%c", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]], + table64[obuf[3]]); + break; + } + output += 4; + } + *output = 0; + *outptr = base64data; /* make it return the actual data memory */ + + return strlen(base64data); /* return the length of the new data */ +} +/* ---- End of Base64 Encoding ---- */ + +LIBSSH2_API void +libssh2_free(LIBSSH2_SESSION *session, void *ptr) +{ + LIBSSH2_FREE(session, ptr); +} + +#ifdef LIBSSH2DEBUG +#include + +LIBSSH2_API int +libssh2_trace(LIBSSH2_SESSION * session, int bitmask) +{ + session->showmask = bitmask; + return 0; +} + +LIBSSH2_API int +libssh2_trace_sethandler(LIBSSH2_SESSION *session, void *handler_context, + libssh2_trace_handler_func callback) +{ + session->tracehandler = callback; + session->tracehandler_context = handler_context; + return 0; +} + +void +_libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, ...) +{ + char buffer[1536]; + int len, msglen, buflen = sizeof(buffer); + va_list vargs; + struct timeval now; + static int firstsec; + static const char *const contexts[] = { + "Unknown", + "Transport", + "Key Ex", + "Userauth", + "Conn", + "SCP", + "SFTP", + "Failure Event", + "Publickey", + "Socket", + }; + const char *contexttext = contexts[0]; + unsigned int contextindex; + + if(!(session->showmask & context)) { + /* no such output asked for */ + return; + } + + /* Find the first matching context string for this message */ + for(contextindex = 0; contextindex < ARRAY_SIZE(contexts); + contextindex++) { + if((context & (1 << contextindex)) != 0) { + contexttext = contexts[contextindex]; + break; + } + } + + _libssh2_gettimeofday(&now, NULL); + if(!firstsec) { + firstsec = now.tv_sec; + } + now.tv_sec -= firstsec; + + len = snprintf(buffer, buflen, "[libssh2] %d.%06d %s: ", + (int)now.tv_sec, (int)now.tv_usec, contexttext); + + if(len >= buflen) + msglen = buflen - 1; + else { + buflen -= len; + msglen = len; + va_start(vargs, format); + len = vsnprintf(buffer + msglen, buflen, format, vargs); + va_end(vargs); + msglen += len < buflen ? len : buflen - 1; + } + + if(session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, buffer, + msglen); + else + fprintf(stderr, "%s\n", buffer); +} + +#else +LIBSSH2_API int +libssh2_trace(LIBSSH2_SESSION * session, int bitmask) +{ + (void) session; + (void) bitmask; + return 0; +} + +LIBSSH2_API int +libssh2_trace_sethandler(LIBSSH2_SESSION *session, void *handler_context, + libssh2_trace_handler_func callback) +{ + (void) session; + (void) handler_context; + (void) callback; + return 0; +} +#endif + +/* init the list head */ +void _libssh2_list_init(struct list_head *head) +{ + head->first = head->last = NULL; +} + +/* add a node to the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry) +{ + /* store a pointer to the head */ + entry->head = head; + + /* we add this entry at the "top" so it has no next */ + entry->next = NULL; + + /* make our prev point to what the head thinks is last */ + entry->prev = head->last; + + /* and make head's last be us now */ + head->last = entry; + + /* make sure our 'prev' node points to us next */ + if(entry->prev) + entry->prev->next = entry; + else + head->first = entry; +} + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head) +{ + return head->first; +} + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node) +{ + return node->next; +} + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node) +{ + return node->prev; +} + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry) +{ + if(entry->prev) + entry->prev->next = entry->next; + else + entry->head->first = entry->next; + + if(entry->next) + entry->next->prev = entry->prev; + else + entry->head->last = entry->prev; +} + +#if 0 +/* insert a node before the given 'after' entry */ +void _libssh2_list_insert(struct list_node *after, /* insert before this */ + struct list_node *entry) +{ + /* 'after' is next to 'entry' */ + bentry->next = after; + + /* entry's prev is then made to be the prev after current has */ + entry->prev = after->prev; + + /* the node that is now before 'entry' was previously before 'after' + and must be made to point to 'entry' correctly */ + if(entry->prev) + entry->prev->next = entry; + else + /* there was no node before this, so we make sure we point the head + pointer to this node */ + after->head->first = entry; + + /* after's prev entry points back to entry */ + after->prev = entry; + + /* after's next entry is still the same as before */ + + /* entry's head is the same as after's */ + entry->head = after->head; +} + +#endif + +/* this define is defined in misc.h for the correct platforms */ +#ifdef LIBSSH2_GETTIMEOFDAY_WIN32 +/* + * gettimeofday + * Implementation according to: + * The Open Group Base Specifications Issue 6 + * IEEE Std 1003.1, 2004 Edition + */ + +/* + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Contributed by: + * Danny Smith + */ + +/* Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ +#define _W32_FT_OFFSET (116444736000000000) + +int __cdecl _libssh2_gettimeofday(struct timeval *tp, void *tzp) +{ + union { + unsigned __int64 ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + (void)tzp; + if(tp) { + GetSystemTimeAsFileTime(&_now.ft); + tp->tv_usec = (long)((_now.ns100 / 10) % 1000000); + tp->tv_sec = (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000); + } + /* Always return 0 as per Open Group Base Specifications Issue 6. + Do not set errno on error. */ + return 0; +} + + +#endif + +void *_libssh2_calloc(LIBSSH2_SESSION* session, size_t size) +{ + void *p = LIBSSH2_ALLOC(session, size); + if(p) { + memset(p, 0, size); + } + return p; +} + +/* XOR operation on buffers input1 and input2, result in output. + It is safe to use an input buffer as the output buffer. */ +void _libssh2_xor_data(unsigned char *output, + const unsigned char *input1, + const unsigned char *input2, + size_t length) +{ + size_t i; + + for(i = 0; i < length; i++) + *output++ = *input1++ ^ *input2++; +} + +/* Increments an AES CTR buffer to prepare it for use with the + next AES block. */ +void _libssh2_aes_ctr_increment(unsigned char *ctr, + size_t length) +{ + unsigned char *pc; + unsigned int val, carry; + + pc = ctr + length - 1; + carry = 1; + + while(pc >= ctr) { + val = (unsigned int)*pc + carry; + *pc-- = val & 0xFF; + carry = val >> 8; + } +} + +#if !defined(WIN32) && !defined(HAVE_MEMSET_S) +static void * (* const volatile memset_libssh)(void *, int, size_t) = memset; +#endif + +void _libssh2_explicit_zero(void *buf, size_t size) +{ +#ifdef WIN32 + SecureZeroMemory(buf, size); +#elif defined(HAVE_MEMSET_S) + (void)memset_s(buf, size, 0, size); +#else + memset_libssh(buf, 0, size); +#endif +} + +/* String buffer */ + +struct string_buf* _libssh2_string_buf_new(LIBSSH2_SESSION *session) +{ + struct string_buf *ret; + + ret = _libssh2_calloc(session, sizeof(*ret)); + if(ret == NULL) + return NULL; + + return ret; +} + +void _libssh2_string_buf_free(LIBSSH2_SESSION *session, struct string_buf *buf) +{ + if(buf == NULL) + return; + + if(buf->data != NULL) + LIBSSH2_FREE(session, buf->data); + + LIBSSH2_FREE(session, buf); + buf = NULL; +} + +int _libssh2_get_byte(struct string_buf *buf, unsigned char *out) +{ + if(!_libssh2_check_length(buf, 1)) { + return -1; + } + + *out = buf->dataptr[0]; + buf->dataptr += 1; + return 0; +} + +int _libssh2_get_boolean(struct string_buf *buf, unsigned char *out) +{ + if(!_libssh2_check_length(buf, 1)) { + return -1; + } + + + *out = buf->dataptr[0] == 0 ? 0 : 1; + buf->dataptr += 1; + return 0; +} + +int _libssh2_get_u32(struct string_buf *buf, uint32_t *out) +{ + if(!_libssh2_check_length(buf, 4)) { + return -1; + } + + *out = _libssh2_ntohu32(buf->dataptr); + buf->dataptr += 4; + return 0; +} + +int _libssh2_get_u64(struct string_buf *buf, libssh2_uint64_t *out) +{ + if(!_libssh2_check_length(buf, 8)) { + return -1; + } + + *out = _libssh2_ntohu64(buf->dataptr); + buf->dataptr += 8; + return 0; +} + +int _libssh2_match_string(struct string_buf *buf, const char *match) +{ + unsigned char *out; + size_t len = 0; + if(_libssh2_get_string(buf, &out, &len) || len != strlen(match) || + strncmp((char *)out, match, strlen(match)) != 0) { + return -1; + } + return 0; +} + +int _libssh2_get_string(struct string_buf *buf, unsigned char **outbuf, + size_t *outlen) +{ + uint32_t data_len; + if(_libssh2_get_u32(buf, &data_len) != 0) { + return -1; + } + if(!_libssh2_check_length(buf, data_len)) { + return -1; + } + *outbuf = buf->dataptr; + buf->dataptr += data_len; + + if(outlen) + *outlen = (size_t)data_len; + + return 0; +} + +int _libssh2_copy_string(LIBSSH2_SESSION *session, struct string_buf *buf, + unsigned char **outbuf, size_t *outlen) +{ + size_t str_len; + unsigned char *str; + + if(_libssh2_get_string(buf, &str, &str_len)) { + return -1; + } + + if(str_len) { + *outbuf = LIBSSH2_ALLOC(session, str_len); + if(*outbuf) { + memcpy(*outbuf, str, str_len); + } + else { + return -1; + } + } + else { + *outlen = 0; + *outbuf = NULL; + } + + if(outlen) + *outlen = str_len; + + return 0; +} + +int _libssh2_get_bignum_bytes(struct string_buf *buf, unsigned char **outbuf, + size_t *outlen) +{ + uint32_t data_len; + uint32_t bn_len; + unsigned char *bnptr; + + if(_libssh2_get_u32(buf, &data_len)) { + return -1; + } + if(!_libssh2_check_length(buf, data_len)) { + return -1; + } + + bn_len = data_len; + bnptr = buf->dataptr; + + /* trim leading zeros */ + while(bn_len > 0 && *bnptr == 0x00) { + bn_len--; + bnptr++; + } + + *outbuf = bnptr; + buf->dataptr += data_len; + + if(outlen) + *outlen = (size_t)bn_len; + + return 0; +} + +/* Given the current location in buf, _libssh2_check_length ensures + callers can read the next len number of bytes out of the buffer + before reading the buffer content */ + +int _libssh2_check_length(struct string_buf *buf, size_t len) +{ + unsigned char *endp = &buf->data[buf->len]; + size_t left = endp - buf->dataptr; + return ((len <= left) && (left <= buf->len)); +} + +int _libssh2_eob(struct string_buf *buf) +{ + unsigned char *endp = &buf->data[buf->len]; + return buf->dataptr >= endp; +} + +/* Wrappers */ + +int _libssh2_bcrypt_pbkdf(const char *pass, + size_t passlen, + const uint8_t *salt, + size_t saltlen, + uint8_t *key, + size_t keylen, + unsigned int rounds) +{ + /* defined in bcrypt_pbkdf.c */ + return bcrypt_pbkdf(pass, + passlen, + salt, + saltlen, + key, + keylen, + rounds); +} +#endif diff --git a/lib/libssh2/misc.h b/lib/libssh2/misc.h new file mode 100644 index 0000000..4d6e96f --- /dev/null +++ b/lib/libssh2/misc.h @@ -0,0 +1,133 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_MISC_H +#define __LIBSSH2_MISC_H +/* Copyright (c) 2009-2019 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +struct list_head { + struct list_node *last; + struct list_node *first; +}; + +struct list_node { + struct list_node *next; + struct list_node *prev; + struct list_head *head; +}; + +struct string_buf { + unsigned char *data; + unsigned char *dataptr; + size_t len; +}; + +int _libssh2_error_flags(LIBSSH2_SESSION* session, int errcode, + const char *errmsg, int errflags); +int _libssh2_error(LIBSSH2_SESSION* session, int errcode, const char *errmsg); + +void _libssh2_list_init(struct list_head *head); + +/* add a node last in the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry); + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head); + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node); + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node); + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry); + +size_t _libssh2_base64_encode(LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr); + +unsigned int _libssh2_ntohu32(const unsigned char *buf); +libssh2_uint64_t _libssh2_ntohu64(const unsigned char *buf); +void _libssh2_htonu32(unsigned char *buf, uint32_t val); +void _libssh2_store_u32(unsigned char **buf, uint32_t value); +void _libssh2_store_str(unsigned char **buf, const char *str, size_t len); +void _libssh2_store_bignum2_bytes(unsigned char **buf, + const unsigned char *bytes, + size_t len); +void *_libssh2_calloc(LIBSSH2_SESSION *session, size_t size); +void _libssh2_explicit_zero(void *buf, size_t size); + +struct string_buf* _libssh2_string_buf_new(LIBSSH2_SESSION *session); +void _libssh2_string_buf_free(LIBSSH2_SESSION *session, + struct string_buf *buf); +int _libssh2_get_boolean(struct string_buf *buf, unsigned char *out); +int _libssh2_get_byte(struct string_buf *buf, unsigned char *out); +int _libssh2_get_u32(struct string_buf *buf, uint32_t *out); +int _libssh2_get_u64(struct string_buf *buf, libssh2_uint64_t *out); +int _libssh2_match_string(struct string_buf *buf, const char *match); +int _libssh2_get_string(struct string_buf *buf, unsigned char **outbuf, + size_t *outlen); +int _libssh2_copy_string(LIBSSH2_SESSION* session, struct string_buf *buf, + unsigned char **outbuf, size_t *outlen); +int _libssh2_get_bignum_bytes(struct string_buf *buf, unsigned char **outbuf, + size_t *outlen); +int _libssh2_check_length(struct string_buf *buf, size_t requested_len); +int _libssh2_eob(struct string_buf *buf); + +#if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) +/* provide a private one */ +#undef HAVE_GETTIMEOFDAY +int __cdecl _libssh2_gettimeofday(struct timeval *tp, void *tzp); +#define HAVE_LIBSSH2_GETTIMEOFDAY +#define LIBSSH2_GETTIMEOFDAY_WIN32 /* enable the win32 implementation */ +#else +#ifdef HAVE_GETTIMEOFDAY +#define _libssh2_gettimeofday(x,y) gettimeofday(x,y) +#define HAVE_LIBSSH2_GETTIMEOFDAY +#endif +#endif + +void _libssh2_xor_data(unsigned char *output, + const unsigned char *input1, + const unsigned char *input2, + size_t length); + +void _libssh2_aes_ctr_increment(unsigned char *ctr, size_t length); + +#endif /* _LIBSSH2_MISC_H */ +#endif diff --git a/lib/libssh2/openssl.h b/lib/libssh2/openssl.h new file mode 100644 index 0000000..490ccfe --- /dev/null +++ b/lib/libssh2/openssl.h @@ -0,0 +1,445 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_OPENSSL_H +#define __LIBSSH2_OPENSSL_H +/* Copyright (C) 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. + * + * Author: Simon Josefsson + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* disable deprecated warnings in OpenSSL 3 */ +#define OPENSSL_SUPPRESS_DEPRECATED + +#ifdef LIBSSH2_WOLFSSL + +#include +#include + +#if defined(NO_DSA) || defined(HAVE_FIPS) +#define OPENSSL_NO_DSA +#endif + +#if defined(NO_MD5) || defined(HAVE_FIPS) +#define OPENSSL_NO_MD5 +#endif + +#if !defined(WOLFSSL_RIPEMD) || defined(HAVE_FIPS) +#define OPENSSL_NO_RIPEMD +#endif + +#if defined(NO_RC4) || defined(HAVE_FIPS) +#define OPENSSL_NO_RC4 +#endif + +#ifdef NO_DES3 +#define OPENSSL_NO_DES +#endif + +#ifdef EVP_aes_128_ctr +#define HAVE_EVP_AES_128_CTR +#endif + +/* wolfSSL doesn't support Blowfish or CAST. */ +#define OPENSSL_NO_BF +#define OPENSSL_NO_CAST +/* wolfSSL has no engine framework. */ +#define OPENSSL_NO_ENGINE + +#endif /* LIBSSH2_WOLFSSL */ + +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +#include +#endif +#ifndef OPENSSL_NO_DSA +#include +#endif +#ifndef OPENSSL_NO_MD5 +#include +#endif +#include +#include +#include +#include +#include + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || defined(LIBSSH2_WOLFSSL) || \ + LIBRESSL_VERSION_NUMBER >= 0x3050000fL +/* For wolfSSL, whether the structs are truly opaque or not, it's best to not + * rely on their internal data members being exposed publicly. */ +# define HAVE_OPAQUE_STRUCTS 1 +#endif + +#ifdef OPENSSL_NO_RSA +# define LIBSSH2_RSA 0 +# define LIBSSH2_RSA_SHA2 0 +#else +# define LIBSSH2_RSA 1 +# define LIBSSH2_RSA_SHA2 1 +#endif + +#ifdef OPENSSL_NO_DSA +# define LIBSSH2_DSA 0 +#else +# define LIBSSH2_DSA 1 +#endif + +#if defined(OPENSSL_NO_ECDSA) || defined(OPENSSL_NO_EC) +# define LIBSSH2_ECDSA 0 +#else +# define LIBSSH2_ECDSA 1 +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + LIBRESSL_VERSION_NUMBER >= 0x3070000fL +# define LIBSSH2_ED25519 1 +#else +# define LIBSSH2_ED25519 0 +#endif + + +#ifdef OPENSSL_NO_MD5 +# define LIBSSH2_MD5 0 +#else +# define LIBSSH2_MD5 1 +#endif + +#if defined(OPENSSL_NO_RIPEMD) || defined(OPENSSL_NO_RMD160) +# define LIBSSH2_HMAC_RIPEMD 0 +#else +# define LIBSSH2_HMAC_RIPEMD 1 +#endif + +#define LIBSSH2_HMAC_SHA256 1 +#define LIBSSH2_HMAC_SHA512 1 + +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES)) || \ + (defined(LIBSSH2_WOLFSSL) && defined(WOLFSSL_AES_COUNTER)) +# define LIBSSH2_AES_CTR 1 +# define LIBSSH2_AES 1 +#else +# define LIBSSH2_AES_CTR 0 +# define LIBSSH2_AES 0 +#endif + +#ifdef OPENSSL_NO_BF +# define LIBSSH2_BLOWFISH 0 +#else +# define LIBSSH2_BLOWFISH 1 +#endif + +#ifdef OPENSSL_NO_RC4 +# define LIBSSH2_RC4 0 +#else +# define LIBSSH2_RC4 1 +#endif + +#ifdef OPENSSL_NO_CAST +# define LIBSSH2_CAST 0 +#else +# define LIBSSH2_CAST 1 +#endif + +#ifdef OPENSSL_NO_DES +# define LIBSSH2_3DES 0 +#else +# define LIBSSH2_3DES 1 +#endif + +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) + +#define _libssh2_random(buf, len) (RAND_bytes((buf), (len)) == 1 ? 0 : -1) + +#define libssh2_prepare_iovec(vec, len) /* Empty. */ + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha1_ctx EVP_MD_CTX * +#else +#define libssh2_sha1_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha1_init(libssh2_sha1_ctx *ctx); +#define libssh2_sha1_init(x) _libssh2_sha1_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha1_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha1_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha1_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha1_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha1(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha1(x,y,z) _libssh2_sha1(x,y,z) + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha256_ctx EVP_MD_CTX * +#else +#define libssh2_sha256_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha256_init(libssh2_sha256_ctx *ctx); +#define libssh2_sha256_init(x) _libssh2_sha256_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha256_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha256_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha256_update(ctx, data, len) \ + EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha256_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha256(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha256(x,y,z) _libssh2_sha256(x,y,z) + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha384_ctx EVP_MD_CTX * +#else +#define libssh2_sha384_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha384_init(libssh2_sha384_ctx *ctx); +#define libssh2_sha384_init(x) _libssh2_sha384_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha384_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha384_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha384_update(ctx, data, len) \ + EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha384_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha384(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha384(x,y,z) _libssh2_sha384(x,y,z) + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha512_ctx EVP_MD_CTX * +#else +#define libssh2_sha512_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha512_init(libssh2_sha512_ctx *ctx); +#define libssh2_sha512_init(x) _libssh2_sha512_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha512_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha512_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha512_update(ctx, data, len) \ + EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha512_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha512(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha512(x,y,z) _libssh2_sha512(x,y,z) + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_md5_ctx EVP_MD_CTX * +#else +#define libssh2_md5_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_md5_init(libssh2_md5_ctx *ctx); +#define libssh2_md5_init(x) _libssh2_md5_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_md5_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_md5_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_md5_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_md5_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_hmac_ctx HMAC_CTX * +#define libssh2_hmac_ctx_init(ctx) ctx = HMAC_CTX_new() +#define libssh2_hmac_sha1_init(ctx, key, keylen) \ + HMAC_Init_ex(*(ctx), key, keylen, EVP_sha1(), NULL) +#define libssh2_hmac_md5_init(ctx, key, keylen) \ + HMAC_Init_ex(*(ctx), key, keylen, EVP_md5(), NULL) +#define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ + HMAC_Init_ex(*(ctx), key, keylen, EVP_ripemd160(), NULL) +#define libssh2_hmac_sha256_init(ctx, key, keylen) \ + HMAC_Init_ex(*(ctx), key, keylen, EVP_sha256(), NULL) +#define libssh2_hmac_sha512_init(ctx, key, keylen) \ + HMAC_Init_ex(*(ctx), key, keylen, EVP_sha512(), NULL) + +#define libssh2_hmac_update(ctx, data, datalen) \ + HMAC_Update(ctx, data, datalen) +#define libssh2_hmac_final(ctx, data) HMAC_Final(ctx, data, NULL) +#define libssh2_hmac_cleanup(ctx) HMAC_CTX_free(*(ctx)) +#else +#define libssh2_hmac_ctx HMAC_CTX +#define libssh2_hmac_ctx_init(ctx) \ + HMAC_CTX_init(&ctx) +#define libssh2_hmac_sha1_init(ctx, key, keylen) \ + HMAC_Init_ex(ctx, key, keylen, EVP_sha1(), NULL) +#define libssh2_hmac_md5_init(ctx, key, keylen) \ + HMAC_Init_ex(ctx, key, keylen, EVP_md5(), NULL) +#define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ + HMAC_Init_ex(ctx, key, keylen, EVP_ripemd160(), NULL) +#define libssh2_hmac_sha256_init(ctx, key, keylen) \ + HMAC_Init_ex(ctx, key, keylen, EVP_sha256(), NULL) +#define libssh2_hmac_sha512_init(ctx, key, keylen) \ + HMAC_Init_ex(ctx, key, keylen, EVP_sha512(), NULL) + +#define libssh2_hmac_update(ctx, data, datalen) \ + HMAC_Update(&(ctx), data, datalen) +#define libssh2_hmac_final(ctx, data) HMAC_Final(&(ctx), data, NULL) +#define libssh2_hmac_cleanup(ctx) HMAC_cleanup(ctx) +#endif + +extern void _libssh2_openssl_crypto_init(void); +extern void _libssh2_openssl_crypto_exit(void); +#define libssh2_crypto_init() _libssh2_openssl_crypto_init() +#define libssh2_crypto_exit() _libssh2_openssl_crypto_exit() + +#define libssh2_rsa_ctx RSA + +#define _libssh2_rsa_free(rsactx) RSA_free(rsactx) + +#define libssh2_dsa_ctx DSA + +#define _libssh2_dsa_free(dsactx) DSA_free(dsactx) + +#if LIBSSH2_ECDSA +#define libssh2_ecdsa_ctx EC_KEY +#define _libssh2_ecdsa_free(ecdsactx) EC_KEY_free(ecdsactx) +#define _libssh2_ec_key EC_KEY + +typedef enum { + LIBSSH2_EC_CURVE_NISTP256 = NID_X9_62_prime256v1, + LIBSSH2_EC_CURVE_NISTP384 = NID_secp384r1, + LIBSSH2_EC_CURVE_NISTP521 = NID_secp521r1 +} +libssh2_curve_type; +#else +#define _libssh2_ec_key void +#endif /* LIBSSH2_ECDSA */ + +#if LIBSSH2_ED25519 +#define libssh2_ed25519_ctx EVP_PKEY + +#define _libssh2_ed25519_free(ctx) EVP_PKEY_free(ctx) +#endif /* ED25519 */ + +#define _libssh2_cipher_type(name) const EVP_CIPHER *(*name)(void) +#ifdef HAVE_OPAQUE_STRUCTS +#define _libssh2_cipher_ctx EVP_CIPHER_CTX * +#else +#define _libssh2_cipher_ctx EVP_CIPHER_CTX +#endif + +#define _libssh2_cipher_aes256 EVP_aes_256_cbc +#define _libssh2_cipher_aes192 EVP_aes_192_cbc +#define _libssh2_cipher_aes128 EVP_aes_128_cbc +#ifdef HAVE_EVP_AES_128_CTR +#define _libssh2_cipher_aes128ctr EVP_aes_128_ctr +#define _libssh2_cipher_aes192ctr EVP_aes_192_ctr +#define _libssh2_cipher_aes256ctr EVP_aes_256_ctr +#else +#define _libssh2_cipher_aes128ctr _libssh2_EVP_aes_128_ctr +#define _libssh2_cipher_aes192ctr _libssh2_EVP_aes_192_ctr +#define _libssh2_cipher_aes256ctr _libssh2_EVP_aes_256_ctr +#endif +#define _libssh2_cipher_blowfish EVP_bf_cbc +#define _libssh2_cipher_arcfour EVP_rc4 +#define _libssh2_cipher_cast5 EVP_cast5_cbc +#define _libssh2_cipher_3des EVP_des_ede3_cbc + +#ifdef HAVE_OPAQUE_STRUCTS +#define _libssh2_cipher_dtor(ctx) EVP_CIPHER_CTX_free(*(ctx)) +#else +#define _libssh2_cipher_dtor(ctx) EVP_CIPHER_CTX_cleanup(ctx) +#endif + +#define _libssh2_bn BIGNUM +#define _libssh2_bn_ctx BN_CTX +#define _libssh2_bn_ctx_new() BN_CTX_new() +#define _libssh2_bn_ctx_free(bnctx) BN_CTX_free(bnctx) +#define _libssh2_bn_init() BN_new() +#define _libssh2_bn_init_from_bin() _libssh2_bn_init() +#define _libssh2_bn_set_word(bn, val) BN_set_word(bn, val) +#define _libssh2_bn_from_bin(bn, len, val) BN_bin2bn(val, len, bn) +#define _libssh2_bn_to_bin(bn, val) BN_bn2bin(bn, val) +#define _libssh2_bn_bytes(bn) BN_num_bytes(bn) +#define _libssh2_bn_bits(bn) BN_num_bits(bn) +#define _libssh2_bn_free(bn) BN_clear_free(bn) + +#define _libssh2_dh_ctx BIGNUM * +#define libssh2_dh_init(dhctx) _libssh2_dh_init(dhctx) +#define libssh2_dh_key_pair(dhctx, public, g, p, group_order, bnctx) \ + _libssh2_dh_key_pair(dhctx, public, g, p, group_order, bnctx) +#define libssh2_dh_secret(dhctx, secret, f, p, bnctx) \ + _libssh2_dh_secret(dhctx, secret, f, p, bnctx) +#define libssh2_dh_dtor(dhctx) _libssh2_dh_dtor(dhctx) +extern void _libssh2_dh_init(_libssh2_dh_ctx *dhctx); +extern int _libssh2_dh_key_pair(_libssh2_dh_ctx *dhctx, _libssh2_bn *public, + _libssh2_bn *g, _libssh2_bn *p, + int group_order, + _libssh2_bn_ctx *bnctx); +extern int _libssh2_dh_secret(_libssh2_dh_ctx *dhctx, _libssh2_bn *secret, + _libssh2_bn *f, _libssh2_bn *p, + _libssh2_bn_ctx *bnctx); +extern void _libssh2_dh_dtor(_libssh2_dh_ctx *dhctx); + +const EVP_CIPHER *_libssh2_EVP_aes_128_ctr(void); +const EVP_CIPHER *_libssh2_EVP_aes_192_ctr(void); +const EVP_CIPHER *_libssh2_EVP_aes_256_ctr(void); + +#endif /* __LIBSSH2_OPENSSL_H */ +#endif diff --git a/lib/libssh2/packet.c b/lib/libssh2/packet.c new file mode 100644 index 0000000..da80412 --- /dev/null +++ b/lib/libssh2/packet.c @@ -0,0 +1,1409 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2005,2006 Mikhail Gusarov + * Copyright (c) 2009-2014 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_INTTYPES_H +#include +#endif + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#include + +#include "transport.h" +#include "channel.h" +#include "packet.h" + +/* + * libssh2_packet_queue_listener + * + * Queue a connection request for a listener + */ +static inline int +packet_queue_listener(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_queue_listener_state_t *listen_state) +{ + /* + * Look for a matching listener + */ + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(FwdNotReq) - 1); + unsigned char *p; + LIBSSH2_LISTENER *listn = _libssh2_list_first(&session->listeners); + char failure_code = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + int rc; + + if(listen_state->state == libssh2_NB_state_idle) { + unsigned long offset = (sizeof("forwarded-tcpip") - 1) + 5; + size_t temp_len = 0; + struct string_buf buf; + buf.data = data; + buf.dataptr = buf.data; + buf.len = datalen; + + if(datalen < offset) { + return _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Unexpected packet size"); + } + + buf.dataptr += offset; + + if(_libssh2_get_u32(&buf, &(listen_state->sender_channel))) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting channel"); + } + if(_libssh2_get_u32(&buf, &(listen_state->initial_window_size))) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting window size"); + } + if(_libssh2_get_u32(&buf, &(listen_state->packet_size))) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting packet"); + } + if(_libssh2_get_string(&buf, &(listen_state->host), &temp_len)) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting host"); + } + listen_state->host_len = (uint32_t)temp_len; + + if(_libssh2_get_u32(&buf, &(listen_state->port))) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting port"); + } + if(_libssh2_get_string(&buf, &(listen_state->shost), &temp_len)) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting shost"); + } + listen_state->shost_len = (uint32_t)temp_len; + + if(_libssh2_get_u32(&buf, &(listen_state->sport))) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Data too short extracting sport"); + } + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Remote received connection from %s:%ld to %s:%ld", + listen_state->shost, listen_state->sport, + listen_state->host, listen_state->port); + + listen_state->state = libssh2_NB_state_allocated; + } + + if(listen_state->state != libssh2_NB_state_sent) { + while(listn) { + if((listn->port == (int) listen_state->port) && + (strlen(listn->host) == listen_state->host_len) && + (memcmp (listn->host, listen_state->host, + listen_state->host_len) == 0)) { + /* This is our listener */ + LIBSSH2_CHANNEL *channel = NULL; + listen_state->channel = NULL; + + if(listen_state->state == libssh2_NB_state_allocated) { + if(listn->queue_maxsize && + (listn->queue_maxsize <= listn->queue_size)) { + /* Queue is full */ + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Listener queue full, ignoring"); + listen_state->state = libssh2_NB_state_sent; + break; + } + + channel = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if(!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for " + "new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + listen_state->channel = channel; + + channel->session = session; + channel->channel_type_len = sizeof("forwarded-tcpip") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel-> + channel_type_len + + 1); + if(!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for new" + " connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + memcpy(channel->channel_type, "forwarded-tcpip", + channel->channel_type_len + 1); + + channel->remote.id = listen_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = + LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + listen_state->initial_window_size; + channel->local.window_size = + listen_state->initial_window_size; + channel->local.packet_size = listen_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Connection queued: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, + channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + listen_state->state = libssh2_NB_state_created; + } + + if(listen_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, listen_state->packet, + 17, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if(rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel " + "open confirmation"); + } + + /* Link the channel into the end of the queue list */ + if(listen_state->channel) { + _libssh2_list_add(&listn->queue, + &listen_state->channel->node); + listn->queue_size++; + } + + listen_state->state = libssh2_NB_state_idle; + return 0; + } + } + + listn = _libssh2_list_next(&listn->node); + } + + listen_state->state = libssh2_NB_state_sent; + } + + /* We're not listening to you */ + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, listen_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, FwdNotReq, sizeof(FwdNotReq) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, listen_state->packet, + packet_len, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + + } + listen_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * packet_x11_open + * + * Accept a forwarded X11 connection + */ +static inline int +packet_x11_open(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_x11_open_state_t *x11open_state) +{ + int failure_code = SSH_OPEN_CONNECT_FAILED; + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(X11FwdUnAvil) - 1); + unsigned char *p; + LIBSSH2_CHANNEL *channel = x11open_state->channel; + int rc; + + if(x11open_state->state == libssh2_NB_state_idle) { + + unsigned long offset = (sizeof("x11") - 1) + 5; + size_t temp_len = 0; + struct string_buf buf; + buf.data = data; + buf.dataptr = buf.data; + buf.len = datalen; + + if(datalen < offset) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected data length"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + + buf.dataptr += offset; + + if(_libssh2_get_u32(&buf, &(x11open_state->sender_channel))) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected sender channel size"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + if(_libssh2_get_u32(&buf, &(x11open_state->initial_window_size))) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected window size"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + if(_libssh2_get_u32(&buf, &(x11open_state->packet_size))) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected window size"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + if(_libssh2_get_string(&buf, &(x11open_state->shost), &temp_len)) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected host size"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + x11open_state->shost_len = (uint32_t)temp_len; + + if(_libssh2_get_u32(&buf, &(x11open_state->sport))) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "unexpected port size"); + failure_code = SSH_OPEN_CONNECT_FAILED; + goto x11_exit; + } + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection Received from %s:%ld on channel %lu", + x11open_state->shost, x11open_state->sport, + x11open_state->sender_channel); + + x11open_state->state = libssh2_NB_state_allocated; + } + + if(session->x11) { + if(x11open_state->state == libssh2_NB_state_allocated) { + channel = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if(!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + + channel->session = session; + channel->channel_type_len = sizeof("x11") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel->channel_type_len + + 1); + if(!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + memcpy(channel->channel_type, "x11", + channel->channel_type_len + 1); + + channel->remote.id = x11open_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + x11open_state->initial_window_size; + channel->local.window_size = x11open_state->initial_window_size; + channel->local.packet_size = x11open_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection established: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + x11open_state->state = libssh2_NB_state_created; + } + + if(x11open_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, x11open_state->packet, 17, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send channel open " + "confirmation"); + } + + /* Link the channel into the session */ + _libssh2_list_add(&session->channels, &channel->node); + + /* + * Pass control to the callback, they may turn right around and + * free the channel, or actually use it + */ + LIBSSH2_X11_OPEN(channel, (char *)x11open_state->shost, + x11open_state->sport); + + x11open_state->state = libssh2_NB_state_idle; + return 0; + } + } + else + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + /* fall-trough */ + x11_exit: + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, x11open_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, X11FwdUnAvil, sizeof(X11FwdUnAvil) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, x11open_state->packet, packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if(rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + } + x11open_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_add + * + * Create a new packet and attach it to the brigade. Called from the transport + * layer when it has received a packet. + * + * The input pointer 'data' is pointing to allocated data that this function + * is asked to deal with so on failure OR success, it must be freed fine. + * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN. + * + * This function will always be called with 'datalen' greater than zero. + */ +int +_libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, + size_t datalen, int macstate) +{ + int rc = 0; + unsigned char *message = NULL; + unsigned char *language = NULL; + size_t message_len = 0; + size_t language_len = 0; + LIBSSH2_CHANNEL *channelp = NULL; + size_t data_head = 0; + unsigned char msg = data[0]; + + switch(session->packAdd_state) { + case libssh2_NB_state_idle: + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Packet type %d received, length=%d", + (int) msg, (int) datalen); + + if((macstate == LIBSSH2_MAC_INVALID) && + (!session->macerror || + LIBSSH2_MACERROR(session, (char *) data, datalen))) { + /* Bad MAC input, but no callback set or non-zero return from the + callback */ + + LIBSSH2_FREE(session, data); + return _libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, + "Invalid MAC received"); + } + session->packAdd_state = libssh2_NB_state_allocated; + break; + case libssh2_NB_state_jump1: + goto libssh2_packet_add_jump_point1; + case libssh2_NB_state_jump2: + goto libssh2_packet_add_jump_point2; + case libssh2_NB_state_jump3: + goto libssh2_packet_add_jump_point3; + case libssh2_NB_state_jump4: + goto libssh2_packet_add_jump_point4; + case libssh2_NB_state_jump5: + goto libssh2_packet_add_jump_point5; + default: /* nothing to do */ + break; + } + + if(session->packAdd_state == libssh2_NB_state_allocated) { + /* A couple exceptions to the packet adding rule: */ + switch(msg) { + + /* + byte SSH_MSG_DISCONNECT + uint32 reason code + string description in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DISCONNECT: + if(datalen >= 5) { + uint32_t reason = 0; + struct string_buf buf; + buf.data = (unsigned char *)data; + buf.dataptr = buf.data; + buf.len = datalen; + buf.dataptr++; /* advance past type */ + + _libssh2_get_u32(&buf, &reason); + _libssh2_get_string(&buf, &message, &message_len); + _libssh2_get_string(&buf, &language, &language_len); + + if(session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, reason, (const char *)message, + message_len, (const char *)language, + language_len); + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Disconnect(%d): %s(%s)", reason, + message, language); + } + + LIBSSH2_FREE(session, data); + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + session->packAdd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "socket disconnect"); + /* + byte SSH_MSG_IGNORE + string data + */ + + case SSH_MSG_IGNORE: + if(datalen >= 2) { + if(session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, (char *) data + 1, datalen - 1); + } + } + else if(session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, "", 0); + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_DEBUG + boolean always_display + string message in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DEBUG: + if(datalen >= 2) { + int always_display = data[1]; + + if(datalen >= 6) { + struct string_buf buf; + buf.data = (unsigned char *)data; + buf.dataptr = buf.data; + buf.len = datalen; + buf.dataptr += 2; /* advance past type & always display */ + + _libssh2_get_string(&buf, &message, &message_len); + _libssh2_get_string(&buf, &language, &language_len); + } + + if(session->ssh_msg_debug) { + LIBSSH2_DEBUG(session, always_display, + (const char *)message, + message_len, (const char *)language, + language_len); + } + } + + /* + * _libssh2_debug will actually truncate this for us so + * that it's not an inordinate about of data + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Debug Packet: %s", message); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_EXT_INFO + uint32 nr-extensions + [repeat "nr-extensions" times] + string extension-name [RFC8308] + string extension-value (binary) + */ + + case SSH_MSG_EXT_INFO: + if(datalen >= 5) { + uint32_t nr_extensions = 0; + struct string_buf buf; + buf.data = (unsigned char *)data; + buf.dataptr = buf.data; + buf.len = datalen; + buf.dataptr += 1; /* advance past type */ + + if(_libssh2_get_u32(&buf, &nr_extensions) != 0) { + rc = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid extension info received"); + } + + while(rc == 0 && nr_extensions > 0) { + + size_t name_len = 0; + size_t value_len = 0; + unsigned char *name = NULL; + unsigned char *value = NULL; + + nr_extensions -= 1; + + _libssh2_get_string(&buf, &name, &name_len); + _libssh2_get_string(&buf, &value, &value_len); + + if(name != NULL && value != NULL) { + _libssh2_debug(session, + LIBSSH2_TRACE_KEX, + "Server to Client extension %.*s: %.*s", + name_len, name, value_len, value); + } + + if(name_len == 15 && + memcmp(name, "server-sig-algs", 15) == 0) { + if(session->server_sign_algorithms) { + LIBSSH2_FREE(session, + session->server_sign_algorithms); + } + + session->server_sign_algorithms = + LIBSSH2_ALLOC(session, + value_len + 1); + + if(session->server_sign_algorithms) { + memcpy(session->server_sign_algorithms, + value, value_len); + session->server_sign_algorithms[value_len] = '\0'; + } + else { + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "memory for server sign algo"); + } + } + } + } + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_GLOBAL_REQUEST + string request name in US-ASCII only + boolean want reply + .... request-specific data follows + */ + + case SSH_MSG_GLOBAL_REQUEST: + if(datalen >= 5) { + uint32_t len = 0; + unsigned char want_reply = 0; + len = _libssh2_ntohu32(data + 1); + if((len <= (UINT_MAX - 6)) && (datalen >= (6 + len))) { + want_reply = data[5 + len]; + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Received global request type %.*s (wr %X)", + len, data + 5, want_reply); + } + + + if(want_reply) { + static const unsigned char packet = + SSH_MSG_REQUEST_FAILURE; + libssh2_packet_add_jump_point5: + session->packAdd_state = libssh2_NB_state_jump5; + rc = _libssh2_transport_send(session, &packet, 1, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_EXTENDED_DATA + uint32 recipient channel + uint32 data_type_code + string data + */ + + case SSH_MSG_CHANNEL_EXTENDED_DATA: + /* streamid(4) */ + data_head += 4; + + /* fall-through */ + + /* + byte SSH_MSG_CHANNEL_DATA + uint32 recipient channel + string data + */ + + case SSH_MSG_CHANNEL_DATA: + /* packet_type(1) + channelno(4) + datalen(4) */ + data_head += 9; + + if(datalen >= data_head) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + + if(!channelp) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, + "Packet received for unknown channel"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } +#ifdef LIBSSH2DEBUG + { + uint32_t stream_id = 0; + if(msg == SSH_MSG_CHANNEL_EXTENDED_DATA) + stream_id = _libssh2_ntohu32(data + 5); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "%d bytes packet_add() for %lu/%lu/%lu", + (int) (datalen - data_head), + channelp->local.id, + channelp->remote.id, + stream_id); + } +#endif + if((channelp->remote.extended_data_ignore_mode == + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && + (msg == SSH_MSG_CHANNEL_EXTENDED_DATA)) { + /* Pretend we didn't receive this */ + LIBSSH2_FREE(session, data); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Ignoring extended data and refunding %d bytes", + (int) (datalen - 13)); + if(channelp->read_avail + datalen - data_head >= + channelp->remote.window_size) + datalen = channelp->remote.window_size - + channelp->read_avail + data_head; + + channelp->remote.window_size -= datalen - data_head; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "shrinking window size by %lu bytes to %lu, " + "read_avail %lu", + datalen - data_head, + channelp->remote.window_size, + channelp->read_avail); + + session->packAdd_channelp = channelp; + + /* Adjust the window based on the block we just freed */ + libssh2_packet_add_jump_point1: + session->packAdd_state = libssh2_NB_state_jump1; + rc = _libssh2_channel_receive_window_adjust(session-> + packAdd_channelp, + datalen - 13, + 1, NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + + /* + * REMEMBER! remote means remote as source of data, + * NOT remote window! + */ + if(channelp->remote.packet_size < (datalen - data_head)) { + /* + * Spec says we MAY ignore bytes sent beyond + * packet_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, + "Packet contains more data than we offered" + " to receive, truncating"); + datalen = channelp->remote.packet_size + data_head; + } + if(channelp->remote.window_size <= channelp->read_avail) { + /* + * Spec says we MAY ignore bytes sent beyond + * window_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "The current receive window is full," + " data ignored"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + /* Reset EOF status */ + channelp->remote.eof = 0; + + if(channelp->read_avail + datalen - data_head > + channelp->remote.window_size) { + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "Remote sent more data than current " + "window allows, truncating"); + datalen = channelp->remote.window_size - + channelp->read_avail + data_head; + } + + /* Update the read_avail counter. The window size will be + * updated once the data is actually read from the queue + * from an upper layer */ + channelp->read_avail += datalen - data_head; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "increasing read_avail by %lu bytes to %lu/%lu", + (long)(datalen - data_head), + (long)channelp->read_avail, + (long)channelp->remote.window_size); + + break; + + /* + byte SSH_MSG_CHANNEL_EOF + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_EOF: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if(!channelp) + /* We may have freed already, just quietly ignore this... */ + ; + else { + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "EOF received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + channelp->remote.eof = 1; + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_REQUEST + uint32 recipient channel + string request type in US-ASCII characters only + boolean want reply + .... type-specific data follows + */ + + case SSH_MSG_CHANNEL_REQUEST: + if(datalen >= 9) { + uint32_t channel = _libssh2_ntohu32(data + 1); + uint32_t len = _libssh2_ntohu32(data + 5); + unsigned char want_reply = 1; + + if((len + 9) < datalen) + want_reply = data[len + 9]; + + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Channel %d received request type %.*s (wr %X)", + channel, len, data + 9, want_reply); + + if(len == sizeof("exit-status") - 1 + && (sizeof("exit-status") - 1 + 9) <= datalen + && !memcmp("exit-status", data + 9, + sizeof("exit-status") - 1)) { + + /* we've got "exit-status" packet. Set the session value */ + if(datalen >= 20) + channelp = + _libssh2_channel_locate(session, channel); + + if(channelp && (sizeof("exit-status") + 13) <= datalen) { + channelp->exit_status = + _libssh2_ntohu32(data + 9 + sizeof("exit-status")); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit status %lu received for " + "channel %lu/%lu", + channelp->exit_status, + channelp->local.id, + channelp->remote.id); + } + + } + else if(len == sizeof("exit-signal") - 1 + && (sizeof("exit-signal") - 1 + 9) <= datalen + && !memcmp("exit-signal", data + 9, + sizeof("exit-signal") - 1)) { + /* command terminated due to signal */ + if(datalen >= 20) + channelp = _libssh2_channel_locate(session, channel); + + if(channelp && (sizeof("exit-signal") + 13) <= datalen) { + /* set signal name (without SIG prefix) */ + uint32_t namelen = + _libssh2_ntohu32(data + 9 + sizeof("exit-signal")); + + if(namelen <= UINT_MAX - 1) { + channelp->exit_signal = + LIBSSH2_ALLOC(session, namelen + 1); + } + else { + channelp->exit_signal = NULL; + } + + if(!channelp->exit_signal) + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "memory for signal name"); + else if((sizeof("exit-signal") + 13 + namelen <= + datalen)) { + memcpy(channelp->exit_signal, + data + 13 + sizeof("exit-signal"), namelen); + channelp->exit_signal[namelen] = '\0'; + /* TODO: save error message and language tag */ + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit signal %s received for " + "channel %lu/%lu", + channelp->exit_signal, + channelp->local.id, + channelp->remote.id); + } + } + } + + + if(want_reply) { + unsigned char packet[5]; + libssh2_packet_add_jump_point4: + session->packAdd_state = libssh2_NB_state_jump4; + packet[0] = SSH_MSG_CHANNEL_FAILURE; + memcpy(&packet[1], data + 1, 4); + rc = _libssh2_transport_send(session, packet, 5, NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_CLOSE + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_CLOSE: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if(!channelp) { + /* We may have freed already, just quietly ignore this... */ + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Close received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + + channelp->remote.close = 1; + channelp->remote.eof = 1; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_OPEN + string "session" + uint32 sender channel + uint32 initial window size + uint32 maximum packet size + */ + + case SSH_MSG_CHANNEL_OPEN: + if(datalen < 17) + ; + else if((datalen >= (sizeof("forwarded-tcpip") + 4)) && + ((sizeof("forwarded-tcpip") - 1) == + _libssh2_ntohu32(data + 1)) + && + (memcmp(data + 5, "forwarded-tcpip", + sizeof("forwarded-tcpip") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_Qlstn_state, 0, + sizeof(session->packAdd_Qlstn_state)); + + libssh2_packet_add_jump_point2: + session->packAdd_state = libssh2_NB_state_jump2; + rc = packet_queue_listener(session, data, datalen, + &session->packAdd_Qlstn_state); + } + else if((datalen >= (sizeof("x11") + 4)) && + ((sizeof("x11") - 1) == _libssh2_ntohu32(data + 1)) && + (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_x11open_state, 0, + sizeof(session->packAdd_x11open_state)); + + libssh2_packet_add_jump_point3: + session->packAdd_state = libssh2_NB_state_jump3; + rc = packet_x11_open(session, data, datalen, + &session->packAdd_x11open_state); + } + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_WINDOW_ADJUST + uint32 recipient channel + uint32 bytes to add + */ + case SSH_MSG_CHANNEL_WINDOW_ADJUST: + if(datalen < 9) + ; + else { + uint32_t bytestoadd = _libssh2_ntohu32(data + 5); + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if(channelp) { + channelp->local.window_size += bytestoadd; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Window adjust for channel %lu/%lu, " + "adding %lu bytes, new window_size=%lu", + channelp->local.id, + channelp->remote.id, + bytestoadd, + channelp->local.window_size); + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + default: + break; + } + + session->packAdd_state = libssh2_NB_state_sent; + } + + if(session->packAdd_state == libssh2_NB_state_sent) { + LIBSSH2_PACKET *packetp = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); + if(!packetp) { + _libssh2_debug(session, LIBSSH2_ERROR_ALLOC, + "memory for packet"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return LIBSSH2_ERROR_ALLOC; + } + packetp->data = data; + packetp->data_len = datalen; + packetp->data_head = data_head; + + _libssh2_list_add(&session->packets, &packetp->node); + + session->packAdd_state = libssh2_NB_state_sent1; + } + + if((msg == SSH_MSG_KEXINIT && + !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) || + (session->packAdd_state == libssh2_NB_state_sent2)) { + if(session->packAdd_state == libssh2_NB_state_sent1) { + /* + * Remote wants new keys + * Well, it's already in the brigade, + * let's just call back into ourselves + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Renegotiating Keys"); + + session->packAdd_state = libssh2_NB_state_sent2; + } + + /* + * The KEXINIT message has been added to the queue. The packAdd and + * readPack states need to be reset because _libssh2_kex_exchange + * (eventually) calls upon _libssh2_transport_read to read the rest of + * the key exchange conversation. + */ + session->readPack_state = libssh2_NB_state_idle; + session->packet.total_num = 0; + session->packAdd_state = libssh2_NB_state_idle; + session->fullpacket_state = libssh2_NB_state_idle; + + memset(&session->startup_key_state, 0, sizeof(key_exchange_state_t)); + + /* + * If there was a key reexchange failure, let's just hope we didn't + * send NEWKEYS yet, otherwise remote will drop us like a rock + */ + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->packAdd_state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_ask + * + * Scan the brigade for a matching packet type, optionally poll the socket for + * a packet first + */ +int +_libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, const unsigned char *match_buf, + size_t match_len) +{ + LIBSSH2_PACKET *packet = _libssh2_list_first(&session->packets); + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Looking for packet of type: %d", (int) packet_type); + + while(packet) { + if(packet->data[0] == packet_type + && (packet->data_len >= (match_ofs + match_len)) + && (!match_buf || + (memcmp(packet->data + match_ofs, match_buf, + match_len) == 0))) { + *data = packet->data; + *data_len = packet->data_len; + + /* unlink struct from session->packets */ + _libssh2_list_remove(&packet->node); + + LIBSSH2_FREE(session, packet); + + return 0; + } + packet = _libssh2_list_next(&packet->node); + } + return -1; +} + +/* + * libssh2_packet_askv + * + * Scan for any of a list of packet types in the brigade, optionally poll the + * socket for a packet first + */ +int +_libssh2_packet_askv(LIBSSH2_SESSION * session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len) +{ + int i, packet_types_len = strlen((char *) packet_types); + + for(i = 0; i < packet_types_len; i++) { + if(0 == _libssh2_packet_ask(session, packet_types[i], data, + data_len, match_ofs, + match_buf, match_len)) { + return 0; + } + } + + return -1; +} + +/* + * _libssh2_packet_require + * + * Loops _libssh2_transport_read() until the packet requested is available + * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout + * + * Returns negative on error + * Returns 0 when it has taken care of the requested packet. + */ +int +_libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_require_state_t *state) +{ + if(state->start == 0) { + if(_libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, + match_len) == 0) { + /* A packet was available in the packet brigade */ + return 0; + } + + state->start = time(NULL); + } + + while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = _libssh2_transport_read(session); + if(ret == LIBSSH2_ERROR_EAGAIN) + return ret; + else if(ret < 0) { + state->start = 0; + /* an error which is not just because of blocking */ + return ret; + } + else if(ret == packet_type) { + /* Be lazy, let packet_ask pull it out of the brigade */ + ret = _libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, match_len); + state->start = 0; + return ret; + } + else if(ret == 0) { + /* nothing available, wait until data arrives or we time out */ + long left = LIBSSH2_READ_TIMEOUT - (long)(time(NULL) - + state->start); + + if(left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + return -1; /* no packet available yet */ + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_burn + * + * Loops _libssh2_transport_read() until any packet is available and promptly + * discards it. + * Used during KEX exchange to discard badly guessed KEX_INIT packets + */ +int +_libssh2_packet_burn(LIBSSH2_SESSION * session, + libssh2_nonblocking_states * state) +{ + unsigned char *data; + size_t data_len; + unsigned char i, all_packets[255]; + int ret; + + if(*state == libssh2_NB_state_idle) { + for(i = 1; i < 255; i++) { + all_packets[i - 1] = i; + } + all_packets[254] = 0; + + if(_libssh2_packet_askv(session, all_packets, &data, &data_len, 0, + NULL, 0) == 0) { + i = data[0]; + /* A packet was available in the packet brigade, burn it */ + LIBSSH2_FREE(session, data); + return i; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Blocking until packet becomes available to burn"); + *state = libssh2_NB_state_created; + } + + while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + ret = _libssh2_transport_read(session); + if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + else if(ret < 0) { + *state = libssh2_NB_state_idle; + return ret; + } + else if(ret == 0) { + /* FIXME: this might busyloop */ + continue; + } + + /* Be lazy, let packet_ask pull it out of the brigade */ + if(0 == + _libssh2_packet_ask(session, (unsigned char)ret, + &data, &data_len, 0, NULL, 0)) { + /* Smoke 'em if you got 'em */ + LIBSSH2_FREE(session, data); + *state = libssh2_NB_state_idle; + return ret; + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_requirev + * + * Loops _libssh2_transport_read() until one of a list of packet types + * requested is available. SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause + * a bailout. packet_types is a null terminated list of packet_type numbers + */ + +int +_libssh2_packet_requirev(LIBSSH2_SESSION *session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, size_t match_len, + packet_requirev_state_t * state) +{ + if(_libssh2_packet_askv(session, packet_types, data, data_len, match_ofs, + match_buf, match_len) == 0) { + /* One of the packets listed was available in the packet brigade */ + state->start = 0; + return 0; + } + + if(state->start == 0) { + state->start = time(NULL); + } + + while(session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) { + int ret = _libssh2_transport_read(session); + if((ret < 0) && (ret != LIBSSH2_ERROR_EAGAIN)) { + state->start = 0; + return ret; + } + if(ret <= 0) { + long left = LIBSSH2_READ_TIMEOUT - + (long)(time(NULL) - state->start); + + if(left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + else if(ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + } + + if(strchr((char *) packet_types, ret)) { + /* Be lazy, let packet_ask pull it out of the brigade */ + ret = _libssh2_packet_askv(session, packet_types, data, + data_len, match_ofs, match_buf, + match_len); + state->start = 0; + return ret; + } + } + + /* Only reached if the socket died */ + state->start = 0; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +#endif diff --git a/lib/libssh2/packet.h b/lib/libssh2/packet.h new file mode 100644 index 0000000..009dc7b --- /dev/null +++ b/lib/libssh2/packet.h @@ -0,0 +1,78 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_PACKET_H +#define __LIBSSH2_PACKET_H +/* + * Copyright (C) 2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +int _libssh2_packet_read(LIBSSH2_SESSION * session); + +int _libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len); + +int _libssh2_packet_askv(LIBSSH2_SESSION * session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len); +int _libssh2_packet_require(LIBSSH2_SESSION * session, + unsigned char packet_type, unsigned char **data, + size_t *data_len, int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_require_state_t * state); +int _libssh2_packet_requirev(LIBSSH2_SESSION *session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_requirev_state_t * state); +int _libssh2_packet_burn(LIBSSH2_SESSION * session, + libssh2_nonblocking_states * state); +int _libssh2_packet_write(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long data_len); +int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, + size_t datalen, int macstate); + +#endif /* __LIBSSH2_PACKET_H */ +#endif diff --git a/lib/libssh2/pem.c b/lib/libssh2/pem.c new file mode 100644 index 0000000..4f2ce48 --- /dev/null +++ b/lib/libssh2/pem.c @@ -0,0 +1,913 @@ +#if defined(ESP32) +/* Copyright (C) 2007 The Written Word, Inc. + * Copyright (C) 2008, Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +static int +readline(char *line, int line_size, FILE * fp) +{ + size_t len; + + if(!line) { + return -1; + } + if(!fgets(line, line_size, fp)) { + return -1; + } + + if(*line) { + len = strlen(line); + if(len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + } + + if(*line) { + len = strlen(line); + if(len > 0 && line[len - 1] == '\r') { + line[len - 1] = '\0'; + } + } + + return 0; +} + +static int +readline_memory(char *line, size_t line_size, + const char *filedata, size_t filedata_len, + size_t *filedata_offset) +{ + size_t off, len; + + off = *filedata_offset; + + for(len = 0; off + len < filedata_len && len < line_size - 1; len++) { + if(filedata[off + len] == '\n' || + filedata[off + len] == '\r') { + break; + } + } + + if(len) { + memcpy(line, filedata + off, len); + *filedata_offset += len; + } + + line[len] = '\0'; + *filedata_offset += 1; + + return 0; +} + +#define LINE_SIZE 128 + +static const char *crypt_annotation = "Proc-Type: 4,ENCRYPTED"; + +static unsigned char hex_decode(char digit) +{ + return (digit >= 'A') ? 0xA + (digit - 'A') : (digit - '0'); +} + +int +_libssh2_pem_parse(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + const unsigned char *passphrase, + FILE * fp, unsigned char **data, unsigned int *datalen) +{ + char line[LINE_SIZE]; + unsigned char iv[LINE_SIZE]; + char *b64data = NULL; + unsigned int b64datalen = 0; + int ret; + const LIBSSH2_CRYPT_METHOD *method = NULL; + + do { + *line = '\0'; + + if(readline(line, LINE_SIZE, fp)) { + return -1; + } + } + while(strcmp(line, headerbegin) != 0); + + if(readline(line, LINE_SIZE, fp)) { + return -1; + } + + if(passphrase && + memcmp(line, crypt_annotation, strlen(crypt_annotation)) == 0) { + const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; + int i; + + if(readline(line, LINE_SIZE, fp)) { + ret = -1; + goto out; + } + + all_methods = libssh2_crypt_methods(); + while((cur_method = *all_methods++) != NULL) { + if(*cur_method->pem_annotation && + memcmp(line, cur_method->pem_annotation, + strlen(cur_method->pem_annotation)) == 0) { + method = cur_method; + memcpy(iv, line + strlen(method->pem_annotation) + 1, + 2*method->iv_len); + } + } + + /* None of the available crypt methods were able to decrypt the key */ + if(method == NULL) + return -1; + + /* Decode IV from hex */ + for(i = 0; i < method->iv_len; ++i) { + iv[i] = hex_decode(iv[2*i]) << 4; + iv[i] |= hex_decode(iv[2*i + 1]); + } + + /* skip to the next line */ + if(readline(line, LINE_SIZE, fp)) { + ret = -1; + goto out; + } + } + + do { + if(*line) { + char *tmp; + size_t linelen; + + linelen = strlen(line); + tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); + if(!tmp) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for PEM parsing"); + ret = -1; + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + *line = '\0'; + + if(readline(line, LINE_SIZE, fp)) { + ret = -1; + goto out; + } + } while(strcmp(line, headerend) != 0); + + if(!b64data) { + return -1; + } + + if(libssh2_base64_decode(session, (char **) data, datalen, + b64data, b64datalen)) { + ret = -1; + goto out; + } + + if(method) { + /* Set up decryption */ + int free_iv = 0, free_secret = 0, len_decrypted = 0, padding = 0; + int blocksize = method->blocksize; + void *abstract; + unsigned char secret[2*MD5_DIGEST_LENGTH]; + libssh2_md5_ctx fingerprint_ctx; + + /* Perform key derivation (PBKDF1/MD5) */ + if(!libssh2_md5_init(&fingerprint_ctx)) { + ret = -1; + goto out; + } + libssh2_md5_update(fingerprint_ctx, passphrase, + strlen((char *)passphrase)); + libssh2_md5_update(fingerprint_ctx, iv, 8); + libssh2_md5_final(fingerprint_ctx, secret); + if(method->secret_len > MD5_DIGEST_LENGTH) { + if(!libssh2_md5_init(&fingerprint_ctx)) { + ret = -1; + goto out; + } + libssh2_md5_update(fingerprint_ctx, secret, MD5_DIGEST_LENGTH); + libssh2_md5_update(fingerprint_ctx, passphrase, + strlen((char *)passphrase)); + libssh2_md5_update(fingerprint_ctx, iv, 8); + libssh2_md5_final(fingerprint_ctx, secret + MD5_DIGEST_LENGTH); + } + + /* Initialize the decryption */ + if(method->init(session, method, iv, &free_iv, secret, + &free_secret, 0, &abstract)) { + _libssh2_explicit_zero((char *)secret, sizeof(secret)); + LIBSSH2_FREE(session, data); + ret = -1; + goto out; + } + + if(free_secret) { + _libssh2_explicit_zero((char *)secret, sizeof(secret)); + } + + /* Do the actual decryption */ + if((*datalen % blocksize) != 0) { + _libssh2_explicit_zero((char *)secret, sizeof(secret)); + method->dtor(session, &abstract); + _libssh2_explicit_zero(*data, *datalen); + LIBSSH2_FREE(session, *data); + ret = -1; + goto out; + } + + while(len_decrypted <= (int)*datalen - blocksize) { + if(method->crypt(session, *data + len_decrypted, blocksize, + &abstract)) { + ret = LIBSSH2_ERROR_DECRYPT; + _libssh2_explicit_zero((char *)secret, sizeof(secret)); + method->dtor(session, &abstract); + _libssh2_explicit_zero(*data, *datalen); + LIBSSH2_FREE(session, *data); + goto out; + } + + len_decrypted += blocksize; + } + + /* Account for padding */ + padding = (*data)[*datalen - 1]; + memset(&(*data)[*datalen-padding], 0, padding); + *datalen -= padding; + + /* Clean up */ + _libssh2_explicit_zero((char *)secret, sizeof(secret)); + method->dtor(session, &abstract); + } + + ret = 0; + out: + if(b64data) { + _libssh2_explicit_zero(b64data, b64datalen); + LIBSSH2_FREE(session, b64data); + } + return ret; +} + +int +_libssh2_pem_parse_memory(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + const char *filedata, size_t filedata_len, + unsigned char **data, unsigned int *datalen) +{ + char line[LINE_SIZE]; + char *b64data = NULL; + unsigned int b64datalen = 0; + size_t off = 0; + int ret; + + do { + *line = '\0'; + + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + return -1; + } + } + while(strcmp(line, headerbegin) != 0); + + *line = '\0'; + + do { + if(*line) { + char *tmp; + size_t linelen; + + linelen = strlen(line); + tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); + if(!tmp) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for PEM parsing"); + ret = -1; + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + *line = '\0'; + + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + ret = -1; + goto out; + } + } while(strcmp(line, headerend) != 0); + + if(!b64data) { + return -1; + } + + if(libssh2_base64_decode(session, (char **) data, datalen, + b64data, b64datalen)) { + ret = -1; + goto out; + } + + ret = 0; + out: + if(b64data) { + _libssh2_explicit_zero(b64data, b64datalen); + LIBSSH2_FREE(session, b64data); + } + return ret; +} + +/* OpenSSH formatted keys */ +#define AUTH_MAGIC "openssh-key-v1" +#define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" +#define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" + +static int +_libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session, + const unsigned char *passphrase, + const char *b64data, size_t b64datalen, + struct string_buf **decrypted_buf) +{ + const LIBSSH2_CRYPT_METHOD *method = NULL; + struct string_buf decoded, decrypted, kdf_buf; + unsigned char *ciphername = NULL; + unsigned char *kdfname = NULL; + unsigned char *kdf = NULL; + unsigned char *buf = NULL; + unsigned char *salt = NULL; + uint32_t nkeys, check1, check2; + uint32_t rounds = 0; + unsigned char *key = NULL; + unsigned char *key_part = NULL; + unsigned char *iv_part = NULL; + unsigned char *f = NULL; + unsigned int f_len = 0; + int ret = 0, keylen = 0, ivlen = 0, total_len = 0; + size_t kdf_len = 0, tmp_len = 0, salt_len = 0; + + if(decrypted_buf) + *decrypted_buf = NULL; + + /* decode file */ + if(libssh2_base64_decode(session, (char **)&f, &f_len, + b64data, b64datalen)) { + ret = -1; + goto out; + } + + /* Parse the file */ + decoded.data = (unsigned char *)f; + decoded.dataptr = (unsigned char *)f; + decoded.len = f_len; + + if(decoded.len < strlen(AUTH_MAGIC)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, "key too short"); + goto out; + } + + if(strncmp((char *) decoded.dataptr, AUTH_MAGIC, + strlen(AUTH_MAGIC)) != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "key auth magic mismatch"); + goto out; + } + + decoded.dataptr += strlen(AUTH_MAGIC) + 1; + + if(_libssh2_get_string(&decoded, &ciphername, &tmp_len) || + tmp_len == 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "ciphername is missing"); + goto out; + } + + if(_libssh2_get_string(&decoded, &kdfname, &tmp_len) || + tmp_len == 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "kdfname is missing"); + goto out; + } + + if(_libssh2_get_string(&decoded, &kdf, &kdf_len)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "kdf is missing"); + goto out; + } + else { + kdf_buf.data = kdf; + kdf_buf.dataptr = kdf; + kdf_buf.len = kdf_len; + } + + if((passphrase == NULL || strlen((const char *)passphrase) == 0) && + strcmp((const char *)ciphername, "none") != 0) { + /* passphrase required */ + ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; + goto out; + } + + if(strcmp((const char *)kdfname, "none") != 0 && + strcmp((const char *)kdfname, "bcrypt") != 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "unknown cipher"); + goto out; + } + + if(!strcmp((const char *)kdfname, "none") && + strcmp((const char *)ciphername, "none") != 0) { + ret =_libssh2_error(session, LIBSSH2_ERROR_PROTO, + "invalid format"); + goto out; + } + + if(_libssh2_get_u32(&decoded, &nkeys) != 0 || nkeys != 1) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Multiple keys are unsupported"); + goto out; + } + + /* unencrypted public key */ + + if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid private key; " + "expect embedded public key"); + goto out; + } + + if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Private key data not found"); + goto out; + } + + /* decode encrypted private key */ + decrypted.data = decrypted.dataptr = buf; + decrypted.len = tmp_len; + + if(ciphername && strcmp((const char *)ciphername, "none") != 0) { + const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; + + all_methods = libssh2_crypt_methods(); + while((cur_method = *all_methods++) != NULL) { + if(*cur_method->name && + memcmp(ciphername, cur_method->name, + strlen(cur_method->name)) == 0) { + method = cur_method; + } + } + + /* None of the available crypt methods were able to decrypt the key */ + + if(method == NULL) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No supported cipher found"); + goto out; + } + } + + if(method) { + int free_iv = 0, free_secret = 0, len_decrypted = 0; + int blocksize; + void *abstract = NULL; + + keylen = method->secret_len; + ivlen = method->iv_len; + total_len = keylen + ivlen; + + key = LIBSSH2_CALLOC(session, total_len); + if(key == NULL) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Could not alloc key"); + goto out; + } + + if(strcmp((const char *)kdfname, "bcrypt") == 0 && + passphrase != NULL) { + if((_libssh2_get_string(&kdf_buf, &salt, &salt_len)) || + (_libssh2_get_u32(&kdf_buf, &rounds) != 0) ) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "kdf contains unexpected values"); + LIBSSH2_FREE(session, key); + goto out; + } + + if(_libssh2_bcrypt_pbkdf((const char *)passphrase, + strlen((const char *)passphrase), + salt, salt_len, key, + keylen + ivlen, rounds) < 0) { + ret = _libssh2_error(session, LIBSSH2_ERROR_DECRYPT, + "invalid format"); + LIBSSH2_FREE(session, key); + goto out; + } + } + else { + ret = _libssh2_error(session, LIBSSH2_ERROR_KEYFILE_AUTH_FAILED, + "bcrypted without passphrase"); + LIBSSH2_FREE(session, key); + goto out; + } + + /* Set up decryption */ + blocksize = method->blocksize; + + key_part = LIBSSH2_CALLOC(session, keylen); + if(key_part == NULL) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Could not alloc key part"); + goto out; + } + + iv_part = LIBSSH2_CALLOC(session, ivlen); + if(iv_part == NULL) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Could not alloc iv part"); + goto out; + } + + memcpy(key_part, key, keylen); + memcpy(iv_part, key + keylen, ivlen); + + /* Initialize the decryption */ + if(method->init(session, method, iv_part, &free_iv, key_part, + &free_secret, 0, &abstract)) { + ret = LIBSSH2_ERROR_DECRYPT; + goto out; + } + + /* Do the actual decryption */ + if((decrypted.len % blocksize) != 0) { + method->dtor(session, &abstract); + ret = LIBSSH2_ERROR_DECRYPT; + goto out; + } + + while((size_t)len_decrypted <= decrypted.len - blocksize) { + if(method->crypt(session, decrypted.data + len_decrypted, + blocksize, + &abstract)) { + ret = LIBSSH2_ERROR_DECRYPT; + method->dtor(session, &abstract); + goto out; + } + + len_decrypted += blocksize; + } + + /* No padding */ + + method->dtor(session, &abstract); + } + + /* Check random bytes match */ + + if(_libssh2_get_u32(&decrypted, &check1) != 0 || + _libssh2_get_u32(&decrypted, &check2) != 0 || + check1 != check2) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Private key unpack failed (correct password?)"); + ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; + goto out; + } + + if(decrypted_buf != NULL) { + /* copy data to out-going buffer */ + struct string_buf *out_buf = _libssh2_string_buf_new(session); + if(!out_buf) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "decrypted struct"); + goto out; + } + + out_buf->data = LIBSSH2_CALLOC(session, decrypted.len); + if(out_buf->data == NULL) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "decrypted struct"); + _libssh2_string_buf_free(session, out_buf); + goto out; + } + memcpy(out_buf->data, decrypted.data, decrypted.len); + out_buf->dataptr = out_buf->data + + (decrypted.dataptr - decrypted.data); + out_buf->len = decrypted.len; + + *decrypted_buf = out_buf; + } + +out: + + /* Clean up */ + if(key) { + _libssh2_explicit_zero(key, total_len); + LIBSSH2_FREE(session, key); + } + if(key_part) { + _libssh2_explicit_zero(key_part, keylen); + LIBSSH2_FREE(session, key_part); + } + if(iv_part) { + _libssh2_explicit_zero(iv_part, ivlen); + LIBSSH2_FREE(session, iv_part); + } + if(f) { + _libssh2_explicit_zero(f, f_len); + LIBSSH2_FREE(session, f); + } + + return ret; +} + +int +_libssh2_openssh_pem_parse(LIBSSH2_SESSION * session, + const unsigned char *passphrase, + FILE * fp, struct string_buf **decrypted_buf) +{ + char line[LINE_SIZE]; + char *b64data = NULL; + unsigned int b64datalen = 0; + int ret = 0; + + /* read file */ + + do { + *line = '\0'; + + if(readline(line, LINE_SIZE, fp)) { + return -1; + } + } + while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); + + if(readline(line, LINE_SIZE, fp)) { + return -1; + } + + do { + if(*line) { + char *tmp; + size_t linelen; + + linelen = strlen(line); + tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); + if(!tmp) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for PEM parsing"); + ret = -1; + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + *line = '\0'; + + if(readline(line, LINE_SIZE, fp)) { + ret = -1; + goto out; + } + } while(strcmp(line, OPENSSH_HEADER_END) != 0); + + if(!b64data) { + return -1; + } + + ret = _libssh2_openssh_pem_parse_data(session, + passphrase, + (const char *)b64data, + (size_t)b64datalen, + decrypted_buf); + + if(b64data) { + _libssh2_explicit_zero(b64data, b64datalen); + LIBSSH2_FREE(session, b64data); + } + +out: + + return ret; +} + +int +_libssh2_openssh_pem_parse_memory(LIBSSH2_SESSION * session, + const unsigned char *passphrase, + const char *filedata, size_t filedata_len, + struct string_buf **decrypted_buf) +{ + char line[LINE_SIZE]; + char *b64data = NULL; + unsigned int b64datalen = 0; + size_t off = 0; + int ret; + + if(filedata == NULL || filedata_len <= 0) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Error parsing PEM: filedata missing"); + + do { + + *line = '\0'; + + if(off >= filedata_len) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Error parsing PEM: offset out of bounds"); + + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + return -1; + } + } + while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); + + *line = '\0'; + + do { + if (*line) { + char *tmp; + size_t linelen; + + linelen = strlen(line); + tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); + if(!tmp) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "PEM parsing"); + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + *line = '\0'; + + if(off >= filedata_len) { + ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Error parsing PEM: offset out of bounds"); + goto out; + } + + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + ret = -1; + goto out; + } + } while(strcmp(line, OPENSSH_HEADER_END) != 0); + + if(!b64data) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Error parsing PEM: base 64 data missing"); + + ret = _libssh2_openssh_pem_parse_data(session, passphrase, b64data, + b64datalen, decrypted_buf); + +out: + if(b64data) { + _libssh2_explicit_zero(b64data, b64datalen); + LIBSSH2_FREE(session, b64data); + } + return ret; + +} + +static int +read_asn1_length(const unsigned char *data, + unsigned int datalen, unsigned int *len) +{ + unsigned int lenlen; + int nextpos; + + if(datalen < 1) { + return -1; + } + *len = data[0]; + + if(*len >= 0x80) { + lenlen = *len & 0x7F; + *len = data[1]; + if(1 + lenlen > datalen) { + return -1; + } + if(lenlen > 1) { + *len <<= 8; + *len |= data[2]; + } + } + else { + lenlen = 0; + } + + nextpos = 1 + lenlen; + if(lenlen > 2 || 1 + lenlen + *len > datalen) { + return -1; + } + + return nextpos; +} + +int +_libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen) +{ + unsigned int len; + int lenlen; + + if(*datalen < 1) { + return -1; + } + + if((*data)[0] != '\x30') { + return -1; + } + + (*data)++; + (*datalen)--; + + lenlen = read_asn1_length(*data, *datalen, &len); + if(lenlen < 0 || lenlen + len != *datalen) { + return -1; + } + + *data += lenlen; + *datalen -= lenlen; + + return 0; +} + +int +_libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, + unsigned char **i, unsigned int *ilen) +{ + unsigned int len; + int lenlen; + + if(*datalen < 1) { + return -1; + } + + if((*data)[0] != '\x02') { + return -1; + } + + (*data)++; + (*datalen)--; + + lenlen = read_asn1_length(*data, *datalen, &len); + if(lenlen < 0 || lenlen + len > *datalen) { + return -1; + } + + *data += lenlen; + *datalen -= lenlen; + + *i = *data; + *ilen = len; + + *data += len; + *datalen -= len; + + return 0; +} +#endif diff --git a/lib/libssh2/session.c b/lib/libssh2/session.c new file mode 100644 index 0000000..d76105b --- /dev/null +++ b/lib/libssh2/session.c @@ -0,0 +1,1860 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2015 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#ifdef HAVE_GETTIMEOFDAY +#include +#endif +#ifdef HAVE_ALLOCA_H +#include +#endif + +#include "transport.h" +#include "session.h" +#include "channel.h" +#include "mac.h" +#include "misc.h" + +/* libssh2_default_alloc + */ +static +LIBSSH2_ALLOC_FUNC(libssh2_default_alloc) +{ + (void) abstract; + return malloc(count); +} + +/* libssh2_default_free + */ +static +LIBSSH2_FREE_FUNC(libssh2_default_free) +{ + (void) abstract; + free(ptr); +} + +/* libssh2_default_realloc + */ +static +LIBSSH2_REALLOC_FUNC(libssh2_default_realloc) +{ + (void) abstract; + return realloc(ptr, count); +} + +/* + * banner_receive + * + * Wait for a hello from the remote host + * Allocate a buffer and store the banner in session->remote.banner + * Returns: 0 on success, LIBSSH2_ERROR_EAGAIN if read would block, negative + * on failure + */ +static int +banner_receive(LIBSSH2_SESSION * session) +{ + int ret; + int banner_len; + + if(session->banner_TxRx_state == libssh2_NB_state_idle) { + banner_len = 0; + + session->banner_TxRx_state = libssh2_NB_state_created; + } + else { + banner_len = session->banner_TxRx_total_send; + } + + while((banner_len < (int) sizeof(session->banner_TxRx_banner)) && + ((banner_len == 0) + || (session->banner_TxRx_banner[banner_len - 1] != '\n'))) { + char c = '\0'; + + /* no incoming block yet! */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND; + + ret = LIBSSH2_RECV(session, &c, 1, + LIBSSH2_SOCKET_RECV_FLAGS(session)); + if(ret < 0) { + if(session->api_block_mode || (ret != -EAGAIN)) + /* ignore EAGAIN when non-blocking */ + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error recving %d bytes: %d", 1, -ret); + } + else + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Recved %d bytes banner", ret); + + if(ret < 0) { + if(ret == -EAGAIN) { + session->socket_block_directions = + LIBSSH2_SESSION_BLOCK_INBOUND; + session->banner_TxRx_total_send = banner_len; + return LIBSSH2_ERROR_EAGAIN; + } + + /* Some kinda error */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_SOCKET_RECV; + } + + if(ret == 0) { + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; + } + + if((c == '\r' || c == '\n') && banner_len == 0) { + continue; + } + + if(c == '\0') { + /* NULLs are not allowed in SSH banners */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_BANNER_RECV; + } + + session->banner_TxRx_banner[banner_len++] = c; + } + + while(banner_len && + ((session->banner_TxRx_banner[banner_len - 1] == '\n') || + (session->banner_TxRx_banner[banner_len - 1] == '\r'))) { + banner_len--; + } + + /* From this point on, we are done here */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + + if(!banner_len) + return LIBSSH2_ERROR_BANNER_RECV; + + if(session->remote.banner) + LIBSSH2_FREE(session, session->remote.banner); + + session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1); + if(!session->remote.banner) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Error allocating space for remote banner"); + } + memcpy(session->remote.banner, session->banner_TxRx_banner, banner_len); + session->remote.banner[banner_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Received Banner: %s", + session->remote.banner); + return LIBSSH2_ERROR_NONE; +} + +/* + * banner_send + * + * Send the default banner, or the one set via libssh2_setopt_string + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block - and if it does so, you + * should call this function again as soon as it is likely that more data can + * be sent, and this function should then be called with the same argument set + * (same data pointer and same data_len) until zero or failure is returned. + */ +static int +banner_send(LIBSSH2_SESSION * session) +{ + char *banner = (char *) LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF; + int banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1; + ssize_t ret; +#ifdef LIBSSH2DEBUG + char banner_dup[256]; +#endif + + if(session->banner_TxRx_state == libssh2_NB_state_idle) { + if(session->local.banner) { + /* setopt_string will have given us our \r\n characters */ + banner_len = strlen((char *) session->local.banner); + banner = (char *) session->local.banner; + } +#ifdef LIBSSH2DEBUG + /* Hack and slash to avoid sending CRLF in debug output */ + if(banner_len < 256) { + memcpy(banner_dup, banner, banner_len - 2); + banner_dup[banner_len - 2] = '\0'; + } + else { + memcpy(banner_dup, banner, 255); + banner_dup[255] = '\0'; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Sending Banner: %s", + banner_dup); +#endif + + session->banner_TxRx_state = libssh2_NB_state_created; + } + + /* no outgoing block yet! */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND; + + ret = LIBSSH2_SEND(session, + banner + session->banner_TxRx_total_send, + banner_len - session->banner_TxRx_total_send, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if(ret < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", + banner_len - session->banner_TxRx_total_send, -ret); + else + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p+%d", ret, + banner_len - session->banner_TxRx_total_send, + banner, session->banner_TxRx_total_send); + + if(ret != (banner_len - session->banner_TxRx_total_send)) { + if(ret >= 0 || ret == -EAGAIN) { + /* the whole packet could not be sent, save the what was */ + session->socket_block_directions = + LIBSSH2_SESSION_BLOCK_OUTBOUND; + if(ret > 0) + session->banner_TxRx_total_send += ret; + return LIBSSH2_ERROR_EAGAIN; + } + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_SOCKET_RECV; + } + + /* Set the state back to idle */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + + return 0; +} + +/* + * session_nonblock() sets the given socket to either blocking or + * non-blocking mode based on the 'nonblock' boolean argument. This function + * is copied from the libcurl sources with permission. + */ +static int +session_nonblock(libssh2_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */ ) +{ +#undef SETBLOCK +#define SETBLOCK 0 +#ifdef HAVE_O_NONBLOCK + /* most recent unix versions */ + int flags; + + flags = fcntl(sockfd, F_GETFL, 0); + if(nonblock) + return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + else + return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); +#undef SETBLOCK +#define SETBLOCK 1 +#endif + +#if defined(HAVE_FIONBIO) && (SETBLOCK == 0) + /* older unix versions and VMS*/ + int flags; + + flags = nonblock; + return ioctl(sockfd, FIONBIO, &flags); +#undef SETBLOCK +#define SETBLOCK 2 +#endif + +#if defined(HAVE_IOCTLSOCKET) && (SETBLOCK == 0) + /* Windows? */ + unsigned long flags; + flags = nonblock; + + return ioctlsocket(sockfd, FIONBIO, &flags); +#undef SETBLOCK +#define SETBLOCK 3 +#endif + +#if defined(HAVE_IOCTLSOCKET_CASE) && (SETBLOCK == 0) + /* presumably for Amiga */ + return IoctlSocket(sockfd, FIONBIO, (long) nonblock); +#undef SETBLOCK +#define SETBLOCK 4 +#endif + +#if defined(HAVE_SO_NONBLOCK) && (SETBLOCK == 0) + /* BeOS */ + long b = nonblock ? 1 : 0; + return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); +#undef SETBLOCK +#define SETBLOCK 5 +#endif + +#ifdef HAVE_DISABLED_NONBLOCKING + return 0; /* returns success */ +#undef SETBLOCK +#define SETBLOCK 6 +#endif + +#if(SETBLOCK == 0) +#error "no non-blocking method was found/used/set" +#endif +} + +/* + * get_socket_nonblocking() + * + * gets the given blocking or non-blocking state of the socket. + */ +static int +get_socket_nonblocking(libssh2_socket_t sockfd) +{ /* operate on this */ +#undef GETBLOCK +#define GETBLOCK 0 +#ifdef HAVE_O_NONBLOCK + /* most recent unix versions */ + int flags = fcntl(sockfd, F_GETFL, 0); + + if(flags == -1) { + /* Assume blocking on error */ + return 1; + } + return (flags & O_NONBLOCK); +#undef GETBLOCK +#define GETBLOCK 1 +#endif + +#if defined(WSAEWOULDBLOCK) && (GETBLOCK == 0) + /* Windows? */ + unsigned int option_value; + socklen_t option_len = sizeof(option_value); + + if(getsockopt + (sockfd, SOL_SOCKET, SO_ERROR, (void *) &option_value, &option_len)) { + /* Assume blocking on error */ + return 1; + } + return (int) option_value; +#undef GETBLOCK +#define GETBLOCK 2 +#endif + +#if defined(HAVE_SO_NONBLOCK) && (GETBLOCK == 0) + /* BeOS */ + long b; + if(getsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b))) { + /* Assume blocking on error */ + return 1; + } + return (int) b; +#undef GETBLOCK +#define GETBLOCK 5 +#endif + +#if defined(SO_STATE) && defined(__VMS) && (GETBLOCK == 0) + + /* VMS TCP/IP Services */ + + size_t sockstat = 0; + int callstat = 0; + size_t size = sizeof(int); + + callstat = getsockopt(sockfd, SOL_SOCKET, SO_STATE, + (char *)&sockstat, &size); + if(callstat == -1) return 0; + if((sockstat&SS_NBIO) != 0) return 1; + return 0; + +#undef GETBLOCK +#define GETBLOCK 6 +#endif + +#ifdef HAVE_DISABLED_NONBLOCKING + return 1; /* returns blocking */ +#undef GETBLOCK +#define GETBLOCK 7 +#endif + +#if(GETBLOCK == 0) +#error "no non-blocking method was found/used/get" +#endif +} + +/* libssh2_session_banner_set + * Set the local banner to use in the server handshake. + */ +LIBSSH2_API int +libssh2_session_banner_set(LIBSSH2_SESSION * session, const char *banner) +{ + size_t banner_len = banner ? strlen(banner) : 0; + + if(session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + session->local.banner = NULL; + } + + if(!banner_len) + return 0; + + session->local.banner = LIBSSH2_ALLOC(session, banner_len + 3); + if(!session->local.banner) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for local banner"); + } + + memcpy(session->local.banner, banner, banner_len); + + /* first zero terminate like this so that the debug output is nice */ + session->local.banner[banner_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Setting local Banner: %s", + session->local.banner); + session->local.banner[banner_len++] = '\r'; + session->local.banner[banner_len++] = '\n'; + session->local.banner[banner_len] = '\0'; + + return 0; +} + +/* libssh2_banner_set + * Set the local banner. DEPRECATED VERSION + */ +LIBSSH2_API int +libssh2_banner_set(LIBSSH2_SESSION * session, const char *banner) +{ + return libssh2_session_banner_set(session, banner); +} + +/* + * libssh2_session_init_ex + * + * Allocate and initialize a libssh2 session structure. Allows for malloc + * callbacks in case the calling program has its own memory manager It's + * allowable (but unadvisable) to define some but not all of the malloc + * callbacks An additional pointer value may be optionally passed to be sent + * to the callbacks (so they know who's asking) + */ +LIBSSH2_API LIBSSH2_SESSION * +libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), + LIBSSH2_FREE_FUNC((*my_free)), + LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract) +{ + LIBSSH2_ALLOC_FUNC((*local_alloc)) = libssh2_default_alloc; + LIBSSH2_FREE_FUNC((*local_free)) = libssh2_default_free; + LIBSSH2_REALLOC_FUNC((*local_realloc)) = libssh2_default_realloc; + LIBSSH2_SESSION *session; + + if(my_alloc) { + local_alloc = my_alloc; + } + if(my_free) { + local_free = my_free; + } + if(my_realloc) { + local_realloc = my_realloc; + } + + session = local_alloc(sizeof(LIBSSH2_SESSION), &abstract); + if(session) { + memset(session, 0, sizeof(LIBSSH2_SESSION)); + session->alloc = local_alloc; + session->free = local_free; + session->realloc = local_realloc; + session->send = _libssh2_send; + session->recv = _libssh2_recv; + session->abstract = abstract; + session->api_timeout = 0; /* timeout-free API by default */ + session->api_block_mode = 1; /* blocking API by default */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "New session resource allocated"); + _libssh2_init_if_needed(); + } + return session; +} + +/* + * libssh2_session_callback_set + * + * Set (or reset) a callback function + * Returns the prior address + * + * ALERT: this function relies on that we can typecast function pointers + * to void pointers, which isn't allowed in ISO C! + */ +#ifdef _MSC_VER +#pragma warning(push) +/* nonstandard extension, function/data pointer conversion in expression */ +#pragma warning(disable:4152) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif +LIBSSH2_API void * +libssh2_session_callback_set(LIBSSH2_SESSION * session, + int cbtype, void *callback) +{ + void *oldcb; + + switch(cbtype) { + case LIBSSH2_CALLBACK_IGNORE: + oldcb = session->ssh_msg_ignore; + session->ssh_msg_ignore = callback; + return oldcb; + + case LIBSSH2_CALLBACK_DEBUG: + oldcb = session->ssh_msg_debug; + session->ssh_msg_debug = callback; + return oldcb; + + case LIBSSH2_CALLBACK_DISCONNECT: + oldcb = session->ssh_msg_disconnect; + session->ssh_msg_disconnect = callback; + return oldcb; + + case LIBSSH2_CALLBACK_MACERROR: + oldcb = session->macerror; + session->macerror = callback; + return oldcb; + + case LIBSSH2_CALLBACK_X11: + oldcb = session->x11; + session->x11 = callback; + return oldcb; + + case LIBSSH2_CALLBACK_SEND: + oldcb = session->send; + session->send = callback; + return oldcb; + + case LIBSSH2_CALLBACK_RECV: + oldcb = session->recv; + session->recv = callback; + return oldcb; + } + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Setting Callback %d", + cbtype); + + return NULL; +} +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +/* + * _libssh2_wait_socket() + * + * Utility function that waits for action on the socket. Returns 0 when ready + * to run again or error on timeout. + */ +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time) +{ + int rc; + int seconds_to_next; + int dir; + int has_timeout; + long ms_to_next = 0; + long elapsed_ms; + + /* since libssh2 often sets EAGAIN internally before this function is + called, we can decrease some amount of confusion in user programs by + resetting the error code in this function to reduce the risk of EAGAIN + being stored as error when a blocking function has returned */ + session->err_code = LIBSSH2_ERROR_NONE; + + rc = libssh2_keepalive_send(session, &seconds_to_next); + if(rc) + return rc; + + ms_to_next = seconds_to_next * 1000; + + /* figure out what to wait for */ + dir = libssh2_session_block_directions(session); + + if(!dir) { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Nothing to wait for in wait_socket"); + /* To avoid that we hang below just because there's nothing set to + wait for, we timeout on 1 second to also avoid busy-looping + during this condition */ + ms_to_next = 1000; + } + + if(session->api_timeout > 0 && + (seconds_to_next == 0 || + ms_to_next > session->api_timeout)) { + time_t now = time(NULL); + elapsed_ms = (long)(1000*difftime(now, start_time)); + if(elapsed_ms > session->api_timeout) { + return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "API timeout expired"); + } + ms_to_next = (session->api_timeout - elapsed_ms); + has_timeout = 1; + } + else if(ms_to_next > 0) { + has_timeout = 1; + } + else + has_timeout = 0; + +#ifdef HAVE_POLL + { + struct pollfd sockets[1]; + + sockets[0].fd = session->socket_fd; + sockets[0].events = 0; + sockets[0].revents = 0; + + if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) + sockets[0].events |= POLLIN; + + if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) + sockets[0].events |= POLLOUT; + + rc = poll(sockets, 1, has_timeout?ms_to_next: -1); + } +#else + { + fd_set rfd; + fd_set wfd; + fd_set *writefd = NULL; + fd_set *readfd = NULL; + struct timeval tv; + + tv.tv_sec = ms_to_next / 1000; + tv.tv_usec = (ms_to_next - tv.tv_sec*1000) * 1000; + + if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) { + FD_ZERO(&rfd); + FD_SET(session->socket_fd, &rfd); + readfd = &rfd; + } + + if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) { + FD_ZERO(&wfd); + FD_SET(session->socket_fd, &wfd); + writefd = &wfd; + } + + rc = select(session->socket_fd + 1, readfd, writefd, NULL, + has_timeout ? &tv : NULL); + } +#endif + if(rc == 0) { + return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "Timed out waiting on socket"); + } + if(rc < 0) { + return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "Error waiting on socket"); + } + + return 0; /* ready to try again */ +} + +static int +session_startup(LIBSSH2_SESSION *session, libssh2_socket_t sock) +{ + int rc; + + if(session->startup_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "session_startup for socket %d", sock); + if(LIBSSH2_INVALID_SOCKET == sock) { + /* Did we forget something? */ + return _libssh2_error(session, LIBSSH2_ERROR_BAD_SOCKET, + "Bad socket provided"); + } + session->socket_fd = sock; + + session->socket_prev_blockstate = + !get_socket_nonblocking(session->socket_fd); + + if(session->socket_prev_blockstate) { + /* If in blocking state change to non-blocking */ + rc = session_nonblock(session->socket_fd, 1); + if(rc) { + return _libssh2_error(session, rc, + "Failed changing socket's " + "blocking state to non-blocking"); + } + } + + session->startup_state = libssh2_NB_state_created; + } + + if(session->startup_state == libssh2_NB_state_created) { + rc = banner_send(session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if(rc) { + return _libssh2_error(session, rc, + "Failed sending banner"); + } + session->startup_state = libssh2_NB_state_sent; + session->banner_TxRx_state = libssh2_NB_state_idle; + } + + if(session->startup_state == libssh2_NB_state_sent) { + do { + rc = banner_receive(session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if(rc) + return _libssh2_error(session, rc, + "Failed getting banner"); + } while(strncmp("SSH-", (char *)session->remote.banner, 4)); + + session->startup_state = libssh2_NB_state_sent1; + } + + if(session->startup_state == libssh2_NB_state_sent1) { + rc = _libssh2_kex_exchange(session, 0, &session->startup_key_state); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if(rc) + return _libssh2_error(session, rc, + "Unable to exchange encryption keys"); + + session->startup_state = libssh2_NB_state_sent2; + } + + if(session->startup_state == libssh2_NB_state_sent2) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Requesting userauth service"); + + /* Request the userauth service */ + session->startup_service[0] = SSH_MSG_SERVICE_REQUEST; + _libssh2_htonu32(session->startup_service + 1, + sizeof("ssh-userauth") - 1); + memcpy(session->startup_service + 5, "ssh-userauth", + sizeof("ssh-userauth") - 1); + + session->startup_state = libssh2_NB_state_sent3; + } + + if(session->startup_state == libssh2_NB_state_sent3) { + rc = _libssh2_transport_send(session, session->startup_service, + sizeof("ssh-userauth") + 5 - 1, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if(rc) { + return _libssh2_error(session, rc, + "Unable to ask for ssh-userauth service"); + } + + session->startup_state = libssh2_NB_state_sent4; + } + + if(session->startup_state == libssh2_NB_state_sent4) { + rc = _libssh2_packet_require(session, SSH_MSG_SERVICE_ACCEPT, + &session->startup_data, + &session->startup_data_len, 0, NULL, 0, + &session->startup_req_state); + if(rc) + return _libssh2_error(session, rc, + "Failed to get response to " + "ssh-userauth request"); + + if(session->startup_data_len < 5) { + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet length"); + } + + session->startup_service_length = + _libssh2_ntohu32(session->startup_data + 1); + + + if((session->startup_service_length != (sizeof("ssh-userauth") - 1)) + || strncmp("ssh-userauth", (char *) session->startup_data + 5, + session->startup_service_length)) { + LIBSSH2_FREE(session, session->startup_data); + session->startup_data = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid response received from server"); + } + LIBSSH2_FREE(session, session->startup_data); + session->startup_data = NULL; + + session->startup_state = libssh2_NB_state_idle; + + return 0; + } + + /* just for safety return some error */ + return LIBSSH2_ERROR_INVAL; +} + +/* + * libssh2_session_handshake() + * + * session: LIBSSH2_SESSION struct allocated and owned by the calling program + * sock: *must* be populated with an opened and connected socket. + * + * Returns: 0 on success, or non-zero on failure + */ +LIBSSH2_API int +libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock) +{ + int rc; + + BLOCK_ADJUST(rc, session, session_startup(session, sock) ); + + return rc; +} + +/* + * libssh2_session_startup() + * + * DEPRECATED. Use libssh2_session_handshake() instead! This function is not + * portable enough. + * + * session: LIBSSH2_SESSION struct allocated and owned by the calling program + * sock: *must* be populated with an opened and connected socket. + * + * Returns: 0 on success, or non-zero on failure + */ +LIBSSH2_API int +libssh2_session_startup(LIBSSH2_SESSION *session, int sock) +{ + return libssh2_session_handshake(session, (libssh2_socket_t) sock); +} + +/* + * libssh2_session_free + * + * Frees the memory allocated to the session + * Also closes and frees any channels attached to this session + */ +static int +session_free(LIBSSH2_SESSION *session) +{ + int rc; + LIBSSH2_PACKET *pkg; + LIBSSH2_CHANNEL *ch; + LIBSSH2_LISTENER *l; + int packets_left = 0; + + if(session->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Freeing session resource", + session->remote.banner); + + session->free_state = libssh2_NB_state_created; + } + + if(session->free_state == libssh2_NB_state_created) { + while((ch = _libssh2_list_first(&session->channels)) != NULL) { + + rc = _libssh2_channel_free(ch); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->free_state = libssh2_NB_state_sent; + } + + if(session->free_state == libssh2_NB_state_sent) { + while((l = _libssh2_list_first(&session->listeners)) != NULL) { + rc = _libssh2_channel_forward_cancel(l); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->free_state = libssh2_NB_state_sent1; + } + + if(session->state & LIBSSH2_STATE_NEWKEYS) { + /* hostkey */ + if(session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, &session->server_hostkey_abstract); + } + + /* Client to Server */ + /* crypt */ + if(session->local.crypt && session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + /* comp */ + if(session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + /* mac */ + if(session->local.mac && session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + /* Server to Client */ + /* crypt */ + if(session->remote.crypt && session->remote.crypt->dtor) { + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + /* comp */ + if(session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + /* mac */ + if(session->remote.mac && session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + /* session_id */ + if(session->session_id) { + LIBSSH2_FREE(session, session->session_id); + } + } + + /* Free banner(s) */ + if(session->remote.banner) { + LIBSSH2_FREE(session, session->remote.banner); + } + if(session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + } + + /* Free preference(s) */ + if(session->kex_prefs) { + LIBSSH2_FREE(session, session->kex_prefs); + } + if(session->hostkey_prefs) { + LIBSSH2_FREE(session, session->hostkey_prefs); + } + + if(session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + if(session->local.crypt_prefs) { + LIBSSH2_FREE(session, session->local.crypt_prefs); + } + if(session->local.mac_prefs) { + LIBSSH2_FREE(session, session->local.mac_prefs); + } + if(session->local.comp_prefs) { + LIBSSH2_FREE(session, session->local.comp_prefs); + } + if(session->local.lang_prefs) { + LIBSSH2_FREE(session, session->local.lang_prefs); + } + + if(session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + } + if(session->remote.crypt_prefs) { + LIBSSH2_FREE(session, session->remote.crypt_prefs); + } + if(session->remote.mac_prefs) { + LIBSSH2_FREE(session, session->remote.mac_prefs); + } + if(session->remote.comp_prefs) { + LIBSSH2_FREE(session, session->remote.comp_prefs); + } + if(session->remote.lang_prefs) { + LIBSSH2_FREE(session, session->remote.lang_prefs); + } + if(session->server_sign_algorithms) { + LIBSSH2_FREE(session, session->server_sign_algorithms); + } + if(session->sign_algo_prefs) { + LIBSSH2_FREE(session, session->sign_algo_prefs); + } + + /* + * Make sure all memory used in the state variables are free + */ + if(session->kexinit_data) { + LIBSSH2_FREE(session, session->kexinit_data); + } + if(session->startup_data) { + LIBSSH2_FREE(session, session->startup_data); + } + if(session->userauth_list_data) { + LIBSSH2_FREE(session, session->userauth_list_data); + } + if(session->userauth_banner) { + LIBSSH2_FREE(session, session->userauth_banner); + } + if(session->userauth_pswd_data) { + LIBSSH2_FREE(session, session->userauth_pswd_data); + } + if(session->userauth_pswd_newpw) { + LIBSSH2_FREE(session, session->userauth_pswd_newpw); + } + if(session->userauth_host_packet) { + LIBSSH2_FREE(session, session->userauth_host_packet); + } + if(session->userauth_host_method) { + LIBSSH2_FREE(session, session->userauth_host_method); + } + if(session->userauth_host_data) { + LIBSSH2_FREE(session, session->userauth_host_data); + } + if(session->userauth_pblc_data) { + LIBSSH2_FREE(session, session->userauth_pblc_data); + } + if(session->userauth_pblc_packet) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + } + if(session->userauth_pblc_method) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + } + if(session->userauth_kybd_data) { + LIBSSH2_FREE(session, session->userauth_kybd_data); + } + if(session->userauth_kybd_packet) { + LIBSSH2_FREE(session, session->userauth_kybd_packet); + } + if(session->userauth_kybd_auth_instruction) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction); + } + if(session->open_packet) { + LIBSSH2_FREE(session, session->open_packet); + } + if(session->open_data) { + LIBSSH2_FREE(session, session->open_data); + } + if(session->direct_message) { + LIBSSH2_FREE(session, session->direct_message); + } + if(session->fwdLstn_packet) { + LIBSSH2_FREE(session, session->fwdLstn_packet); + } + if(session->pkeyInit_data) { + LIBSSH2_FREE(session, session->pkeyInit_data); + } + if(session->scpRecv_command) { + LIBSSH2_FREE(session, session->scpRecv_command); + } + if(session->scpSend_command) { + LIBSSH2_FREE(session, session->scpSend_command); + } + if(session->sftpInit_sftp) { + LIBSSH2_FREE(session, session->sftpInit_sftp); + } + + /* Free payload buffer */ + if(session->packet.total_num) { + LIBSSH2_FREE(session, session->packet.payload); + } + + /* Cleanup all remaining packets */ + while((pkg = _libssh2_list_first(&session->packets)) != NULL) { + packets_left++; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "packet left with id %d", pkg->data[0]); + /* unlink the node */ + _libssh2_list_remove(&pkg->node); + + /* free */ + LIBSSH2_FREE(session, pkg->data); + LIBSSH2_FREE(session, pkg); + } + (void)packets_left; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Extra packets left %d", packets_left); + + if(session->socket_prev_blockstate) { + /* if the socket was previously blocking, put it back so */ + rc = session_nonblock(session->socket_fd, 0); + if(rc) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unable to reset socket's blocking state"); + } + } + + if(session->server_hostkey) { + LIBSSH2_FREE(session, session->server_hostkey); + } + + /* error string */ + if(session->err_msg && + ((session->err_flags & LIBSSH2_ERR_FLAG_DUP) != 0)) { + LIBSSH2_FREE(session, (char *)session->err_msg); + } + + LIBSSH2_FREE(session, session); + + return 0; +} + +/* + * libssh2_session_free + * + * Frees the memory allocated to the session + * Also closes and frees any channels attached to this session + */ +LIBSSH2_API int +libssh2_session_free(LIBSSH2_SESSION * session) +{ + int rc; + + BLOCK_ADJUST(rc, session, session_free(session) ); + + return rc; +} + +/* + * libssh2_session_disconnect_ex + */ +static int +session_disconnect(LIBSSH2_SESSION *session, int reason, + const char *description, + const char *lang) +{ + unsigned char *s; + unsigned long descr_len = 0, lang_len = 0; + int rc; + + if(session->disconnect_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Disconnecting: reason=%d, desc=%s, lang=%s", reason, + description, lang); + if(description) + descr_len = strlen(description); + + if(lang) + lang_len = strlen(lang); + + if(descr_len > 256) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "too long description"); + + /* 13 = packet_type(1) + reason code(4) + descr_len(4) + lang_len(4) */ + session->disconnect_data_len = descr_len + lang_len + 13; + + s = session->disconnect_data; + + *(s++) = SSH_MSG_DISCONNECT; + _libssh2_store_u32(&s, reason); + _libssh2_store_str(&s, description, descr_len); + /* store length only, lang is sent separately */ + _libssh2_store_u32(&s, lang_len); + + session->disconnect_state = libssh2_NB_state_created; + } + + rc = _libssh2_transport_send(session, session->disconnect_data, + session->disconnect_data_len, + (unsigned char *)lang, lang_len); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + session->disconnect_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_session_disconnect_ex + */ +LIBSSH2_API int +libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, + const char *desc, const char *lang) +{ + int rc; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + BLOCK_ADJUST(rc, session, + session_disconnect(session, reason, desc, lang)); + + return rc; +} + +/* libssh2_session_methods + * + * Return the currently active methods for method_type + * + * NOTE: Currently lang_cs and lang_sc are ALWAYS set to empty string + * regardless of actual negotiation Strings should NOT be freed + */ +LIBSSH2_API const char * +libssh2_session_methods(LIBSSH2_SESSION * session, int method_type) +{ + /* All methods have char *name as their first element */ + const LIBSSH2_KEX_METHOD *method = NULL; + + switch(method_type) { + case LIBSSH2_METHOD_KEX: + method = session->kex; + break; + + case LIBSSH2_METHOD_HOSTKEY: + method = (LIBSSH2_KEX_METHOD *) session->hostkey; + break; + + case LIBSSH2_METHOD_CRYPT_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.crypt; + break; + + case LIBSSH2_METHOD_CRYPT_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.crypt; + break; + + case LIBSSH2_METHOD_MAC_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.mac; + break; + + case LIBSSH2_METHOD_MAC_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.mac; + break; + + case LIBSSH2_METHOD_COMP_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.comp; + break; + + case LIBSSH2_METHOD_COMP_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.comp; + break; + + case LIBSSH2_METHOD_LANG_CS: + return ""; + + case LIBSSH2_METHOD_LANG_SC: + return ""; + + default: + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "Invalid parameter specified for method_type"); + return NULL; + } + + if(!method) { + _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No method negotiated"); + return NULL; + } + + return method->name; +} + +/* libssh2_session_abstract + * Retrieve a pointer to the abstract property + */ +LIBSSH2_API void ** +libssh2_session_abstract(LIBSSH2_SESSION * session) +{ + return &session->abstract; +} + +/* libssh2_session_last_error + * + * Returns error code and populates an error string into errmsg If want_buf is + * non-zero then the string placed into errmsg must be freed by the calling + * program. Otherwise it is assumed to be owned by libssh2 + */ +LIBSSH2_API int +libssh2_session_last_error(LIBSSH2_SESSION * session, char **errmsg, + int *errmsg_len, int want_buf) +{ + size_t msglen = 0; + + /* No error to report */ + if(!session->err_code) { + if(errmsg) { + if(want_buf) { + *errmsg = LIBSSH2_ALLOC(session, 1); + if(*errmsg) { + **errmsg = 0; + } + } + else { + *errmsg = (char *) ""; + } + } + if(errmsg_len) { + *errmsg_len = 0; + } + return 0; + } + + if(errmsg) { + const char *error = session->err_msg ? session->err_msg : ""; + + msglen = strlen(error); + + if(want_buf) { + /* Make a copy so the calling program can own it */ + *errmsg = LIBSSH2_ALLOC(session, msglen + 1); + if(*errmsg) { + memcpy(*errmsg, error, msglen); + (*errmsg)[msglen] = 0; + } + } + else + *errmsg = (char *)error; + } + + if(errmsg_len) { + *errmsg_len = msglen; + } + + return session->err_code; +} + +/* libssh2_session_last_errno + * + * Returns error code + */ +LIBSSH2_API int +libssh2_session_last_errno(LIBSSH2_SESSION * session) +{ + return session->err_code; +} + +/* libssh2_session_set_last_error + * + * Sets the internal error code for the session. + * + * This function is available specifically to be used by high level + * language wrappers (i.e. Python or Perl) that may extend the library + * features while still relying on its error reporting mechanism. + */ +LIBSSH2_API int +libssh2_session_set_last_error(LIBSSH2_SESSION* session, + int errcode, + const char *errmsg) +{ + return _libssh2_error_flags(session, errcode, errmsg, + LIBSSH2_ERR_FLAG_DUP); +} + +/* Libssh2_session_flag + * + * Set/Get session flags + * + * Return error code. + */ +LIBSSH2_API int +libssh2_session_flag(LIBSSH2_SESSION * session, int flag, int value) +{ + switch(flag) { + case LIBSSH2_FLAG_SIGPIPE: + session->flag.sigpipe = value; + break; + case LIBSSH2_FLAG_COMPRESS: + session->flag.compress = value; + break; + default: + /* unknown flag */ + return LIBSSH2_ERROR_INVAL; + } + + return LIBSSH2_ERROR_NONE; +} + +/* _libssh2_session_set_blocking + * + * Set a session's blocking mode on or off, return the previous status when + * this function is called. Note this function does not alter the state of the + * actual socket involved. + */ +int +_libssh2_session_set_blocking(LIBSSH2_SESSION *session, int blocking) +{ + int bl = session->api_block_mode; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Setting blocking mode %s", blocking?"ON":"OFF"); + session->api_block_mode = blocking; + + return bl; +} + +/* libssh2_session_set_blocking + * + * Set a channel's blocking mode on or off, similar to a socket's + * fcntl(fd, F_SETFL, O_NONBLOCK); type command + */ +LIBSSH2_API void +libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking) +{ + (void) _libssh2_session_set_blocking(session, blocking); +} + +/* libssh2_session_get_blocking + * + * Returns a session's blocking mode on or off + */ +LIBSSH2_API int +libssh2_session_get_blocking(LIBSSH2_SESSION * session) +{ + return session->api_block_mode; +} + + +/* libssh2_session_set_timeout + * + * Set a session's timeout (in msec) for blocking mode, + * or 0 to disable timeouts. + */ +LIBSSH2_API void +libssh2_session_set_timeout(LIBSSH2_SESSION * session, long timeout) +{ + session->api_timeout = timeout; +} + +/* libssh2_session_get_timeout + * + * Returns a session's timeout, or 0 if disabled + */ +LIBSSH2_API long +libssh2_session_get_timeout(LIBSSH2_SESSION * session) +{ + return session->api_timeout; +} + +/* + * libssh2_poll_channel_read + * + * Returns 0 if no data is waiting on channel, + * non-0 if data is available + */ +LIBSSH2_API int +libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended) +{ + LIBSSH2_SESSION *session; + LIBSSH2_PACKET *packet; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + session = channel->session; + packet = _libssh2_list_first(&session->packets); + + while(packet) { + if(packet->data_len < 5) { + return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Packet too small"); + } + + if(channel->local.id == _libssh2_ntohu32(packet->data + 1)) { + if(extended == 1 && + (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA + || packet->data[0] == SSH_MSG_CHANNEL_DATA)) { + return 1; + } + else if(extended == 0 && + packet->data[0] == SSH_MSG_CHANNEL_DATA) { + return 1; + } + /* else - no data of any type is ready to be read */ + } + packet = _libssh2_list_next(&packet->node); + } + + return 0; +} + +/* + * poll_channel_write + * + * Returns 0 if writing to channel would block, + * non-0 if data can be written without blocking + */ +static inline int +poll_channel_write(LIBSSH2_CHANNEL * channel) +{ + return channel->local.window_size ? 1 : 0; +} + +/* poll_listener_queued + * + * Returns 0 if no connections are waiting to be accepted + * non-0 if one or more connections are available + */ +static inline int +poll_listener_queued(LIBSSH2_LISTENER * listener) +{ + return _libssh2_list_first(&listener->queue) ? 1 : 0; +} + +/* + * libssh2_poll + * + * Poll sockets, channels, and listeners for activity + */ +LIBSSH2_API int +libssh2_poll(LIBSSH2_POLLFD * fds, unsigned int nfds, long timeout) +{ + long timeout_remaining; + unsigned int i, active_fds; +#ifdef HAVE_POLL + LIBSSH2_SESSION *session = NULL; +#ifdef HAVE_ALLOCA + struct pollfd *sockets = alloca(sizeof(struct pollfd) * nfds); +#else + struct pollfd sockets[256]; + + if(nfds > 256) + /* systems without alloca use a fixed-size array, this can be fixed if + we really want to, at least if the compiler is a C99 capable one */ + return -1; +#endif + /* Setup sockets for polling */ + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch(fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + sockets[i].fd = fds[i].fd.socket; + sockets[i].events = fds[i].events; + sockets[i].revents = 0; + break; + + case LIBSSH2_POLLFD_CHANNEL: + sockets[i].fd = fds[i].fd.channel->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if(!session) + session = fds[i].fd.channel->session; + break; + + case LIBSSH2_POLLFD_LISTENER: + sockets[i].fd = fds[i].fd.listener->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if(!session) + session = fds[i].fd.listener->session; + break; + + default: + if(session) + _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, + "Invalid descriptor passed to libssh2_poll()"); + return -1; + } + } +#elif defined(HAVE_SELECT) + LIBSSH2_SESSION *session = NULL; + libssh2_socket_t maxfd = 0; + fd_set rfds, wfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch(fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if(fds[i].events & LIBSSH2_POLLFD_POLLIN) { + FD_SET(fds[i].fd.socket, &rfds); + if(fds[i].fd.socket > maxfd) + maxfd = fds[i].fd.socket; + } + if(fds[i].events & LIBSSH2_POLLFD_POLLOUT) { + FD_SET(fds[i].fd.socket, &wfds); + if(fds[i].fd.socket > maxfd) + maxfd = fds[i].fd.socket; + } + break; + + case LIBSSH2_POLLFD_CHANNEL: + FD_SET(fds[i].fd.channel->session->socket_fd, &rfds); + if(fds[i].fd.channel->session->socket_fd > maxfd) + maxfd = fds[i].fd.channel->session->socket_fd; + if(!session) + session = fds[i].fd.channel->session; + break; + + case LIBSSH2_POLLFD_LISTENER: + FD_SET(fds[i].fd.listener->session->socket_fd, &rfds); + if(fds[i].fd.listener->session->socket_fd > maxfd) + maxfd = fds[i].fd.listener->session->socket_fd; + if(!session) + session = fds[i].fd.listener->session; + break; + + default: + if(session) + _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, + "Invalid descriptor passed to libssh2_poll()"); + return -1; + } + } +#else + /* No select() or poll() + * no sockets structure to setup + */ + + timeout = 0; +#endif /* HAVE_POLL or HAVE_SELECT */ + + timeout_remaining = timeout; + do { +#if defined(HAVE_POLL) || defined(HAVE_SELECT) + int sysret; +#endif + + active_fds = 0; + + for(i = 0; i < nfds; i++) { + if(fds[i].events != fds[i].revents) { + switch(fds[i].type) { + case LIBSSH2_POLLFD_CHANNEL: + if((fds[i].events & LIBSSH2_POLLFD_POLLIN) && + /* Want to be ready for read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { + /* Not yet known to be ready for read */ + fds[i].revents |= + libssh2_poll_channel_read(fds[i].fd.channel, + 0) ? + LIBSSH2_POLLFD_POLLIN : 0; + } + if((fds[i].events & LIBSSH2_POLLFD_POLLEXT) && + /* Want to be ready for extended read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLEXT) == 0)) { + /* Not yet known to be ready for extended read */ + fds[i].revents |= + libssh2_poll_channel_read(fds[i].fd.channel, + 1) ? + LIBSSH2_POLLFD_POLLEXT : 0; + } + if((fds[i].events & LIBSSH2_POLLFD_POLLOUT) && + /* Want to be ready for write */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLOUT) == 0)) { + /* Not yet known to be ready for write */ + fds[i].revents |= + poll_channel_write(fds[i].fd. channel) ? + LIBSSH2_POLLFD_POLLOUT : 0; + } + if(fds[i].fd.channel->remote.close + || fds[i].fd.channel->local.close) { + fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED; + } + if(fds[i].fd.channel->session->socket_state == + LIBSSH2_SOCKET_DISCONNECTED) { + fds[i].revents |= + LIBSSH2_POLLFD_CHANNEL_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + break; + + case LIBSSH2_POLLFD_LISTENER: + if((fds[i].events & LIBSSH2_POLLFD_POLLIN) && + /* Want a connection */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { + /* No connections known of yet */ + fds[i].revents |= + poll_listener_queued(fds[i].fd. listener) ? + LIBSSH2_POLLFD_POLLIN : 0; + } + if(fds[i].fd.listener->session->socket_state == + LIBSSH2_SOCKET_DISCONNECTED) { + fds[i].revents |= + LIBSSH2_POLLFD_LISTENER_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + break; + } + } + if(fds[i].revents) { + active_fds++; + } + } + + if(active_fds) { + /* Don't block on the sockets if we have channels/listeners which + are ready */ + timeout_remaining = 0; + } +#ifdef HAVE_POLL + +#ifdef HAVE_LIBSSH2_GETTIMEOFDAY + { + struct timeval tv_begin, tv_end; + + _libssh2_gettimeofday((struct timeval *) &tv_begin, NULL); + sysret = poll(sockets, nfds, timeout_remaining); + _libssh2_gettimeofday((struct timeval *) &tv_end, NULL); + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; + } +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = poll(sockets, nfds, timeout_remaining); + timeout_remaining = 0; +#endif /* HAVE_GETTIMEOFDAY */ + + if(sysret > 0) { + for(i = 0; i < nfds; i++) { + switch(fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + fds[i].revents = sockets[i].revents; + sockets[i].revents = 0; /* In case we loop again, be + nice */ + if(fds[i].revents) { + active_fds++; + } + break; + case LIBSSH2_POLLFD_CHANNEL: + if(sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while(_libssh2_transport_read(fds[i].fd. + channel->session) + > 0); + } + if(sockets[i].revents & POLLHUP) { + fds[i].revents |= + LIBSSH2_POLLFD_CHANNEL_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + sockets[i].revents = 0; + break; + case LIBSSH2_POLLFD_LISTENER: + if(sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while(_libssh2_transport_read(fds[i].fd. + listener->session) + > 0); + } + if(sockets[i].revents & POLLHUP) { + fds[i].revents |= + LIBSSH2_POLLFD_LISTENER_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + sockets[i].revents = 0; + break; + } + } + } +#elif defined(HAVE_SELECT) + tv.tv_sec = timeout_remaining / 1000; + tv.tv_usec = (timeout_remaining % 1000) * 1000; +#ifdef HAVE_LIBSSH2_GETTIMEOFDAY + { + struct timeval tv_begin, tv_end; + + _libssh2_gettimeofday((struct timeval *) &tv_begin, NULL); + sysret = select(maxfd + 1, &rfds, &wfds, NULL, &tv); + _libssh2_gettimeofday((struct timeval *) &tv_end, NULL); + + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; + } +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = select(maxfd + 1, &rfds, &wfds, NULL, &tv); + timeout_remaining = 0; +#endif + + if(sysret > 0) { + for(i = 0; i < nfds; i++) { + switch(fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if(FD_ISSET(fds[i].fd.socket, &rfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLIN; + } + if(FD_ISSET(fds[i].fd.socket, &wfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLOUT; + } + if(fds[i].revents) { + active_fds++; + } + break; + + case LIBSSH2_POLLFD_CHANNEL: + if(FD_ISSET(fds[i].fd.channel->session->socket_fd, + &rfds)) { + /* Spin session until no data available */ + while(_libssh2_transport_read(fds[i].fd. + channel->session) + > 0); + } + break; + + case LIBSSH2_POLLFD_LISTENER: + if(FD_ISSET + (fds[i].fd.listener->session->socket_fd, &rfds)) { + /* Spin session until no data available */ + while(_libssh2_transport_read(fds[i].fd. + listener->session) + > 0); + } + break; + } + } + } +#endif /* else no select() or poll() -- timeout (and by extension + * timeout_remaining) will be equal to 0 */ + } while((timeout_remaining > 0) && !active_fds); + + return active_fds; +} + +/* + * libssh2_session_block_directions + * + * Get blocked direction when a function returns LIBSSH2_ERROR_EAGAIN + * Returns LIBSSH2_SOCKET_BLOCK_INBOUND if recv() blocked + * or LIBSSH2_SOCKET_BLOCK_OUTBOUND if send() blocked + */ +LIBSSH2_API int +libssh2_session_block_directions(LIBSSH2_SESSION *session) +{ + return session->socket_block_directions; +} + +/* libssh2_session_banner_get + * Get the remote banner (server ID string) + */ + +LIBSSH2_API const char * +libssh2_session_banner_get(LIBSSH2_SESSION *session) +{ + /* to avoid a coredump when session is NULL */ + if(NULL == session) + return NULL; + + if(NULL == session->remote.banner) + return NULL; + + return (const char *) session->remote.banner; +} +#endif diff --git a/lib/libssh2/session.h b/lib/libssh2/session.h new file mode 100644 index 0000000..ff875b4 --- /dev/null +++ b/lib/libssh2/session.h @@ -0,0 +1,95 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_SESSION_H +#define __LIBSSH2_SESSION_H +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* Conveniance-macros to allow code like this; + + int rc = BLOCK_ADJUST(rc, session, session_startup(session, sock) ); + + int rc = BLOCK_ADJUST_ERRNO(ptr, session, session_startup(session, sock) ); + + The point of course being to make sure that while in non-blocking mode + these always return no matter what the return code is, but in blocking mode + it blocks if EAGAIN is the reason for the return from the underlying + function. + +*/ +#define BLOCK_ADJUST(rc, sess, x) \ + do { \ + time_t entry_time = time(NULL); \ + do { \ + rc = x; \ + /* the order of the check below is important to properly deal with \ + the case when the 'sess' is freed */ \ + if((rc != LIBSSH2_ERROR_EAGAIN) || !sess->api_block_mode) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) + +/* + * For functions that returns a pointer, we need to check if the API is + * non-blocking and return immediately. If the pointer is non-NULL we return + * immediately. If the API is blocking and we get a NULL we check the errno + * and *only* if that is EAGAIN we loop and wait for socket action. + */ +#define BLOCK_ADJUST_ERRNO(ptr, sess, x) \ + do { \ + time_t entry_time = time(NULL); \ + int rc; \ + do { \ + ptr = x; \ + if(!sess->api_block_mode || \ + (ptr != NULL) || \ + (libssh2_session_last_errno(sess) != LIBSSH2_ERROR_EAGAIN) ) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) + + +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t entry_time); + +/* this is the lib-internal set blocking function */ +int _libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking); + +#endif /* __LIBSSH2_SESSION_H */ +#endif diff --git a/lib/libssh2/sftp.h b/lib/libssh2/sftp.h new file mode 100644 index 0000000..49b57fc --- /dev/null +++ b/lib/libssh2/sftp.h @@ -0,0 +1,240 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_SFTP_H +#define __LIBSSH2_SFTP_H +/* + * Copyright (C) 2010 - 2012 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +/* + * MAX_SFTP_OUTGOING_SIZE MUST not be larger than 32500 or so. This is the + * amount of data sent in each FXP_WRITE packet + */ +#define MAX_SFTP_OUTGOING_SIZE 30000 + +/* MAX_SFTP_READ_SIZE is how much data is asked for at max in each FXP_READ + * packets. + */ +#define MAX_SFTP_READ_SIZE 30000 + +struct sftp_pipeline_chunk { + struct list_node node; + libssh2_uint64_t offset; /* READ: offset at which to start reading + WRITE: not used */ + size_t len; /* WRITE: size of the data to write + READ: how many bytes that was asked for */ + size_t sent; + ssize_t lefttosend; /* if 0, the entire packet has been sent off */ + uint32_t request_id; + unsigned char packet[1]; /* data */ +}; + +struct sftp_zombie_requests { + struct list_node node; + uint32_t request_id; +}; + +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +struct _LIBSSH2_SFTP_PACKET +{ + struct list_node node; /* linked list header */ + uint32_t request_id; + unsigned char *data; + size_t data_len; /* payload size */ +}; + +typedef struct _LIBSSH2_SFTP_PACKET LIBSSH2_SFTP_PACKET; + +#define SFTP_HANDLE_MAXLEN 256 /* according to spec! */ + +struct _LIBSSH2_SFTP_HANDLE +{ + struct list_node node; + + LIBSSH2_SFTP *sftp; + + char handle[SFTP_HANDLE_MAXLEN]; + size_t handle_len; + + enum { + LIBSSH2_SFTP_HANDLE_FILE, + LIBSSH2_SFTP_HANDLE_DIR + } handle_type; + + union _libssh2_sftp_handle_data + { + struct _libssh2_sftp_handle_file_data + { + libssh2_uint64_t offset; + libssh2_uint64_t offset_sent; + size_t acked; /* container for acked data that hasn't been + returned to caller yet, used for sftp_write */ + + /* 'data' is used by sftp_read() and is allocated data that has + been received already from the server but wasn't returned to + the caller yet. It is of size 'data_len' and 'data_left is the + number of bytes not yet returned, counted from the end of the + buffer. */ + unsigned char *data; + size_t data_len; + size_t data_left; + + char eof; /* we have read to the end */ + } file; + struct _libssh2_sftp_handle_dir_data + { + uint32_t names_left; + void *names_packet; + char *next_name; + size_t names_packet_len; + } dir; + } u; + + /* State variables used in libssh2_sftp_close_handle() */ + libssh2_nonblocking_states close_state; + uint32_t close_request_id; + unsigned char *close_packet; + + /* list of outstanding packets sent to server */ + struct list_head packet_list; + +}; + +struct _LIBSSH2_SFTP +{ + LIBSSH2_CHANNEL *channel; + + uint32_t request_id, version; + + struct list_head packets; + + /* List of FXP_READ responses to ignore because EOF already received. */ + struct list_head zombie_requests; + + /* a list of _LIBSSH2_SFTP_HANDLE structs */ + struct list_head sftp_handles; + + uint32_t last_errno; + + /* Holder for partial packet, use in libssh2_sftp_packet_read() */ + unsigned char partial_size[4]; /* buffer for size field */ + size_t partial_size_len; /* size field length */ + unsigned char *partial_packet; /* The data */ + uint32_t partial_len; /* Desired number of bytes */ + size_t partial_received; /* Bytes received so far */ + + /* Time that libssh2_sftp_packet_requirev() started reading */ + time_t requirev_start; + + /* State variables used in libssh2_sftp_open_ex() */ + libssh2_nonblocking_states open_state; + unsigned char *open_packet; + uint32_t open_packet_len; /* 32 bit on the wire */ + size_t open_packet_sent; + uint32_t open_request_id; + + /* State variable used in sftp_read() */ + libssh2_nonblocking_states read_state; + + /* State variable used in sftp_packet_read() */ + libssh2_nonblocking_states packet_state; + + /* State variable used in sftp_write() */ + libssh2_nonblocking_states write_state; + + /* State variables used in sftp_fsync() */ + libssh2_nonblocking_states fsync_state; + unsigned char *fsync_packet; + uint32_t fsync_request_id; + + /* State variables used in libssh2_sftp_readdir() */ + libssh2_nonblocking_states readdir_state; + unsigned char *readdir_packet; + uint32_t readdir_request_id; + + /* State variables used in libssh2_sftp_fstat_ex() */ + libssh2_nonblocking_states fstat_state; + unsigned char *fstat_packet; + uint32_t fstat_request_id; + + /* State variables used in libssh2_sftp_unlink_ex() */ + libssh2_nonblocking_states unlink_state; + unsigned char *unlink_packet; + uint32_t unlink_request_id; + + /* State variables used in libssh2_sftp_rename_ex() */ + libssh2_nonblocking_states rename_state; + unsigned char *rename_packet; + unsigned char *rename_s; + uint32_t rename_request_id; + + /* State variables used in libssh2_sftp_fstatvfs() */ + libssh2_nonblocking_states fstatvfs_state; + unsigned char *fstatvfs_packet; + uint32_t fstatvfs_request_id; + + /* State variables used in libssh2_sftp_statvfs() */ + libssh2_nonblocking_states statvfs_state; + unsigned char *statvfs_packet; + uint32_t statvfs_request_id; + + /* State variables used in libssh2_sftp_mkdir() */ + libssh2_nonblocking_states mkdir_state; + unsigned char *mkdir_packet; + uint32_t mkdir_request_id; + + /* State variables used in libssh2_sftp_rmdir() */ + libssh2_nonblocking_states rmdir_state; + unsigned char *rmdir_packet; + uint32_t rmdir_request_id; + + /* State variables used in libssh2_sftp_stat() */ + libssh2_nonblocking_states stat_state; + unsigned char *stat_packet; + uint32_t stat_request_id; + + /* State variables used in libssh2_sftp_symlink() */ + libssh2_nonblocking_states symlink_state; + unsigned char *symlink_packet; + uint32_t symlink_request_id; +}; + +#endif /* __LIBSSH2_SFTP_H */ +#endif diff --git a/lib/libssh2/transport.c b/lib/libssh2/transport.c new file mode 100644 index 0000000..722d3b8 --- /dev/null +++ b/lib/libssh2/transport.c @@ -0,0 +1,933 @@ +#if defined(ESP32) +/* Copyright (C) 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2009-2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file handles reading and writing to the SECSH transport layer. RFC4253. + */ + +#include "libssh2_priv.h" +#include +#include +#include +#ifdef LIBSSH2DEBUG +#include +#endif + +#include + +#include "transport.h" +#include "mac.h" + +#define MAX_BLOCKSIZE 32 /* MUST fit biggest crypto block size we use/get */ +#define MAX_MACSIZE 64 /* MUST fit biggest MAC length we support */ + +#ifdef LIBSSH2DEBUG +#define UNPRINTABLE_CHAR '.' +static void +debugdump(LIBSSH2_SESSION * session, + const char *desc, const unsigned char *ptr, size_t size) +{ + size_t i; + size_t c; + unsigned int width = 0x10; + char buffer[256]; /* Must be enough for width*4 + about 30 or so */ + size_t used; + static const char *hex_chars = "0123456789ABCDEF"; + + if(!(session->showmask & LIBSSH2_TRACE_TRANS)) { + /* not asked for, bail out */ + return; + } + + used = snprintf(buffer, sizeof(buffer), "=> %s (%d bytes)\n", + desc, (int) size); + if(session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, + buffer, used); + else + fprintf(stderr, "%s", buffer); + + for(i = 0; i < size; i += width) { + + used = snprintf(buffer, sizeof(buffer), "%04lx: ", (long)i); + + /* hex not disabled, show it */ + for(c = 0; c < width; c++) { + if(i + c < size) { + buffer[used++] = hex_chars[(ptr[i + c] >> 4) & 0xF]; + buffer[used++] = hex_chars[ptr[i + c] & 0xF]; + } + else { + buffer[used++] = ' '; + buffer[used++] = ' '; + } + + buffer[used++] = ' '; + if((width/2) - 1 == c) + buffer[used++] = ' '; + } + + buffer[used++] = ':'; + buffer[used++] = ' '; + + for(c = 0; (c < width) && (i + c < size); c++) { + buffer[used++] = isprint(ptr[i + c]) ? + ptr[i + c] : UNPRINTABLE_CHAR; + } + buffer[used++] = '\n'; + buffer[used] = 0; + + if(session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, + buffer, used); + else + fprintf(stderr, "%s", buffer); + } +} +#else +#define debugdump(a,x,y,z) do {} while(0) +#endif + + +/* decrypt() decrypts 'len' bytes from 'source' to 'dest' in units of + * blocksize. + * + * returns 0 on success and negative on failure + */ + +static int +decrypt(LIBSSH2_SESSION * session, unsigned char *source, + unsigned char *dest, int len) +{ + struct transportpacket *p = &session->packet; + int blocksize = session->remote.crypt->blocksize; + + /* if we get called with a len that isn't an even number of blocksizes + we risk losing those extra bytes */ + assert((len % blocksize) == 0); + + while(len >= blocksize) { + if(session->remote.crypt->crypt(session, source, blocksize, + &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, p->payload); + return LIBSSH2_ERROR_DECRYPT; + } + + /* if the crypt() function would write to a given address it + wouldn't have to memcpy() and we could avoid this memcpy() + too */ + memcpy(dest, source, blocksize); + + len -= blocksize; /* less bytes left */ + dest += blocksize; /* advance write pointer */ + source += blocksize; /* advance read pointer */ + } + return LIBSSH2_ERROR_NONE; /* all is fine */ +} + +/* + * fullpacket() gets called when a full packet has been received and properly + * collected. + */ +static int +fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) +{ + unsigned char macbuf[MAX_MACSIZE]; + struct transportpacket *p = &session->packet; + int rc; + int compressed; + + if(session->fullpacket_state == libssh2_NB_state_idle) { + session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED; + session->fullpacket_payload_len = p->packet_length - 1; + + if(encrypted) { + + /* Calculate MAC hash */ + session->remote.mac->hash(session, macbuf, /* store hash here */ + session->remote.seqno, + p->init, 5, + p->payload, + session->fullpacket_payload_len, + &session->remote.mac_abstract); + + /* Compare the calculated hash with the MAC we just read from + * the network. The read one is at the very end of the payload + * buffer. Note that 'payload_len' here is the packet_length + * field which includes the padding but not the MAC. + */ + if(memcmp(macbuf, p->payload + session->fullpacket_payload_len, + session->remote.mac->mac_len)) { + session->fullpacket_macstate = LIBSSH2_MAC_INVALID; + } + } + + session->remote.seqno++; + + /* ignore the padding */ + session->fullpacket_payload_len -= p->padding_length; + + /* Check for and deal with decompression */ + compressed = + session->local.comp != NULL && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if(compressed && session->remote.comp_abstract) { + /* + * The buffer for the decompression (remote.comp_abstract) is + * initialised in time when it is needed so as long it is NULL we + * cannot decompress. + */ + + unsigned char *data; + size_t data_len; + rc = session->remote.comp->decomp(session, + &data, &data_len, + LIBSSH2_PACKET_MAXDECOMP, + p->payload, + session->fullpacket_payload_len, + &session->remote.comp_abstract); + LIBSSH2_FREE(session, p->payload); + if(rc) + return rc; + + p->payload = data; + session->fullpacket_payload_len = data_len; + } + + session->fullpacket_packet_type = p->payload[0]; + + debugdump(session, "libssh2_transport_read() plain", + p->payload, session->fullpacket_payload_len); + + session->fullpacket_state = libssh2_NB_state_created; + } + + if(session->fullpacket_state == libssh2_NB_state_created) { + rc = _libssh2_packet_add(session, p->payload, + session->fullpacket_payload_len, + session->fullpacket_macstate); + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + if(rc) { + session->fullpacket_state = libssh2_NB_state_idle; + return rc; + } + } + + session->fullpacket_state = libssh2_NB_state_idle; + + return session->fullpacket_packet_type; +} + + +/* + * _libssh2_transport_read + * + * Collect a packet into the input queue. + * + * Returns packet type added to input queue (0 if nothing added), or a + * negative error number. + */ + +/* + * This function reads the binary stream as specified in chapter 6 of RFC4253 + * "The Secure Shell (SSH) Transport Layer Protocol" + * + * DOES NOT call _libssh2_error() for ANY error case. + */ +int _libssh2_transport_read(LIBSSH2_SESSION * session) +{ + int rc; + struct transportpacket *p = &session->packet; + int remainpack; /* how much there is left to add to the current payload + package */ + int remainbuf; /* how much data there is remaining in the buffer to deal + with before we should read more from the network */ + int numbytes; /* how much data to deal with from the buffer on this + iteration through the loop */ + int numdecrypt; /* number of bytes to decrypt this iteration */ + unsigned char block[MAX_BLOCKSIZE]; /* working block buffer */ + int blocksize; /* minimum number of bytes we need before we can + use them */ + int encrypted = 1; /* whether the packet is encrypted or not */ + + /* default clear the bit */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND; + + /* + * All channels, systems, subsystems, etc eventually make it down here + * when looking for more incoming data. If a key exchange is going on + * (LIBSSH2_STATE_EXCHANGING_KEYS bit is set) then the remote end will + * ONLY send key exchange related traffic. In non-blocking mode, there is + * a chance to break out of the kex_exchange function with an EAGAIN + * status, and never come back to it. If LIBSSH2_STATE_EXCHANGING_KEYS is + * active, then we must redirect to the key exchange. However, if + * kex_exchange is active (as in it is the one that calls this execution + * of packet_read, then don't redirect, as that would be an infinite loop! + */ + + if(session->state & LIBSSH2_STATE_EXCHANGING_KEYS && + !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { + + /* Whoever wants a packet won't get anything until the key re-exchange + * is done! + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_read"); + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if(rc) + return rc; + } + + /* + * =============================== NOTE =============================== + * I know this is very ugly and not a really good use of "goto", but + * this case statement would be even uglier to do it any other way + */ + if(session->readPack_state == libssh2_NB_state_jump1) { + session->readPack_state = libssh2_NB_state_idle; + encrypted = session->readPack_encrypted; + goto libssh2_transport_read_point1; + } + + do { + if(session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { + return LIBSSH2_ERROR_SOCKET_DISCONNECT; + } + + if(session->state & LIBSSH2_STATE_NEWKEYS) { + blocksize = session->remote.crypt->blocksize; + } + else { + encrypted = 0; /* not encrypted */ + blocksize = 5; /* not strictly true, but we can use 5 here to + make the checks below work fine still */ + } + + /* read/use a whole big chunk into a temporary area stored in + the LIBSSH2_SESSION struct. We will decrypt data from that + buffer into the packet buffer so this temp one doesn't have + to be able to keep a whole SSH packet, just be large enough + so that we can read big chunks from the network layer. */ + + /* how much data there is remaining in the buffer to deal with + before we should read more from the network */ + remainbuf = p->writeidx - p->readidx; + + /* if remainbuf turns negative we have a bad internal error */ + assert(remainbuf >= 0); + + if(remainbuf < blocksize) { + /* If we have less than a blocksize left, it is too + little data to deal with, read more */ + ssize_t nread; + + /* move any remainder to the start of the buffer so + that we can do a full refill */ + if(remainbuf) { + memmove(p->buf, &p->buf[p->readidx], remainbuf); + p->readidx = 0; + p->writeidx = remainbuf; + } + else { + /* nothing to move, just zero the indexes */ + p->readidx = p->writeidx = 0; + } + + /* now read a big chunk from the network into the temp buffer */ + nread = + LIBSSH2_RECV(session, &p->buf[remainbuf], + PACKETBUFSIZE - remainbuf, + LIBSSH2_SOCKET_RECV_FLAGS(session)); + if(nread <= 0) { + /* check if this is due to EAGAIN and return the special + return code if so, error out normally otherwise */ + if((nread < 0) && (nread == -EAGAIN)) { + session->socket_block_directions |= + LIBSSH2_SESSION_BLOCK_INBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error recving %d bytes (got %d)", + PACKETBUFSIZE - remainbuf, -nread); + return LIBSSH2_ERROR_SOCKET_RECV; + } + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Recved %d/%d bytes to %p+%d", nread, + PACKETBUFSIZE - remainbuf, p->buf, remainbuf); + + debugdump(session, "libssh2_transport_read() raw", + &p->buf[remainbuf], nread); + /* advance write pointer */ + p->writeidx += nread; + + /* update remainbuf counter */ + remainbuf = p->writeidx - p->readidx; + } + + /* how much data to deal with from the buffer */ + numbytes = remainbuf; + + if(!p->total_num) { + size_t total_num; /* the number of bytes following the initial + (5 bytes) packet length and padding length + fields */ + + /* No payload package area allocated yet. To know the + size of this payload, we need to decrypt the first + blocksize data. */ + + if(numbytes < blocksize) { + /* we can't act on anything less than blocksize, but this + check is only done for the initial block since once we have + got the start of a block we can in fact deal with fractions + */ + session->socket_block_directions |= + LIBSSH2_SESSION_BLOCK_INBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + + if(encrypted) { + rc = decrypt(session, &p->buf[p->readidx], block, blocksize); + if(rc != LIBSSH2_ERROR_NONE) { + return rc; + } + /* save the first 5 bytes of the decrypted package, to be + used in the hash calculation later down. */ + memcpy(p->init, block, 5); + } + else { + /* the data is plain, just copy it verbatim to + the working block buffer */ + memcpy(block, &p->buf[p->readidx], blocksize); + } + + /* advance the read pointer */ + p->readidx += blocksize; + + /* we now have the initial blocksize bytes decrypted, + * and we can extract packet and padding length from it + */ + p->packet_length = _libssh2_ntohu32(block); + if(p->packet_length < 1) { + return LIBSSH2_ERROR_DECRYPT; + } + else if(p->packet_length > LIBSSH2_PACKET_MAXPAYLOAD) { + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } + + p->padding_length = block[4]; + if(p->padding_length > p->packet_length - 1) { + return LIBSSH2_ERROR_DECRYPT; + } + + + /* total_num is the number of bytes following the initial + (5 bytes) packet length and padding length fields */ + total_num = + p->packet_length - 1 + + (encrypted ? session->remote.mac->mac_len : 0); + + /* RFC4253 section 6.1 Maximum Packet Length says: + * + * "All implementations MUST be able to process + * packets with uncompressed payload length of 32768 + * bytes or less and total packet size of 35000 bytes + * or less (including length, padding length, payload, + * padding, and MAC.)." + */ + if(total_num > LIBSSH2_PACKET_MAXPAYLOAD || total_num == 0) { + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } + + /* Get a packet handle put data into. We get one to + hold all data, including padding and MAC. */ + p->payload = LIBSSH2_ALLOC(session, total_num); + if(!p->payload) { + return LIBSSH2_ERROR_ALLOC; + } + p->total_num = total_num; + /* init write pointer to start of payload buffer */ + p->wptr = p->payload; + + if(blocksize > 5) { + /* copy the data from index 5 to the end of + the blocksize from the temporary buffer to + the start of the decrypted buffer */ + if(blocksize - 5 <= (int) total_num) { + memcpy(p->wptr, &block[5], blocksize - 5); + p->wptr += blocksize - 5; /* advance write pointer */ + } + else { + if(p->payload) + LIBSSH2_FREE(session, p->payload); + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } + } + + /* init the data_num field to the number of bytes of + the package read so far */ + p->data_num = p->wptr - p->payload; + + /* we already dealt with a blocksize worth of data */ + numbytes -= blocksize; + } + + /* how much there is left to add to the current payload + package */ + remainpack = p->total_num - p->data_num; + + if(numbytes > remainpack) { + /* if we have more data in the buffer than what is going into this + particular packet, we limit this round to this packet only */ + numbytes = remainpack; + } + + if(encrypted) { + /* At the end of the incoming stream, there is a MAC, + and we don't want to decrypt that since we need it + "raw". We MUST however decrypt the padding data + since it is used for the hash later on. */ + int skip = session->remote.mac->mac_len; + + /* if what we have plus numbytes is bigger than the + total minus the skip margin, we should lower the + amount to decrypt even more */ + if((p->data_num + numbytes) > (p->total_num - skip)) { + numdecrypt = (p->total_num - skip) - p->data_num; + } + else { + int frac; + numdecrypt = numbytes; + frac = numdecrypt % blocksize; + if(frac) { + /* not an aligned amount of blocks, + align it */ + numdecrypt -= frac; + /* and make it no unencrypted data + after it */ + numbytes = 0; + } + } + } + else { + /* unencrypted data should not be decrypted at all */ + numdecrypt = 0; + } + + /* if there are bytes to decrypt, do that */ + if(numdecrypt > 0) { + /* now decrypt the lot */ + rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt); + if(rc != LIBSSH2_ERROR_NONE) { + p->total_num = 0; /* no packet buffer available */ + return rc; + } + + /* advance the read pointer */ + p->readidx += numdecrypt; + /* advance write pointer */ + p->wptr += numdecrypt; + /* increase data_num */ + p->data_num += numdecrypt; + + /* bytes left to take care of without decryption */ + numbytes -= numdecrypt; + } + + /* if there are bytes to copy that aren't decrypted, simply + copy them as-is to the target buffer */ + if(numbytes > 0) { + + if(numbytes <= (int)(p->total_num - (p->wptr - p->payload))) { + memcpy(p->wptr, &p->buf[p->readidx], numbytes); + } + else { + if(p->payload) + LIBSSH2_FREE(session, p->payload); + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } + + /* advance the read pointer */ + p->readidx += numbytes; + /* advance write pointer */ + p->wptr += numbytes; + /* increase data_num */ + p->data_num += numbytes; + } + + /* now check how much data there's left to read to finish the + current packet */ + remainpack = p->total_num - p->data_num; + + if(!remainpack) { + /* we have a full packet */ + libssh2_transport_read_point1: + rc = fullpacket(session, encrypted); + if(rc == LIBSSH2_ERROR_EAGAIN) { + + if(session->packAdd_state != libssh2_NB_state_idle) { + /* fullpacket only returns LIBSSH2_ERROR_EAGAIN if + * libssh2_packet_add returns LIBSSH2_ERROR_EAGAIN. If + * that returns LIBSSH2_ERROR_EAGAIN but the packAdd_state + * is idle, then the packet has been added to the brigade, + * but some immediate action that was taken based on the + * packet type (such as key re-exchange) is not yet + * complete. Clear the way for a new packet to be read + * in. + */ + session->readPack_encrypted = encrypted; + session->readPack_state = libssh2_NB_state_jump1; + } + + return rc; + } + + p->total_num = 0; /* no packet buffer available */ + + return rc; + } + } while(1); /* loop */ + + return LIBSSH2_ERROR_SOCKET_RECV; /* we never reach this point */ +} + +static int +send_existing(LIBSSH2_SESSION *session, const unsigned char *data, + size_t data_len, ssize_t *ret) +{ + ssize_t rc; + ssize_t length; + struct transportpacket *p = &session->packet; + + if(!p->olen) { + *ret = 0; + return LIBSSH2_ERROR_NONE; + } + + /* send as much as possible of the existing packet */ + if((data != p->odata) || (data_len != p->olen)) { + /* When we are about to complete the sending of a packet, it is vital + that the caller doesn't try to send a new/different packet since + we don't add this one up until the previous one has been sent. To + make the caller really notice his/hers flaw, we return error for + this case */ + return LIBSSH2_ERROR_BAD_USE; + } + + *ret = 1; /* set to make our parent return */ + + /* number of bytes left to send */ + length = p->ototal_num - p->osent; + + rc = LIBSSH2_SEND(session, &p->outbuf[p->osent], length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if(rc < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", length, -rc); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p+%d", rc, length, p->outbuf, + p->osent); + debugdump(session, "libssh2_transport_write send()", + &p->outbuf[p->osent], rc); + } + + if(rc == length) { + /* the remainder of the package was sent */ + p->ototal_num = 0; + p->olen = 0; + /* we leave *ret set so that the parent returns as we MUST return back + a send success now, so that we don't risk sending EAGAIN later + which then would confuse the parent function */ + return LIBSSH2_ERROR_NONE; + + } + else if(rc < 0) { + /* nothing was sent */ + if(rc != -EAGAIN) + /* send failure! */ + return LIBSSH2_ERROR_SOCKET_SEND; + + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + + p->osent += rc; /* we sent away this much data */ + + return rc < length ? LIBSSH2_ERROR_EAGAIN : LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_transport_send + * + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure. + * + * The data is provided as _two_ data areas that are combined by this + * function. The 'data' part is sent immediately before 'data2'. 'data2' may + * be set to NULL to only use a single part. + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block or if the whole packet was + * not sent yet. If it does so, the caller should call this function again as + * soon as it is likely that more data can be sent, and this function MUST + * then be called with the same argument set (same data pointer and same + * data_len) until ERROR_NONE or failure is returned. + * + * This function DOES NOT call _libssh2_error() on any errors. + */ +int _libssh2_transport_send(LIBSSH2_SESSION *session, + const unsigned char *data, size_t data_len, + const unsigned char *data2, size_t data2_len) +{ + int blocksize = + (session->state & LIBSSH2_STATE_NEWKEYS) ? + session->local.crypt->blocksize : 8; + int padding_length; + size_t packet_length; + int total_length; +#ifdef RANDOM_PADDING + int rand_max; + int seed = data[0]; /* FIXME: make this random */ +#endif + struct transportpacket *p = &session->packet; + int encrypted; + int compressed; + ssize_t ret; + int rc; + const unsigned char *orgdata = data; + size_t orgdata_len = data_len; + + /* + * If the last read operation was interrupted in the middle of a key + * exchange, we must complete that key exchange before continuing to write + * further data. + * + * See the similar block in _libssh2_transport_read for more details. + */ + if(session->state & LIBSSH2_STATE_EXCHANGING_KEYS && + !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { + /* Don't write any new packets if we're still in the middle of a key + * exchange. */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_send"); + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if(rc) + return rc; + } + + debugdump(session, "libssh2_transport_write plain", data, data_len); + if(data2) + debugdump(session, "libssh2_transport_write plain2", data2, data2_len); + + /* FIRST, check if we have a pending write to complete. send_existing + only sanity-check data and data_len and not data2 and data2_len!! */ + rc = send_existing(session, data, data_len, &ret); + if(rc) + return rc; + + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND; + + if(ret) + /* set by send_existing if data was sent */ + return rc; + + encrypted = (session->state & LIBSSH2_STATE_NEWKEYS) ? 1 : 0; + + compressed = + session->local.comp != NULL && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if(encrypted && compressed && session->local.comp_abstract) { + /* the idea here is that these function must fail if the output gets + larger than what fits in the assigned buffer so thus they don't + check the input size as we don't know how much it compresses */ + size_t dest_len = MAX_SSH_PACKET_LEN-5-256; + size_t dest2_len = dest_len; + + /* compress directly to the target buffer */ + rc = session->local.comp->comp(session, + &p->outbuf[5], &dest_len, + data, data_len, + &session->local.comp_abstract); + if(rc) + return rc; /* compression failure */ + + if(data2 && data2_len) { + /* compress directly to the target buffer right after where the + previous call put data */ + dest2_len -= dest_len; + + rc = session->local.comp->comp(session, + &p->outbuf[5 + dest_len], + &dest2_len, + data2, data2_len, + &session->local.comp_abstract); + } + else + dest2_len = 0; + if(rc) + return rc; /* compression failure */ + + data_len = dest_len + dest2_len; /* use the combined length */ + } + else { + if((data_len + data2_len) >= (MAX_SSH_PACKET_LEN-0x100)) + /* too large packet, return error for this until we make this + function split it up and send multiple SSH packets */ + return LIBSSH2_ERROR_INVAL; + + /* copy the payload data */ + memcpy(&p->outbuf[5], data, data_len); + if(data2 && data2_len) + memcpy(&p->outbuf[5 + data_len], data2, data2_len); + data_len += data2_len; /* use the combined length */ + } + + + /* RFC4253 says: Note that the length of the concatenation of + 'packet_length', 'padding_length', 'payload', and 'random padding' + MUST be a multiple of the cipher block size or 8, whichever is + larger. */ + + /* Plain math: (4 + 1 + packet_length + padding_length) % blocksize == 0 */ + + packet_length = data_len + 1 + 4; /* 1 is for padding_length field + 4 for the packet_length field */ + + /* at this point we have it all except the padding */ + + /* first figure out our minimum padding amount to make it an even + block size */ + padding_length = blocksize - (packet_length % blocksize); + + /* if the padding becomes too small we add another blocksize worth + of it (taken from the original libssh2 where it didn't have any + real explanation) */ + if(padding_length < 4) { + padding_length += blocksize; + } +#ifdef RANDOM_PADDING + /* FIXME: we can add padding here, but that also makes the packets + bigger etc */ + + /* now we can add 'blocksize' to the padding_length N number of times + (to "help thwart traffic analysis") but it must be less than 255 in + total */ + rand_max = (255 - padding_length) / blocksize + 1; + padding_length += blocksize * (seed % rand_max); +#endif + + packet_length += padding_length; + + /* append the MAC length to the total_length size */ + total_length = + packet_length + (encrypted ? session->local.mac->mac_len : 0); + + /* store packet_length, which is the size of the whole packet except + the MAC and the packet_length field itself */ + _libssh2_htonu32(p->outbuf, packet_length - 4); + /* store padding_length */ + p->outbuf[4] = (unsigned char)padding_length; + + /* fill the padding area with random junk */ + if(_libssh2_random(p->outbuf + 5 + data_len, padding_length)) { + return _libssh2_error(session, LIBSSH2_ERROR_RANDGEN, + "Unable to get random bytes for packet padding"); + } + + if(encrypted) { + size_t i; + + /* Calculate MAC hash. Put the output at index packet_length, + since that size includes the whole packet. The MAC is + calculated on the entire unencrypted packet, including all + fields except the MAC field itself. */ + session->local.mac->hash(session, p->outbuf + packet_length, + session->local.seqno, p->outbuf, + packet_length, NULL, 0, + &session->local.mac_abstract); + + /* Encrypt the whole packet data, one block size at a time. + The MAC field is not encrypted. */ + for(i = 0; i < packet_length; i += session->local.crypt->blocksize) { + unsigned char *ptr = &p->outbuf[i]; + if(session->local.crypt->crypt(session, ptr, + session->local.crypt->blocksize, + &session->local.crypt_abstract)) + return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */ + } + } + + session->local.seqno++; + + ret = LIBSSH2_SEND(session, p->outbuf, total_length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if(ret < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", total_length, -ret); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, "Sent %d/%d bytes at %p", + ret, total_length, p->outbuf); + debugdump(session, "libssh2_transport_write send()", p->outbuf, ret); + } + + if(ret != total_length) { + if(ret >= 0 || ret == -EAGAIN) { + /* the whole packet could not be sent, save the rest */ + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + p->odata = orgdata; + p->olen = orgdata_len; + p->osent = ret <= 0 ? 0 : ret; + p->ototal_num = total_length; + return LIBSSH2_ERROR_EAGAIN; + } + return LIBSSH2_ERROR_SOCKET_SEND; + } + + /* the whole thing got sent away */ + p->odata = NULL; + p->olen = 0; + + return LIBSSH2_ERROR_NONE; /* all is good */ +} +#endif diff --git a/lib/libssh2/transport.h b/lib/libssh2/transport.h new file mode 100644 index 0000000..6ff9f59 --- /dev/null +++ b/lib/libssh2/transport.h @@ -0,0 +1,88 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_TRANSPORT_H +#define __LIBSSH2_TRANSPORT_H +/* Copyright (C) 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2009-2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file handles reading and writing to the SECSH transport layer. RFC4253. + */ + +#include "libssh2_priv.h" +#include "packet.h" + + +/* + * libssh2_transport_send + * + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure. + * + * The data is provided as _two_ data areas that are combined by this + * function. The 'data' part is sent immediately before 'data2'. 'data2' can + * be set to NULL (or data2_len to 0) to only use a single part. + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block or if the whole packet was + * not sent yet. If it does so, the caller should call this function again as + * soon as it is likely that more data can be sent, and this function MUST + * then be called with the same argument set (same data pointer and same + * data_len) until ERROR_NONE or failure is returned. + * + * This function DOES NOT call _libssh2_error() on any errors. + */ +int _libssh2_transport_send(LIBSSH2_SESSION *session, + const unsigned char *data, size_t data_len, + const unsigned char *data2, size_t data2_len); + +/* + * _libssh2_transport_read + * + * Collect a packet into the input brigade block only controls whether or not + * to wait for a packet to start. + * + * Returns packet type added to input brigade (PACKET_NONE if nothing added), + * or PACKET_FAIL on failure and PACKET_EAGAIN if it couldn't process a full + * packet. + */ + +/* + * This function reads the binary stream as specified in chapter 6 of RFC4253 + * "The Secure Shell (SSH) Transport Layer Protocol" + */ +int _libssh2_transport_read(LIBSSH2_SESSION * session); + +#endif /* __LIBSSH2_TRANSPORT_H */ +#endif diff --git a/lib/libssh2/userauth.c b/lib/libssh2/userauth.c new file mode 100644 index 0000000..4525295 --- /dev/null +++ b/lib/libssh2/userauth.c @@ -0,0 +1,2348 @@ +#if defined(ESP32) +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2005 Mikhail Gusarov + * Copyright (c) 2009-2014 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#include +#include + +#include + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#include "transport.h" +#include "session.h" +#include "userauth.h" +#include "userauth_kbd_packet.h" + +/* libssh2_userauth_list + * + * List authentication methods + * Will yield successful login if "none" happens to be allowable for this user + * Not a common configuration for any SSH server though + * username should be NULL, or a null terminated string + */ +static char *userauth_list(LIBSSH2_SESSION *session, const char *username, + unsigned int username_len) +{ + unsigned char reply_codes[4] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_BANNER, 0 }; + /* packet_type(1) + username_len(4) + service_len(4) + + service(14)"ssh-connection" + method_len(4) = 27 */ + unsigned long methods_len; + unsigned int banner_len; + unsigned char *s; + int rc; + + if(session->userauth_list_state == libssh2_NB_state_idle) { + /* Zero the whole thing out */ + memset(&session->userauth_list_packet_requirev_state, 0, + sizeof(session->userauth_list_packet_requirev_state)); + + session->userauth_list_data_len = username_len + 27; + + s = session->userauth_list_data = + LIBSSH2_ALLOC(session, session->userauth_list_data_len); + if(!session->userauth_list_data) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for userauth_list"); + return NULL; + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", 14); + _libssh2_store_u32(&s, 4); /* send "none" separately */ + + session->userauth_list_state = libssh2_NB_state_created; + } + + if(session->userauth_list_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_list_data, + session->userauth_list_data_len, + (unsigned char *)"none", 4); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + return NULL; + } + /* now free the packet that was sent */ + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + + if(rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-none request"); + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + session->userauth_list_state = libssh2_NB_state_sent; + } + + if(session->userauth_list_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_list_data, + &session->userauth_list_data_len, 0, + NULL, 0, + &session->userauth_list_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + return NULL; + } + else if(rc || (session->userauth_list_data_len < 1)) { + _libssh2_error(session, rc, "Failed getting response"); + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + if(session->userauth_list_data[0] == SSH_MSG_USERAUTH_BANNER) { + if(session->userauth_list_data_len < 5) { + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + return NULL; + } + banner_len = _libssh2_ntohu32(session->userauth_list_data + 1); + if(banner_len > session->userauth_list_data_len - 5) { + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Unexpected userauth banner size"); + return NULL; + } + session->userauth_banner = LIBSSH2_ALLOC(session, banner_len + 1); + if(!session->userauth_banner) { + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for userauth_banner"); + return NULL; + } + memcpy(session->userauth_banner, session->userauth_list_data + 5, + banner_len); + session->userauth_banner[banner_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Banner: %s", + session->userauth_banner); + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + /* SSH_MSG_USERAUTH_BANNER has been handled */ + reply_codes[2] = 0; + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_list_data, + &session->userauth_list_data_len, 0, + NULL, 0, + &session->userauth_list_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + return NULL; + } + else if(rc || (session->userauth_list_data_len < 1)) { + _libssh2_error(session, rc, "Failed getting response"); + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + } + + if(session->userauth_list_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + /* Wow, who'dve thought... */ + _libssh2_error(session, LIBSSH2_ERROR_NONE, "No error"); + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + if(session->userauth_list_data_len < 5) { + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + return NULL; + } + + methods_len = _libssh2_ntohu32(session->userauth_list_data + 1); + if(methods_len >= session->userauth_list_data_len - 5) { + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Unexpected userauth list size"); + return NULL; + } + + /* Do note that the memory areas overlap! */ + memmove(session->userauth_list_data, session->userauth_list_data + 5, + methods_len); + session->userauth_list_data[methods_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Permitted auth methods: %s", + session->userauth_list_data); + } + + session->userauth_list_state = libssh2_NB_state_idle; + return (char *) session->userauth_list_data; +} + +/* libssh2_userauth_list + * + * List authentication methods + * Will yield successful login if "none" happens to be allowable for this user + * Not a common configuration for any SSH server though + * username should be NULL, or a null terminated string + */ +LIBSSH2_API char * +libssh2_userauth_list(LIBSSH2_SESSION * session, const char *user, + unsigned int user_len) +{ + char *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + userauth_list(session, user, user_len)); + return ptr; +} + +/* libssh2_userauth_banner + * + * Retrieve banner message from server, if available. + * When no such message is sent by server or if no authentication attempt has + * been made, this function returns LIBSSH2_ERROR_MISSING_AUTH_BANNER. + */ +LIBSSH2_API int +libssh2_userauth_banner(LIBSSH2_SESSION *session, char **banner) +{ + if(NULL == session) + return LIBSSH2_ERROR_MISSING_USERAUTH_BANNER; + + if(!session->userauth_banner) { + return _libssh2_error(session, + LIBSSH2_ERROR_MISSING_USERAUTH_BANNER, + "Missing userauth banner"); + } + + if(banner != NULL) + *banner = session->userauth_banner; + + return LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_userauth_authenticated + * + * Returns: 0 if not yet authenticated + * 1 if already authenticated + */ +LIBSSH2_API int +libssh2_userauth_authenticated(LIBSSH2_SESSION * session) +{ + return (session->state & LIBSSH2_STATE_AUTHENTICATED)?1:0; +} + + + +/* userauth_password + * Plain ol' login + */ +static int +userauth_password(LIBSSH2_SESSION *session, + const char *username, unsigned int username_len, + const unsigned char *password, unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) +{ + unsigned char *s; + static const unsigned char reply_codes[4] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 0 + }; + int rc; + + if(session->userauth_pswd_state == libssh2_NB_state_idle) { + /* Zero the whole thing out */ + memset(&session->userauth_pswd_packet_requirev_state, 0, + sizeof(session->userauth_pswd_packet_requirev_state)); + + /* + * 40 = packet_type(1) + username_len(4) + service_len(4) + + * service(14)"ssh-connection" + method_len(4) + method(8)"password" + + * chgpwdbool(1) + password_len(4) */ + session->userauth_pswd_data_len = username_len + 40; + + session->userauth_pswd_data0 = + (unsigned char) ~SSH_MSG_USERAUTH_PASSWD_CHANGEREQ; + + /* TODO: remove this alloc with a fixed buffer in the session + struct */ + s = session->userauth_pswd_data = + LIBSSH2_ALLOC(session, session->userauth_pswd_data_len); + if(!session->userauth_pswd_data) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "userauth-password request"); + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", sizeof("ssh-connection") - 1); + _libssh2_store_str(&s, "password", sizeof("password") - 1); + *s++ = '\0'; + _libssh2_store_u32(&s, password_len); + /* 'password' is sent separately */ + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting to login using password authentication"); + + session->userauth_pswd_state = libssh2_NB_state_created; + } + + if(session->userauth_pswd_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_pswd_data, + session->userauth_pswd_data_len, + password, password_len); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block writing password request"); + } + + /* now free the sent packet */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + + if(rc) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-password request"); + } + + session->userauth_pswd_state = libssh2_NB_state_sent; + } + + password_response: + + if((session->userauth_pswd_state == libssh2_NB_state_sent) + || (session->userauth_pswd_state == libssh2_NB_state_sent1) + || (session->userauth_pswd_state == libssh2_NB_state_sent2)) { + if(session->userauth_pswd_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pswd_data, + &session->userauth_pswd_data_len, + 0, NULL, 0, + &session-> + userauth_pswd_packet_requirev_state); + + if(rc) { + if(rc != LIBSSH2_ERROR_EAGAIN) + session->userauth_pswd_state = libssh2_NB_state_idle; + + return _libssh2_error(session, rc, + "Waiting for password response"); + } + else if(session->userauth_pswd_data_len < 1) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + } + + if(session->userauth_pswd_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password authentication successful"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pswd_state = libssh2_NB_state_idle; + return 0; + } + else if(session->userauth_pswd_data[0] == + SSH_MSG_USERAUTH_FAILURE) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password authentication failed"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed " + "(username/password)"); + } + + session->userauth_pswd_newpw = NULL; + session->userauth_pswd_newpw_len = 0; + + session->userauth_pswd_state = libssh2_NB_state_sent1; + } + + if(session->userauth_pswd_data_len < 1) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Unexpected packet size"); + } + + if((session->userauth_pswd_data[0] == + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ) + || (session->userauth_pswd_data0 == + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ)) { + session->userauth_pswd_data0 = SSH_MSG_USERAUTH_PASSWD_CHANGEREQ; + + if((session->userauth_pswd_state == libssh2_NB_state_sent1) || + (session->userauth_pswd_state == libssh2_NB_state_sent2)) { + if(session->userauth_pswd_state == libssh2_NB_state_sent1) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password change required"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + } + if(passwd_change_cb) { + if(session->userauth_pswd_state == + libssh2_NB_state_sent1) { + passwd_change_cb(session, + &session->userauth_pswd_newpw, + &session->userauth_pswd_newpw_len, + &session->abstract); + if(!session->userauth_pswd_newpw) { + return _libssh2_error(session, + LIBSSH2_ERROR_PASSWORD_EXPIRED, + "Password expired, and " + "callback failed"); + } + + /* basic data_len + newpw_len(4) */ + if(username_len + password_len + 44 <= UINT_MAX) { + session->userauth_pswd_data_len = + username_len + password_len + 44; + s = session->userauth_pswd_data = + LIBSSH2_ALLOC(session, + session->userauth_pswd_data_len); + } + else { + s = session->userauth_pswd_data = NULL; + session->userauth_pswd_data_len = 0; + } + + if(!session->userauth_pswd_data) { + LIBSSH2_FREE(session, + session->userauth_pswd_newpw); + session->userauth_pswd_newpw = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for userauth password " + "change request"); + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", + sizeof("ssh-connection") - 1); + _libssh2_store_str(&s, "password", + sizeof("password") - 1); + *s++ = 0x01; + _libssh2_store_str(&s, (char *)password, password_len); + _libssh2_store_u32(&s, + session->userauth_pswd_newpw_len); + /* send session->userauth_pswd_newpw separately */ + + session->userauth_pswd_state = libssh2_NB_state_sent2; + } + + if(session->userauth_pswd_state == + libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, + session->userauth_pswd_data, + session->userauth_pswd_data_len, + (unsigned char *) + session->userauth_pswd_newpw, + session->userauth_pswd_newpw_len); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, + LIBSSH2_ERROR_EAGAIN, + "Would block waiting"); + } + + /* free the allocated packets again */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + LIBSSH2_FREE(session, session->userauth_pswd_newpw); + session->userauth_pswd_newpw = NULL; + + if(rc) { + return _libssh2_error(session, + LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth " + "password-change request"); + } + + /* + * Ugliest use of goto ever. Blame it on the + * askN => requirev migration. + */ + session->userauth_pswd_state = libssh2_NB_state_sent; + goto password_response; + } + } + } + else { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, + "Password Expired, and no callback " + "specified"); + } + } + } + + /* FAILURE */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->userauth_pswd_state = libssh2_NB_state_idle; + + return _libssh2_error(session, LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed"); +} + +/* + * libssh2_userauth_password_ex + * + * Plain ol' login + */ + +LIBSSH2_API int +libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, + unsigned int username_len, const char *password, + unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC + ((*passwd_change_cb))) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_password(session, username, username_len, + (unsigned char *)password, password_len, + passwd_change_cb)); + return rc; +} + +static int +memory_read_publickey(LIBSSH2_SESSION * session, unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *pubkeyfiledata, + size_t pubkeyfiledata_len) +{ + unsigned char *pubkey = NULL, *sp1, *sp2, *tmp; + size_t pubkey_len = pubkeyfiledata_len; + unsigned int tmp_len; + + if(pubkeyfiledata_len <= 1) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public key file"); + } + + pubkey = LIBSSH2_ALLOC(session, pubkeyfiledata_len); + if(!pubkey) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for public key data"); + } + + memcpy(pubkey, pubkeyfiledata, pubkeyfiledata_len); + + /* + * Remove trailing whitespace + */ + while(pubkey_len && isspace(pubkey[pubkey_len - 1])) + pubkey_len--; + + if(!pubkey_len) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Missing public key data"); + } + + sp1 = memchr(pubkey, ' ', pubkey_len); + if(sp1 == NULL) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid public key data"); + } + + sp1++; + + sp2 = memchr(sp1, ' ', pubkey_len - (sp1 - pubkey)); + if(sp2 == NULL) { + /* Assume that the id string is missing, but that it's okay */ + sp2 = pubkey + pubkey_len; + } + + if(libssh2_base64_decode(session, (char **) &tmp, &tmp_len, + (char *) sp1, sp2 - sp1)) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid key data, not base64 encoded"); + } + + /* Wasting some bytes here (okay, more than some), but since it's likely + * to be freed soon anyway, we'll just avoid the extra free/alloc and call + * it a wash + */ + *method = pubkey; + *method_len = sp1 - pubkey - 1; + + *pubkeydata = tmp; + *pubkeydata_len = tmp_len; + + return 0; +} + +/* + * file_read_publickey + * + * Read a public key from an id_???.pub style file + * + * Returns an allocated string containing the decoded key in *pubkeydata + * on success. + * Returns an allocated string containing the key method (e.g. "ssh-dss") + * in method on success. + */ +static int +file_read_publickey(LIBSSH2_SESSION * session, unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *pubkeyfile) +{ + FILE *fd; + char c; + unsigned char *pubkey = NULL, *sp1, *sp2, *tmp; + size_t pubkey_len = 0, sp_len; + unsigned int tmp_len; + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, "Loading public key file: %s", + pubkeyfile); + /* Read Public Key */ + fd = fopen(pubkeyfile, FOPEN_READTEXT); + if(!fd) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to open public key file"); + } + while(!feof(fd) && 1 == fread(&c, 1, 1, fd) && c != '\r' && c != '\n') { + pubkey_len++; + } + rewind(fd); + + if(pubkey_len <= 1) { + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public key file"); + } + + pubkey = LIBSSH2_ALLOC(session, pubkey_len); + if(!pubkey) { + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for public key data"); + } + if(fread(pubkey, 1, pubkey_len, fd) != pubkey_len) { + LIBSSH2_FREE(session, pubkey); + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to read public key from file"); + } + fclose(fd); + /* + * Remove trailing whitespace + */ + while(pubkey_len && isspace(pubkey[pubkey_len - 1])) { + pubkey_len--; + } + + if(!pubkey_len) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Missing public key data"); + } + + sp1 = memchr(pubkey, ' ', pubkey_len); + if(sp1 == NULL) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid public key data"); + } + + sp1++; + + sp_len = sp1 > pubkey ? (sp1 - pubkey) : 0; + sp2 = memchr(sp1, ' ', pubkey_len - sp_len); + if(sp2 == NULL) { + /* Assume that the id string is missing, but that it's okay */ + sp2 = pubkey + pubkey_len; + } + + if(libssh2_base64_decode(session, (char **) &tmp, &tmp_len, + (char *) sp1, sp2 - sp1)) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid key data, not base64 encoded"); + } + + /* Wasting some bytes here (okay, more than some), but since it's likely + * to be freed soon anyway, we'll just avoid the extra free/alloc and call + * it a wash */ + *method = pubkey; + *method_len = sp1 - pubkey - 1; + + *pubkeydata = tmp; + *pubkeydata_len = tmp_len; + + return 0; +} + +static int +memory_read_privatekey(LIBSSH2_SESSION * session, + const LIBSSH2_HOSTKEY_METHOD ** hostkey_method, + void **hostkey_abstract, + const unsigned char *method, int method_len, + const char *privkeyfiledata, size_t privkeyfiledata_len, + const char *passphrase) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = + libssh2_hostkey_methods(); + + *hostkey_method = NULL; + *hostkey_abstract = NULL; + while(*hostkey_methods_avail && (*hostkey_methods_avail)->name) { + if((*hostkey_methods_avail)->initPEMFromMemory + && strncmp((*hostkey_methods_avail)->name, (const char *) method, + method_len) == 0) { + *hostkey_method = *hostkey_methods_avail; + break; + } + hostkey_methods_avail++; + } + if(!*hostkey_method) { + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No handler for specified private key"); + } + + if((*hostkey_method)-> + initPEMFromMemory(session, privkeyfiledata, privkeyfiledata_len, + (unsigned char *) passphrase, + hostkey_abstract)) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to initialize private key from memory"); + } + + return 0; +} + +/* libssh2_file_read_privatekey + * Read a PEM encoded private key from an id_??? style file + */ +static int +file_read_privatekey(LIBSSH2_SESSION * session, + const LIBSSH2_HOSTKEY_METHOD ** hostkey_method, + void **hostkey_abstract, + const unsigned char *method, int method_len, + const char *privkeyfile, const char *passphrase) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = + libssh2_hostkey_methods(); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, "Loading private key file: %s", + privkeyfile); + *hostkey_method = NULL; + *hostkey_abstract = NULL; + while(*hostkey_methods_avail && (*hostkey_methods_avail)->name) { + if((*hostkey_methods_avail)->initPEM + && strncmp((*hostkey_methods_avail)->name, (const char *) method, + method_len) == 0) { + *hostkey_method = *hostkey_methods_avail; + break; + } + hostkey_methods_avail++; + } + if(!*hostkey_method) { + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No handler for specified private key"); + } + + if((*hostkey_method)-> + initPEM(session, privkeyfile, (unsigned char *) passphrase, + hostkey_abstract)) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to initialize private key from file"); + } + + return 0; +} + +struct privkey_file { + const char *filename; + const char *passphrase; +}; + +static int +sign_frommemory(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + struct privkey_file *pk_file = (struct privkey_file *) (*abstract); + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + void *hostkey_abstract; + struct iovec datavec; + int rc; + + rc = memory_read_privatekey(session, &privkeyobj, &hostkey_abstract, + session->userauth_pblc_method, + session->userauth_pblc_method_len, + pk_file->filename, + strlen(pk_file->filename), + pk_file->passphrase); + if(rc) + return rc; + + libssh2_prepare_iovec(&datavec, 1); + datavec.iov_base = (void *)data; + datavec.iov_len = data_len; + + if(privkeyobj->signv(session, sig, sig_len, 1, &datavec, + &hostkey_abstract)) { + if(privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return -1; + } + + if(privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return 0; +} + +static int +sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + struct privkey_file *privkey_file = (struct privkey_file *) (*abstract); + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + void *hostkey_abstract; + struct iovec datavec; + int rc; + + rc = file_read_privatekey(session, &privkeyobj, &hostkey_abstract, + session->userauth_pblc_method, + session->userauth_pblc_method_len, + privkey_file->filename, + privkey_file->passphrase); + if(rc) + return rc; + + libssh2_prepare_iovec(&datavec, 1); + datavec.iov_base = (void *)data; + datavec.iov_len = data_len; + + if(privkeyobj->signv(session, sig, sig_len, 1, &datavec, + &hostkey_abstract)) { + if(privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return -1; + } + + if(privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return 0; +} + +int +libssh2_sign_sk(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + int rc = LIBSSH2_ERROR_DECRYPT; + LIBSSH2_PRIVKEY_SK *sk_info = (LIBSSH2_PRIVKEY_SK *) (*abstract); + LIBSSH2_SK_SIG_INFO sig_info = { 0 }; + + if(sk_info->handle_len <= 0) { + return LIBSSH2_ERROR_DECRYPT; + } + + rc = sk_info->sign_callback(session, + &sig_info, + data, + data_len, + sk_info->algorithm, + sk_info->flags, + sk_info->application, + sk_info->key_handle, + sk_info->handle_len, + sk_info->orig_abstract); + + if(rc == 0 && sig_info.sig_r_len > 0 && sig_info.sig_r) { + unsigned char *p = NULL; + + if(sig_info.sig_s_len > 0 && sig_info.sig_s) { + /* sig length, sig_r, sig_s, flags, counter, plus 4 bytes for each + component's length, and up to 1 extra byte for each component */ + *sig_len = 4 + 5 + sig_info.sig_r_len + 5 + sig_info.sig_s_len + 5; + *sig = LIBSSH2_ALLOC(session, *sig_len); + + if(*sig) { + unsigned char *x = *sig; + p = *sig; + + _libssh2_store_u32(&p, 0); + + _libssh2_store_bignum2_bytes(&p, + sig_info.sig_r, + sig_info.sig_r_len); + + _libssh2_store_bignum2_bytes(&p, + sig_info.sig_s, + sig_info.sig_s_len); + + *sig_len = p - *sig; + + _libssh2_store_u32(&x, *sig_len - 4); + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate ecdsa-sk signature."); + rc = LIBSSH2_ERROR_ALLOC; + } + } + else { + /* sig, flags, counter, plus 4 bytes for sig length. */ + *sig_len = 4 + sig_info.sig_r_len + 1 + 4; + *sig = LIBSSH2_ALLOC(session, *sig_len); + + if(*sig) { + p = *sig; + + _libssh2_store_str(&p, + (const char *)sig_info.sig_r, + sig_info.sig_r_len); + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate ed25519-sk signature."); + rc = LIBSSH2_ERROR_ALLOC; + } + } + + if(p) { + *p = sig_info.flags; + ++p; + _libssh2_store_u32(&p, sig_info.counter); + + *sig_len = p - *sig; + } + + LIBSSH2_FREE(session, sig_info.sig_r); + + if(sig_info.sig_s != NULL) { + LIBSSH2_FREE(session, sig_info.sig_s); + } + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_DECRYPT, + "sign_callback failed or returned invalid signature."); + *sig_len = 0; + } + + return rc; +} + +/* userauth_hostbased_fromfile + * Authenticate using a keypair found in the named files + */ +static int +userauth_hostbased_fromfile(LIBSSH2_SESSION *session, + const char *username, size_t username_len, + const char *publickey, const char *privatekey, + const char *passphrase, const char *hostname, + size_t hostname_len, + const char *local_username, + size_t local_username_len) +{ + int rc; + + if(session->userauth_host_state == libssh2_NB_state_idle) { + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + unsigned char *pubkeydata = NULL; + unsigned char *sig = NULL; + size_t pubkeydata_len = 0; + size_t sig_len = 0; + void *abstract; + unsigned char buf[5]; + struct iovec datavec[4]; + + /* Zero the whole thing out */ + memset(&session->userauth_host_packet_requirev_state, 0, + sizeof(session->userauth_host_packet_requirev_state)); + + if(publickey) { + rc = file_read_publickey(session, &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, publickey); + if(rc) + /* Note: file_read_publickey() calls _libssh2_error() */ + return rc; + } + else { + /* Compute public key from private key. */ + rc = _libssh2_pub_priv_keyfile(session, + &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase); + if(rc) + /* libssh2_pub_priv_keyfile calls _libssh2_error() */ + return rc; + } + + /* + * 52 = packet_type(1) + username_len(4) + servicename_len(4) + + * service_name(14)"ssh-connection" + authmethod_len(4) + + * authmethod(9)"hostbased" + method_len(4) + pubkeydata_len(4) + + * hostname_len(4) + local_username_len(4) + */ + session->userauth_host_packet_len = + username_len + session->userauth_host_method_len + hostname_len + + local_username_len + pubkeydata_len + 52; + + /* + * Preallocate space for an overall length, method name again, + * and the signature, which won't be any larger than the size of + * the publickeydata itself + */ + session->userauth_host_s = session->userauth_host_packet = + LIBSSH2_ALLOC(session, + session->userauth_host_packet_len + 4 + + (4 + session->userauth_host_method_len) + + (4 + pubkeydata_len)); + if(!session->userauth_host_packet) { + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, pubkeydata); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory"); + } + + *(session->userauth_host_s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&session->userauth_host_s, username, username_len); + _libssh2_store_str(&session->userauth_host_s, "ssh-connection", 14); + _libssh2_store_str(&session->userauth_host_s, "hostbased", 9); + _libssh2_store_str(&session->userauth_host_s, + (const char *)session->userauth_host_method, + session->userauth_host_method_len); + _libssh2_store_str(&session->userauth_host_s, (const char *)pubkeydata, + pubkeydata_len); + LIBSSH2_FREE(session, pubkeydata); + _libssh2_store_str(&session->userauth_host_s, hostname, hostname_len); + _libssh2_store_str(&session->userauth_host_s, local_username, + local_username_len); + + rc = file_read_privatekey(session, &privkeyobj, &abstract, + session->userauth_host_method, + session->userauth_host_method_len, + privatekey, passphrase); + if(rc) { + /* Note: file_read_privatekey() calls _libssh2_error() */ + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + return rc; + } + + _libssh2_htonu32(buf, session->session_id_len); + libssh2_prepare_iovec(datavec, 4); + datavec[0].iov_base = (void *)buf; + datavec[0].iov_len = 4; + datavec[1].iov_base = (void *)session->session_id; + datavec[1].iov_len = session->session_id_len; + datavec[2].iov_base = (void *)session->userauth_host_packet; + datavec[2].iov_len = session->userauth_host_packet_len; + + if(privkeyobj && privkeyobj->signv && + privkeyobj->signv(session, &sig, &sig_len, 3, + datavec, &abstract)) { + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + if(privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + return -1; + } + + if(privkeyobj && privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + + if(sig_len > pubkeydata_len) { + unsigned char *newpacket; + /* Should *NEVER* happen, but...well.. better safe than sorry */ + newpacket = LIBSSH2_REALLOC(session, session->userauth_host_packet, + session->userauth_host_packet_len + 4 + + (4 + session->userauth_host_method_len) + + (4 + sig_len)); /* PK sigblob */ + if(!newpacket) { + LIBSSH2_FREE(session, sig); + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating additional space for " + "userauth-hostbased packet"); + } + session->userauth_host_packet = newpacket; + } + + session->userauth_host_s = + session->userauth_host_packet + session->userauth_host_packet_len; + + _libssh2_store_u32(&session->userauth_host_s, + 4 + session->userauth_host_method_len + + 4 + sig_len); + _libssh2_store_str(&session->userauth_host_s, + (const char *)session->userauth_host_method, + session->userauth_host_method_len); + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + + _libssh2_store_str(&session->userauth_host_s, (const char *)sig, + sig_len); + LIBSSH2_FREE(session, sig); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting hostbased authentication"); + + session->userauth_host_state = libssh2_NB_state_created; + } + + if(session->userauth_host_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_host_packet, + session->userauth_host_s - + session->userauth_host_packet, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc) { + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + session->userauth_host_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-hostbased request"); + } + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + + session->userauth_host_state = libssh2_NB_state_sent; + } + + if(session->userauth_host_state == libssh2_NB_state_sent) { + static const unsigned char reply_codes[3] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 }; + size_t data_len; + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_host_data, + &data_len, 0, NULL, 0, + &session-> + userauth_host_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + + session->userauth_host_state = libssh2_NB_state_idle; + if(rc || data_len < 1) { + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Auth failed"); + } + + if(session->userauth_host_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Hostbased authentication successful"); + /* We are us and we've proved it. */ + LIBSSH2_FREE(session, session->userauth_host_data); + session->userauth_host_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + return 0; + } + } + + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_host_data); + session->userauth_host_data = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid signature for supplied public key, or bad " + "username/public key combination"); +} + +/* libssh2_userauth_hostbased_fromfile_ex + * Authenticate using a keypair found in the named files + */ +LIBSSH2_API int +libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + const char *publickey, + const char *privatekey, + const char *passphrase, + const char *host, + unsigned int host_len, + const char *localuser, + unsigned int localuser_len) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_hostbased_fromfile(session, user, user_len, + publickey, privatekey, + passphrase, host, host_len, + localuser, localuser_len)); + return rc; +} + +static int plain_method_len(const char *method, size_t method_len) +{ + if(!strncmp("ssh-rsa-cert-v01@openssh.com", + method, + method_len)) { + return 7; + } + + if(!strncmp("ecdsa-sha2-nistp256-cert-v01@openssh.com", + method, + method_len) || + !strncmp("ecdsa-sha2-nistp384-cert-v01@openssh.com", + method, + method_len) || + !strncmp("ecdsa-sha2-nistp521-cert-v01@openssh.com", + method, + method_len)) { + return 19; + } + return method_len; +} + +/** + * @function _libssh2_key_sign_algorithm + * @abstract Upgrades the algorithm used for public key signing RFC 8332 + * @discussion Based on the incoming key_method value, this function + * will upgrade the key method input based on user preferences, + * server support algos and crypto backend support + * @related _libssh2_supported_key_sign_algorithms() + * @param key_method current key method, usually the default key sig method + * @param key_method_len length of the key method buffer + * @result error code or zero on success + */ + +static int +_libssh2_key_sign_algorithm(LIBSSH2_SESSION *session, + unsigned char **key_method, + size_t *key_method_len) +{ + const char *s = NULL; + const char *a = NULL; + const char *match = NULL; + const char *p = NULL; + const char *f = NULL; + char *i = NULL; + int p_len = 0; + int f_len = 0; + int rc = 0; + int match_len = 0; + char *filtered_algs = NULL; + + const char *supported_algs = + _libssh2_supported_key_sign_algorithms(session, + *key_method, + *key_method_len); + + if(supported_algs == NULL || session->server_sign_algorithms == NULL) { + /* no upgrading key algorithm supported, do nothing */ + return LIBSSH2_ERROR_NONE; + } + + filtered_algs = LIBSSH2_ALLOC(session, strlen(supported_algs) + 1); + if(!filtered_algs) { + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate filtered algs"); + return rc; + } + + s = session->server_sign_algorithms; + i = filtered_algs; + + /* this walks the server algo list and the supported algo list and creates + a filtered list that includes matches */ + + while(s && *s) { + p = strchr(s, ','); + p_len = p ? (p - s) : (int) strlen(s); + a = supported_algs; + + while(a && *a) { + f = strchr(a, ','); + f_len = f ? (f - a) : (int) strlen(a); + + if(f_len == p_len && memcmp(a, s, p_len) == 0) { + + if(i != filtered_algs) { + memcpy(i, ",", 1); + i += 1; + } + + memcpy(i, s, p_len); + i += p_len; + } + + a = f ? (f + 1) : NULL; + } + + s = p ? (p + 1) : NULL; + } + + filtered_algs[i - filtered_algs] = '\0'; + + if(session->sign_algo_prefs) { + s = session->sign_algo_prefs; + } + else { + s = supported_algs; + } + + /* now that we have the possible supported algos, match based on the prefs + or what is supported by the crypto backend, look for a match */ + + while(s && *s && !match) { + p = strchr(s, ','); + p_len = p ? (p - s) : (int) strlen(s); + a = filtered_algs; + + while(a && *a && !match) { + f = strchr(a, ','); + f_len = f ? (f - a) : (int) strlen(a); + + if(f_len == p_len && memcmp(a, s, p_len) == 0) { + /* found a match, upgrade key method */ + match = s; + match_len = p_len; + } + else { + a = f ? (f + 1) : NULL; + } + } + + s = p ? (p + 1) : NULL; + } + + if(match != NULL) { + if(*key_method) + LIBSSH2_FREE(session, *key_method); + + *key_method = LIBSSH2_ALLOC(session, match_len); + if(key_method) { + memcpy(*key_method, match, match_len); + *key_method_len = match_len; + } + else { + *key_method_len = 0; + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate key method upgrade"); + } + } + else { + /* no match was found */ + rc = _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No signing signature matched"); + } + + if(filtered_algs) + LIBSSH2_FREE(session, filtered_algs); + + return rc; +} + +int +_libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const unsigned char *pubkeydata, + unsigned long pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC + ((*sign_callback)), + void *abstract) +{ + unsigned char reply_codes[4] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_PK_OK, 0 + }; + int rc; + unsigned char *s; + int auth_attempts = 0; + + retry_auth: + auth_attempts++; + + if(session->userauth_pblc_state == libssh2_NB_state_idle) { + + /* + * The call to _libssh2_ntohu32 later relies on pubkeydata having at + * least 4 valid bytes containing the length of the method name. + */ + if(pubkeydata_len < 4) + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid public key, too short"); + + /* Zero the whole thing out */ + memset(&session->userauth_pblc_packet_requirev_state, 0, + sizeof(session->userauth_pblc_packet_requirev_state)); + + /* + * As an optimisation, userauth_publickey_fromfile reuses a + * previously allocated copy of the method name to avoid an extra + * allocation/free. + * For other uses, we allocate and populate it here. + */ + if(!session->userauth_pblc_method) { + session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata); + + if(session->userauth_pblc_method_len > pubkeydata_len - 4) + /* the method length simply cannot be longer than the entire + passed in data, so we use this to detect crazy input + data */ + return _libssh2_error(session, + LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid public key"); + + session->userauth_pblc_method = + LIBSSH2_ALLOC(session, session->userauth_pblc_method_len); + if(!session->userauth_pblc_method) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for public key data"); + } + memcpy(session->userauth_pblc_method, pubkeydata + 4, + session->userauth_pblc_method_len); + } + + /* upgrade key signing algo if it is supported and + * it is our first auth attempt, otherwise fallback to + * the key default algo */ + if(auth_attempts == 1) { + rc = _libssh2_key_sign_algorithm(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len); + + if(rc) + return rc; + } + + if(session->userauth_pblc_method_len && + session->userauth_pblc_method) { + _libssh2_debug(session, + LIBSSH2_TRACE_KEX, + "Signing using %.*s", + session->userauth_pblc_method_len, + session->userauth_pblc_method); + } + + /* + * 45 = packet_type(1) + username_len(4) + servicename_len(4) + + * service_name(14)"ssh-connection" + authmethod_len(4) + + * authmethod(9)"publickey" + sig_included(1)'\0' + algmethod_len(4) + + * publickey_len(4) + */ + session->userauth_pblc_packet_len = + username_len + session->userauth_pblc_method_len + pubkeydata_len + + 45; + + /* + * Preallocate space for an overall length, method name again, and the + * signature, which won't be any larger than the size of the + * publickeydata itself. + * + * Note that the 'pubkeydata_len' extra bytes allocated here will not + * be used in this first send, but will be used in the later one where + * this same allocation is re-used. + */ + s = session->userauth_pblc_packet = + LIBSSH2_ALLOC(session, + session->userauth_pblc_packet_len + 4 + + (4 + session->userauth_pblc_method_len) + + (4 + pubkeydata_len)); + if(!session->userauth_pblc_packet) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory"); + } + + *s++ = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", 14); + _libssh2_store_str(&s, "publickey", 9); + + session->userauth_pblc_b = s; + /* Not sending signature with *this* packet */ + *s++ = 0; + + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + _libssh2_store_str(&s, (const char *)pubkeydata, pubkeydata_len); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting publickey authentication"); + + session->userauth_pblc_state = libssh2_NB_state_created; + } + + if(session->userauth_pblc_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_pblc_packet, + session->userauth_pblc_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + else if(rc) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-publickey request"); + } + + session->userauth_pblc_state = libssh2_NB_state_sent; + } + + if(session->userauth_pblc_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pblc_data, + &session->userauth_pblc_data_len, 0, + NULL, 0, + &session-> + userauth_pblc_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc || (session->userauth_pblc_data_len < 1)) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Waiting for USERAUTH response"); + } + + if(session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Pubkey authentication prematurely successful"); + /* + * God help any SSH server that allows an UNVERIFIED + * public key to validate the user + */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pblc_state = libssh2_NB_state_idle; + return 0; + } + + if(session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_FAILURE) { + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Username/PublicKey combination invalid"); + } + + /* Semi-Success! */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + + *session->userauth_pblc_b = 0x01; + session->userauth_pblc_state = libssh2_NB_state_sent1; + } + + if(session->userauth_pblc_state == libssh2_NB_state_sent1) { + unsigned char *buf; + unsigned char *sig; + size_t sig_len; + + s = buf = LIBSSH2_ALLOC(session, 4 + session->session_id_len + + session->userauth_pblc_packet_len); + if(!buf) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "userauth-publickey signed data"); + } + + _libssh2_store_str(&s, (const char *)session->session_id, + session->session_id_len); + + memcpy(s, session->userauth_pblc_packet, + session->userauth_pblc_packet_len); + s += session->userauth_pblc_packet_len; + + rc = sign_callback(session, &sig, &sig_len, buf, s - buf, abstract); + LIBSSH2_FREE(session, buf); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc == LIBSSH2_ERROR_ALGO_UNSUPPORTED && auth_attempts == 1) { + /* try again with the default key algo */ + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + + rc = LIBSSH2_ERROR_NONE; + goto retry_auth; + } + else if(rc) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Callback returned error"); + } + + /* + * If this function was restarted, pubkeydata_len might still be 0 + * which will cause an unnecessary but harmless realloc here. + */ + if(sig_len > pubkeydata_len) { + unsigned char *newpacket; + /* Should *NEVER* happen, but...well.. better safe than sorry */ + newpacket = LIBSSH2_REALLOC(session, + session->userauth_pblc_packet, + session->userauth_pblc_packet_len + 4 + + (4 + session->userauth_pblc_method_len) + + (4 + sig_len)); /* PK sigblob */ + if(!newpacket) { + LIBSSH2_FREE(session, sig); + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating additional space for " + "userauth-publickey packet"); + } + session->userauth_pblc_packet = newpacket; + } + + s = session->userauth_pblc_packet + session->userauth_pblc_packet_len; + session->userauth_pblc_b = NULL; + + session->userauth_pblc_method_len = + plain_method_len((const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + + if(strncmp((const char *)session->userauth_pblc_method, + "sk-ecdsa-sha2-nistp256@openssh.com", + session->userauth_pblc_method_len) == 0 || + strncmp((const char *)session->userauth_pblc_method, + "sk-ssh-ed25519@openssh.com", + session->userauth_pblc_method_len) == 0) { + _libssh2_store_u32(&s, + 4 + session->userauth_pblc_method_len + + sig_len); + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + memcpy(s, sig, sig_len); + s += sig_len; + } + else { + _libssh2_store_u32(&s, + 4 + session->userauth_pblc_method_len + 4 + + sig_len); + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + _libssh2_store_str(&s, (const char *)sig, sig_len); + } + + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + + LIBSSH2_FREE(session, sig); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting publickey authentication -- phase 2"); + + session->userauth_pblc_s = s; + session->userauth_pblc_state = libssh2_NB_state_sent2; + } + + if(session->userauth_pblc_state == libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, session->userauth_pblc_packet, + session->userauth_pblc_s - + session->userauth_pblc_packet, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-publickey request"); + } + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + + session->userauth_pblc_state = libssh2_NB_state_sent3; + } + + /* PK_OK is no longer valid */ + reply_codes[2] = 0; + + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pblc_data, + &session->userauth_pblc_data_len, 0, NULL, 0, + &session->userauth_pblc_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + } + else if(rc || session->userauth_pblc_data_len < 1) { + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Waiting for publickey USERAUTH response"); + } + + if(session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Publickey authentication successful"); + /* We are us and we've proved it. */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pblc_state = libssh2_NB_state_idle; + return 0; + } + + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid signature for supplied public key, or bad " + "username/public key combination"); +} + + /* + * userauth_publickey_frommemory + * Authenticate using a keypair from memory + */ +static int +userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickeydata, + size_t publickeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + struct privkey_file privkey_file; + void *abstract = &privkey_file; + int rc; + + privkey_file.filename = privatekeydata; + privkey_file.passphrase = passphrase; + + if(session->userauth_pblc_state == libssh2_NB_state_idle) { + if(publickeydata_len && publickeydata) { + rc = memory_read_publickey(session, &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + publickeydata, publickeydata_len); + if(rc) + return rc; + } + else if(privatekeydata_len && privatekeydata) { + /* Compute public key from private key. */ + rc = _libssh2_pub_priv_keyfilememory(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + privatekeydata, privatekeydata_len, + passphrase); + if(rc) + return rc; + } + else { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public and private key."); + } + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + sign_frommemory, &abstract); + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + return rc; +} + +/* + * userauth_publickey_fromfile + * Authenticate using a keypair found in the named files + */ +static int +userauth_publickey_fromfile(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + struct privkey_file privkey_file; + void *abstract = &privkey_file; + int rc; + + privkey_file.filename = privatekey; + privkey_file.passphrase = passphrase; + + if(session->userauth_pblc_state == libssh2_NB_state_idle) { + if(publickey) { + rc = file_read_publickey(session, &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, publickey); + if(rc) + return rc; + } + else { + /* Compute public key from private key. */ + rc = _libssh2_pub_priv_keyfile(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase); + + /* _libssh2_pub_priv_keyfile calls _libssh2_error() */ + if(rc) + return rc; + } + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + sign_fromfile, &abstract); + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + return rc; +} + +/* libssh2_userauth_publickey_frommemory + * Authenticate using a keypair from memory + */ +LIBSSH2_API int +libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *user, + size_t user_len, + const char *publickeyfiledata, + size_t publickeyfiledata_len, + const char *privatekeyfiledata, + size_t privatekeyfiledata_len, + const char *passphrase) +{ + int rc; + + if(NULL == passphrase) + /* if given a NULL pointer, make it point to a zero-length + string to save us from having to check this all over */ + passphrase = ""; + + BLOCK_ADJUST(rc, session, + userauth_publickey_frommemory(session, user, user_len, + publickeyfiledata, + publickeyfiledata_len, + privatekeyfiledata, + privatekeyfiledata_len, + passphrase)); + return rc; +} + +/* libssh2_userauth_publickey_fromfile_ex + * Authenticate using a keypair found in the named files + */ +LIBSSH2_API int +libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + int rc; + + if(NULL == passphrase) + /* if given a NULL pointer, make it point to a zero-length + string to save us from having to check this all over */ + passphrase = ""; + + BLOCK_ADJUST(rc, session, + userauth_publickey_fromfile(session, user, user_len, + publickey, privatekey, + passphrase)); + return rc; +} + +/* libssh2_userauth_publickey_ex + * Authenticate using an external callback function + */ +LIBSSH2_API int +libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *user, + const unsigned char *pubkeydata, + size_t pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC + ((*sign_callback)), + void **abstract) +{ + int rc; + + if(!session) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, session, + _libssh2_userauth_publickey(session, user, strlen(user), + pubkeydata, pubkeydata_len, + sign_callback, abstract)); + return rc; +} + + + +/* + * userauth_keyboard_interactive + * + * Authenticate using a challenge-response authentication + */ +static int +userauth_keyboard_interactive(LIBSSH2_SESSION * session, + const char *username, + unsigned int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC + ((*response_callback))) +{ + unsigned char *s; + + int rc; + + static const unsigned char reply_codes[4] = { + SSH_MSG_USERAUTH_SUCCESS, + SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 + }; + unsigned int i; + + if(session->userauth_kybd_state == libssh2_NB_state_idle) { + session->userauth_kybd_auth_name = NULL; + session->userauth_kybd_auth_instruction = NULL; + session->userauth_kybd_num_prompts = 0; + session->userauth_kybd_auth_failure = 1; + session->userauth_kybd_prompts = NULL; + session->userauth_kybd_responses = NULL; + + /* Zero the whole thing out */ + memset(&session->userauth_kybd_packet_requirev_state, 0, + sizeof(session->userauth_kybd_packet_requirev_state)); + + session->userauth_kybd_packet_len = + 1 /* byte SSH_MSG_USERAUTH_REQUEST */ + + 4 + username_len /* string user name (ISO-10646 UTF-8, as + defined in [RFC-3629]) */ + + 4 + 14 /* string service name (US-ASCII) */ + + 4 + 20 /* string "keyboard-interactive" (US-ASCII) */ + + 4 + 0 /* string language tag (as defined in + [RFC-3066]) */ + + 4 + 0 /* string submethods (ISO-10646 UTF-8) */ + ; + + session->userauth_kybd_data = s = + LIBSSH2_ALLOC(session, session->userauth_kybd_packet_len); + if(!s) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive authentication"); + } + + *s++ = SSH_MSG_USERAUTH_REQUEST; + + /* user name */ + _libssh2_store_str(&s, username, username_len); + + /* service name */ + _libssh2_store_str(&s, "ssh-connection", sizeof("ssh-connection") - 1); + + /* "keyboard-interactive" */ + _libssh2_store_str(&s, "keyboard-interactive", + sizeof("keyboard-interactive") - 1); + /* language tag */ + _libssh2_store_u32(&s, 0); + + /* submethods */ + _libssh2_store_u32(&s, 0); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting keyboard-interactive authentication"); + + session->userauth_kybd_state = libssh2_NB_state_created; + } + + if(session->userauth_kybd_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_kybd_data, + session->userauth_kybd_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc) { + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send keyboard-interactive" + " request"); + } + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + + session->userauth_kybd_state = libssh2_NB_state_sent; + } + + for(;;) { + if(session->userauth_kybd_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_kybd_data, + &session->userauth_kybd_data_len, + 0, NULL, 0, + &session-> + userauth_kybd_packet_requirev_state); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } + else if(rc || session->userauth_kybd_data_len < 1) { + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Waiting for keyboard " + "USERAUTH response"); + } + + if(session->userauth_kybd_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive " + "authentication successful"); + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_kybd_state = libssh2_NB_state_idle; + return 0; + } + + if(session->userauth_kybd_data[0] == SSH_MSG_USERAUTH_FAILURE) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive authentication failed"); + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed " + "(keyboard-interactive)"); + } + + /* server requested PAM-like conversation */ + if(userauth_keyboard_interactive_decode_info_request(session) + < 0) { + goto cleanup; + } + + response_callback((const char *)session->userauth_kybd_auth_name, + session->userauth_kybd_auth_name_len, + (const char *) + session->userauth_kybd_auth_instruction, + session->userauth_kybd_auth_instruction_len, + session->userauth_kybd_num_prompts, + session->userauth_kybd_prompts, + session->userauth_kybd_responses, + &session->abstract); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive response callback function" + " invoked"); + + session->userauth_kybd_packet_len = + 1 /* byte SSH_MSG_USERAUTH_INFO_RESPONSE */ + + 4 /* int num-responses */ + ; + + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + /* string response[1] (ISO-10646 UTF-8) */ + if(session->userauth_kybd_responses[i].length <= + (SIZE_MAX - 4 - session->userauth_kybd_packet_len) ) { + session->userauth_kybd_packet_len += + 4 + session->userauth_kybd_responses[i].length; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for keyboard-" + "interactive response packet"); + goto cleanup; + } + } + + /* A new userauth_kybd_data area is to be allocated, free the + former one. */ + LIBSSH2_FREE(session, session->userauth_kybd_data); + + session->userauth_kybd_data = s = + LIBSSH2_ALLOC(session, session->userauth_kybd_packet_len); + if(!s) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for keyboard-" + "interactive response packet"); + goto cleanup; + } + + *s = SSH_MSG_USERAUTH_INFO_RESPONSE; + s++; + _libssh2_store_u32(&s, session->userauth_kybd_num_prompts); + + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + _libssh2_store_str(&s, + session->userauth_kybd_responses[i].text, + session->userauth_kybd_responses[i].length); + } + + session->userauth_kybd_state = libssh2_NB_state_sent1; + } + + if(session->userauth_kybd_state == libssh2_NB_state_sent1) { + rc = _libssh2_transport_send(session, session->userauth_kybd_data, + session->userauth_kybd_packet_len, + NULL, 0); + if(rc == LIBSSH2_ERROR_EAGAIN) + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + if(rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-keyboard-interactive" + " request"); + goto cleanup; + } + + session->userauth_kybd_auth_failure = 0; + } + + cleanup: + /* + * It's safe to clean all the data here, because unallocated pointers + * are filled by zeroes + */ + + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + + if(session->userauth_kybd_prompts) { + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + LIBSSH2_FREE(session, session->userauth_kybd_prompts[i].text); + session->userauth_kybd_prompts[i].text = NULL; + } + } + + if(session->userauth_kybd_responses) { + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + LIBSSH2_FREE(session, + session->userauth_kybd_responses[i].text); + session->userauth_kybd_responses[i].text = NULL; + } + } + + if(session->userauth_kybd_prompts) { + LIBSSH2_FREE(session, session->userauth_kybd_prompts); + session->userauth_kybd_prompts = NULL; + } + if(session->userauth_kybd_responses) { + LIBSSH2_FREE(session, session->userauth_kybd_responses); + session->userauth_kybd_responses = NULL; + } + if(session->userauth_kybd_auth_name) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_name); + session->userauth_kybd_auth_name = NULL; + } + if(session->userauth_kybd_auth_instruction) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction); + session->userauth_kybd_auth_instruction = NULL; + } + + if(session->userauth_kybd_auth_failure) { + session->userauth_kybd_state = libssh2_NB_state_idle; + return -1; + } + + session->userauth_kybd_state = libssh2_NB_state_sent; + } +} + +/* + * libssh2_userauth_keyboard_interactive_ex + * + * Authenticate using a challenge-response authentication + */ +LIBSSH2_API int +libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC + ((*response_callback))) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_keyboard_interactive(session, user, user_len, + response_callback)); + return rc; +} + +/* libssh2_userauth_publickey_sk + * Authenticate using an external callback function + */ +LIBSSH2_API int +libssh2_userauth_publickey_sk(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase, + LIBSSH2_USERAUTH_SK_SIGN_FUNC + ((*sign_callback)), + void **abstract) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + LIBSSH2_PRIVKEY_SK sk_info = { 0 }; + void *sign_abstract = &sk_info; + int rc; + + sk_info.sign_callback = sign_callback; + sk_info.orig_abstract = abstract; + + if(privatekeydata_len && privatekeydata) { + + if(_libssh2_sk_pub_keyfilememory(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + &(sk_info.algorithm), + &(sk_info.flags), + &(sk_info.application), + &(sk_info.key_handle), + &(sk_info.handle_len), + privatekeydata, privatekeydata_len, + passphrase)) + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key."); + } + else { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public and private key."); + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + libssh2_sign_sk, &sign_abstract); + + while(rc == LIBSSH2_ERROR_EAGAIN) { + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + libssh2_sign_sk, &sign_abstract); + } + + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + if(sk_info.application) { + LIBSSH2_FREE(session, (void *)sk_info.application); + } + + return rc; +} +#endif diff --git a/lib/libssh2/userauth.h b/lib/libssh2/userauth.h new file mode 100644 index 0000000..c493353 --- /dev/null +++ b/lib/libssh2/userauth.h @@ -0,0 +1,53 @@ +#if defined(ESP32) +#ifndef __LIBSSH2_USERAUTH_H +#define __LIBSSH2_USERAUTH_H +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2009-2010 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +int +_libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const unsigned char *pubkeydata, + unsigned long pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC + ((*sign_callback)), + void *abstract); + +#endif /* __LIBSSH2_USERAUTH_H */ +#endif diff --git a/lib/libssh2/userauth_kbd_packet.c b/lib/libssh2/userauth_kbd_packet.c new file mode 100644 index 0000000..9997886 --- /dev/null +++ b/lib/libssh2/userauth_kbd_packet.c @@ -0,0 +1,164 @@ +#if defined(ESP32) +/* Copyright (c) 2022, Xaver Loppenstedt + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "userauth_kbd_packet.h" + +int userauth_keyboard_interactive_decode_info_request(LIBSSH2_SESSION *session) +{ + unsigned char *language_tag; + size_t language_tag_len; + unsigned int i; + unsigned char packet_type; + + struct string_buf decoded; + + decoded.data = session->userauth_kybd_data; + decoded.dataptr = session->userauth_kybd_data; + decoded.len = session->userauth_kybd_data_len; + + if(session->userauth_kybd_data_len < 17) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "userauth keyboard data buffer too small " + "to get length"); + return -1; + } + + /* byte SSH_MSG_USERAUTH_INFO_REQUEST */ + _libssh2_get_byte(&decoded, &packet_type); + + /* string name (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_auth_name, + &session->userauth_kybd_auth_name_len) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'name' " + "request field"); + return -1; + } + + /* string instruction (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_auth_instruction, + &session->userauth_kybd_auth_instruction_len) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'instruction' " + "request field"); + return -1; + } + + /* string language tag (as defined in [RFC-3066]) */ + if(_libssh2_get_string(&decoded, &language_tag, + &language_tag_len) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'language tag' " + "request field"); + return -1; + } + + /* int num-prompts */ + if(_libssh2_get_u32(&decoded, &session->userauth_kybd_num_prompts) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Unable to decode " + "keyboard-interactive number of keyboard prompts"); + return -1; + } + + if(session->userauth_kybd_num_prompts > 100) { + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Too many replies for " + "keyboard-interactive prompts"); + return -1; + } + + if(session->userauth_kybd_num_prompts == 0) { + return 0; + } + + session->userauth_kybd_prompts = + LIBSSH2_CALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * + session->userauth_kybd_num_prompts); + if(!session->userauth_kybd_prompts) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive prompts array"); + return -1; + } + + session->userauth_kybd_responses = + LIBSSH2_CALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * + session->userauth_kybd_num_prompts); + if(!session->userauth_kybd_responses) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive responses array"); + return -1; + } + + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + /* string prompt[1] (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_prompts[i].text, + &session->userauth_kybd_prompts[i].length) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive prompt message"); + return -1; + } + + /* boolean echo[1] */ + if(_libssh2_get_boolean(&decoded, + &session->userauth_kybd_prompts[i].echo) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Unable to decode " + "user auth keyboard prompt echo"); + return -1; + } + } + + return 0; +} +#endif diff --git a/lib/libssh2/userauth_kbd_packet.h b/lib/libssh2/userauth_kbd_packet.h new file mode 100644 index 0000000..2435743 --- /dev/null +++ b/lib/libssh2/userauth_kbd_packet.h @@ -0,0 +1,45 @@ +#if defined(ESP32) +/* Copyright (c) 2022, Xaver Loppenstedt + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef __LIBSSH2_USERAUTH_KBD_PARSE_H +#define __LIBSSH2_USERAUTH_KBD_PARSE_H + +int userauth_keyboard_interactive_decode_info_request(LIBSSH2_SESSION *); + +#endif /* __LIBSSH2_USERAUTH_KBD_PARSE_H */ +#endif diff --git a/lib/missing/slipif.c b/lib/missing/slipif.c new file mode 100644 index 0000000..7b46848 --- /dev/null +++ b/lib/missing/slipif.c @@ -0,0 +1,572 @@ +/** + * @file + * SLIP Interface + * + */ + +/* + * Copyright (c) 2001-2004 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is built upon the file: src/arch/rtxc/netif/sioslip.c + * + * Author: Magnus Ivarsson + * Simon Goldschmidt + */ + + +/** + * @defgroup slipif SLIP + * @ingroup netifs + * + * This is an arch independent SLIP netif. The specific serial hooks must be + * provided by another file. They are sio_open, sio_read/sio_tryread and sio_send + * + * Usage: This netif can be used in three ways: + * 1. For NO_SYS==0, an RX thread can be used which blocks on sio_read() + * until data is received. + * 2. In your main loop, call slipif_poll() to check for new RX bytes, + * completed packets are fed into netif->input(). + * 3. Call slipif_received_byte[s]() from your serial RX ISR and + * slipif_process_rxqueue() from your main loop. ISR level decodes + * packets and puts completed packets on a queue which is fed into + * the stack from the main loop (needs SYS_LIGHTWEIGHT_PROT for + * pbuf_alloc to work on ISR level!). + * + */ + +#include "slipif.h" +#include "lwip/opt.h" + +#include "lwip/def.h" +#include "lwip/pbuf.h" +#include "lwip/stats.h" +#include "lwip/snmp.h" +#include "lwip/sys.h" +#include "lwip/sio.h" + + +void* sio_open (u8_t sid) +{ + return 0; +} +u32_t sio_read (sio_fd_t fd, u8_t *data, u32_t len) +{ + return 0; +} +u32_t sio_tryread (sio_fd_t fd, u8_t *data, u32_t len) +{ + return 0; +} +void sio_send (u8_t c, void *s) {} +#define SLIP_END 0xC0 /* 0300: start and end of every packet */ +#define SLIP_ESC 0xDB /* 0333: escape start (one byte escaped data follows) */ +#define SLIP_ESC_END 0xDC /* 0334: following escape: original byte is 0xC0 (END) */ +#define SLIP_ESC_ESC 0xDD /* 0335: following escape: original byte is 0xDB (ESC) */ + +/** Maximum packet size that is received by this netif */ +#ifndef SLIP_MAX_SIZE +#define SLIP_MAX_SIZE 1500 +#endif + +/** Define this to the interface speed for SNMP + * (sio_fd is the sio_fd_t returned by sio_open). + * The default value of zero means 'unknown'. + */ +#ifndef SLIP_SIO_SPEED +#define SLIP_SIO_SPEED(sio_fd) 0 +#endif + +enum slipif_recv_state { + SLIP_RECV_NORMAL, + SLIP_RECV_ESCAPE +}; + +struct slipif_priv { + sio_fd_t sd; + /* q is the whole pbuf chain for a packet, p is the current pbuf in the chain */ + struct pbuf *p, *q; + u8_t state; + u16_t i, recved; +#if SLIP_RX_FROM_ISR + struct pbuf *rxpackets; +#endif +}; + +/** + * Send a pbuf doing the necessary SLIP encapsulation + * + * Uses the serial layer's sio_send() + * + * @param netif the lwip network interface structure for this slipif + * @param p the pbuf chain packet to send + * @return always returns ERR_OK since the serial layer does not provide return values + */ +static err_t +slipif_output(struct netif *netif, struct pbuf *p) +{ + struct slipif_priv *priv; + struct pbuf *q; + u16_t i; + u8_t c; + + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + LWIP_ASSERT("p != NULL", (p != NULL)); + + LWIP_DEBUGF(SLIP_DEBUG, ("slipif_output: sending %"U16_F" bytes\n", p->tot_len)); + priv = (struct slipif_priv *)netif->state; + + /* Send pbuf out on the serial I/O device. */ + /* Start with packet delimiter. */ + sio_send(SLIP_END, priv->sd); + + for (q = p; q != NULL; q = q->next) { + for (i = 0; i < q->len; i++) { + c = ((u8_t *)q->payload)[i]; + switch (c) { + case SLIP_END: + /* need to escape this byte (0xC0 -> 0xDB, 0xDC) */ + sio_send(SLIP_ESC, priv->sd); + sio_send(SLIP_ESC_END, priv->sd); + break; + case SLIP_ESC: + /* need to escape this byte (0xDB -> 0xDB, 0xDD) */ + sio_send(SLIP_ESC, priv->sd); + sio_send(SLIP_ESC_ESC, priv->sd); + break; + default: + /* normal byte - no need for escaping */ + sio_send(c, priv->sd); + break; + } + } + } + /* End with packet delimiter. */ + sio_send(SLIP_END, priv->sd); + return ERR_OK; +} + +#if LWIP_IPV4 +/** + * Send a pbuf doing the necessary SLIP encapsulation + * + * Uses the serial layer's sio_send() + * + * @param netif the lwip network interface structure for this slipif + * @param p the pbuf chain packet to send + * @param ipaddr the ip address to send the packet to (not used for slipif) + * @return always returns ERR_OK since the serial layer does not provide return values + */ +static err_t +slipif_output_v4(struct netif *netif, struct pbuf *p, const ip4_addr_t *ipaddr) +{ + LWIP_UNUSED_ARG(ipaddr); + return slipif_output(netif, p); +} +#endif /* LWIP_IPV4 */ + +#if LWIP_IPV6 +/** + * Send a pbuf doing the necessary SLIP encapsulation + * + * Uses the serial layer's sio_send() + * + * @param netif the lwip network interface structure for this slipif + * @param p the pbuf chain packet to send + * @param ipaddr the ip address to send the packet to (not used for slipif) + * @return always returns ERR_OK since the serial layer does not provide return values + */ +static err_t +slipif_output_v6(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr) +{ + LWIP_UNUSED_ARG(ipaddr); + return slipif_output(netif, p); +} +#endif /* LWIP_IPV6 */ + +/** + * Handle the incoming SLIP stream character by character + * + * @param netif the lwip network interface structure for this slipif + * @param c received character (multiple calls to this function will + * return a complete packet, NULL is returned before - used for polling) + * @return The IP packet when SLIP_END is received + */ +static struct pbuf * +slipif_rxbyte(struct netif *netif, u8_t c) +{ + struct slipif_priv *priv; + struct pbuf *t; + + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + + priv = (struct slipif_priv *)netif->state; + + switch (priv->state) { + case SLIP_RECV_NORMAL: + switch (c) { + case SLIP_END: + if (priv->recved > 0) { + /* Received whole packet. */ + /* Trim the pbuf to the size of the received packet. */ + pbuf_realloc(priv->q, priv->recved); + + LINK_STATS_INC(link.recv); + + LWIP_DEBUGF(SLIP_DEBUG, ("slipif: Got packet (%"U16_F" bytes)\n", priv->recved)); + t = priv->q; + priv->p = priv->q = NULL; + priv->i = priv->recved = 0; + return t; + } + return NULL; + case SLIP_ESC: + priv->state = SLIP_RECV_ESCAPE; + return NULL; + default: + break; + } /* end switch (c) */ + break; + case SLIP_RECV_ESCAPE: + /* un-escape END or ESC bytes, leave other bytes + (although that would be a protocol error) */ + switch (c) { + case SLIP_ESC_END: + c = SLIP_END; + break; + case SLIP_ESC_ESC: + c = SLIP_ESC; + break; + default: + break; + } + priv->state = SLIP_RECV_NORMAL; + break; + default: + break; + } /* end switch (priv->state) */ + + /* byte received, packet not yet completely received */ + if (priv->p == NULL) { + /* allocate a new pbuf */ + LWIP_DEBUGF(SLIP_DEBUG, ("slipif_input: alloc\n")); + priv->p = pbuf_alloc(PBUF_LINK, (PBUF_POOL_BUFSIZE - PBUF_LINK_HLEN - PBUF_LINK_ENCAPSULATION_HLEN), PBUF_POOL); + + if (priv->p == NULL) { + LINK_STATS_INC(link.drop); + LWIP_DEBUGF(SLIP_DEBUG, ("slipif_input: no new pbuf! (DROP)\n")); + /* don't process any further since we got no pbuf to receive to */ + return NULL; + } + + if (priv->q != NULL) { + /* 'chain' the pbuf to the existing chain */ + pbuf_cat(priv->q, priv->p); + } else { + /* p is the first pbuf in the chain */ + priv->q = priv->p; + } + } + + /* this automatically drops bytes if > SLIP_MAX_SIZE */ + if ((priv->p != NULL) && (priv->recved <= SLIP_MAX_SIZE)) { + ((u8_t *)priv->p->payload)[priv->i] = c; + priv->recved++; + priv->i++; + if (priv->i >= priv->p->len) { + /* on to the next pbuf */ + priv->i = 0; + if (priv->p->next != NULL && priv->p->next->len > 0) { + /* p is a chain, on to the next in the chain */ + priv->p = priv->p->next; + } else { + /* p is a single pbuf, set it to NULL so next time a new + * pbuf is allocated */ + priv->p = NULL; + } + } + } + return NULL; +} + +/** Like slipif_rxbyte, but passes completed packets to netif->input + * + * @param netif The lwip network interface structure for this slipif + * @param c received character + */ +static void +slipif_rxbyte_input(struct netif *netif, u8_t c) +{ + struct pbuf *p; + p = slipif_rxbyte(netif, c); + if (p != NULL) { + if (netif->input(p, netif) != ERR_OK) { + pbuf_free(p); + } + } +} + +#if SLIP_USE_RX_THREAD +/** + * The SLIP input thread. + * + * Feed the IP layer with incoming packets + * + * @param nf the lwip network interface structure for this slipif + */ +static void +slipif_loop_thread(void *nf) +{ + u8_t c; + struct netif *netif = (struct netif *)nf; + struct slipif_priv *priv = (struct slipif_priv *)netif->state; + + while (1) { + if (sio_read(priv->sd, &c, 1) > 0) { + slipif_rxbyte_input(netif, c); + } + } +} +#endif /* SLIP_USE_RX_THREAD */ + +/** + * @ingroup slipif + * SLIP netif initialization + * + * Call the arch specific sio_open and remember + * the opened device in the state field of the netif. + * + * @param netif the lwip network interface structure for this slipif + * @return ERR_OK if serial line could be opened, + * ERR_MEM if no memory could be allocated, + * ERR_IF is serial line couldn't be opened + * + * @note If netif->state is interpreted as an u8_t serial port number. + * + */ +err_t +slipif_init(struct netif *netif) +{ + struct slipif_priv *priv; + u8_t sio_num; + + LWIP_ASSERT("slipif needs an input callback", netif->input != NULL); + + /* netif->state contains serial port number */ + sio_num = LWIP_PTR_NUMERIC_CAST(u8_t, netif->state); + + LWIP_DEBUGF(SLIP_DEBUG, ("slipif_init: netif->num=%"U16_F"\n", (u16_t)sio_num)); + + /* Allocate private data */ + priv = (struct slipif_priv *)mem_malloc(sizeof(struct slipif_priv)); + if (!priv) { + return ERR_MEM; + } + + netif->name[0] = 's'; + netif->name[1] = 'l'; +#if LWIP_IPV4 + netif->output = slipif_output_v4; +#endif /* LWIP_IPV4 */ +#if LWIP_IPV6 + netif->output_ip6 = slipif_output_v6; +#endif /* LWIP_IPV6 */ + netif->mtu = SLIP_MAX_SIZE; + + /* Try to open the serial port. */ + priv->sd = sio_open(sio_num); + if (!priv->sd) { + /* Opening the serial port failed. */ + mem_free(priv); + return ERR_IF; + } + + /* Initialize private data */ + priv->p = NULL; + priv->q = NULL; + priv->state = SLIP_RECV_NORMAL; + priv->i = 0; + priv->recved = 0; +#if SLIP_RX_FROM_ISR + priv->rxpackets = NULL; +#endif + + netif->state = priv; + + /* initialize the snmp variables and counters inside the struct netif */ + MIB2_INIT_NETIF(netif, snmp_ifType_slip, SLIP_SIO_SPEED(priv->sd)); + +#if SLIP_USE_RX_THREAD + /* Create a thread to poll the serial line. */ + sys_thread_new(SLIPIF_THREAD_NAME, slipif_loop_thread, netif, + SLIPIF_THREAD_STACKSIZE, SLIPIF_THREAD_PRIO); +#endif /* SLIP_USE_RX_THREAD */ + return ERR_OK; +} + +/** + * @ingroup slipif + * Polls the serial device and feeds the IP layer with incoming packets. + * + * @param netif The lwip network interface structure for this slipif + */ +void +slipif_poll(struct netif *netif) +{ + u8_t c; + struct slipif_priv *priv; + + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + + priv = (struct slipif_priv *)netif->state; + + while (sio_tryread(priv->sd, &c, 1) > 0) { + slipif_rxbyte_input(netif, c); + } +} + +#if SLIP_RX_FROM_ISR +/** + * @ingroup slipif + * Feeds the IP layer with incoming packets that were receive + * + * @param netif The lwip network interface structure for this slipif + */ +void +slipif_process_rxqueue(struct netif *netif) +{ + struct slipif_priv *priv; + SYS_ARCH_DECL_PROTECT(old_level); + + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + + priv = (struct slipif_priv *)netif->state; + + SYS_ARCH_PROTECT(old_level); + while (priv->rxpackets != NULL) { + struct pbuf *p = priv->rxpackets; +#if SLIP_RX_QUEUE + /* dequeue packet */ + struct pbuf *q = p; + while ((q->len != q->tot_len) && (q->next != NULL)) { + q = q->next; + } + priv->rxpackets = q->next; + q->next = NULL; +#else /* SLIP_RX_QUEUE */ + priv->rxpackets = NULL; +#endif /* SLIP_RX_QUEUE */ + SYS_ARCH_UNPROTECT(old_level); + if (netif->input(p, netif) != ERR_OK) { + pbuf_free(p); + } + SYS_ARCH_PROTECT(old_level); + } + SYS_ARCH_UNPROTECT(old_level); +} + +/** Like slipif_rxbyte, but queues completed packets. + * + * @param netif The lwip network interface structure for this slipif + * @param data Received serial byte + */ +static void +slipif_rxbyte_enqueue(struct netif *netif, u8_t data) +{ + struct pbuf *p; + struct slipif_priv *priv = (struct slipif_priv *)netif->state; + SYS_ARCH_DECL_PROTECT(old_level); + + p = slipif_rxbyte(netif, data); + if (p != NULL) { + SYS_ARCH_PROTECT(old_level); + if (priv->rxpackets != NULL) { +#if SLIP_RX_QUEUE + /* queue multiple pbufs */ + struct pbuf *q = p; + while (q->next != NULL) { + q = q->next; + } + q->next = p; + } else { +#else /* SLIP_RX_QUEUE */ + pbuf_free(priv->rxpackets); + } + { +#endif /* SLIP_RX_QUEUE */ + priv->rxpackets = p; + } + SYS_ARCH_UNPROTECT(old_level); + } +} + +/** + * @ingroup slipif + * Process a received byte, completed packets are put on a queue that is + * fed into IP through slipif_process_rxqueue(). + * + * This function can be called from ISR if SYS_LIGHTWEIGHT_PROT is enabled. + * + * @param netif The lwip network interface structure for this slipif + * @param data received character + */ +void +slipif_received_byte(struct netif *netif, u8_t data) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + slipif_rxbyte_enqueue(netif, data); +} + +/** + * @ingroup slipif + * Process multiple received byte, completed packets are put on a queue that is + * fed into IP through slipif_process_rxqueue(). + * + * This function can be called from ISR if SYS_LIGHTWEIGHT_PROT is enabled. + * + * @param netif The lwip network interface structure for this slipif + * @param data received character + * @param len Number of received characters + */ +void +slipif_received_bytes(struct netif *netif, u8_t *data, u8_t len) +{ + u8_t i; + u8_t *rxdata = data; + LWIP_ASSERT("netif != NULL", (netif != NULL)); + LWIP_ASSERT("netif->state != NULL", (netif->state != NULL)); + + for (i = 0; i < len; i++, rxdata++) { + slipif_rxbyte_enqueue(netif, *rxdata); + } +} +#endif /* SLIP_RX_FROM_ISR */ diff --git a/lib/missing/slipif.h b/lib/missing/slipif.h new file mode 100644 index 0000000..551e98d --- /dev/null +++ b/lib/missing/slipif.h @@ -0,0 +1,86 @@ +/** + * @file + * + * SLIP netif API + */ + +/* + * Copyright (c) 2001, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ +#ifndef LWIP_HDR_NETIF_SLIPIF_H +#define LWIP_HDR_NETIF_SLIPIF_H + +#include "lwip/opt.h" +#include "lwip/netif.h" + +/** Set this to 1 to start a thread that blocks reading on the serial line + * (using sio_read()). + */ +#ifndef SLIP_USE_RX_THREAD +#define SLIP_USE_RX_THREAD !NO_SYS +#endif + +/** Set this to 1 to enable functions to pass in RX bytes from ISR context. + * If enabled, slipif_received_byte[s]() process incoming bytes and put assembled + * packets on a queue, which is fed into lwIP from slipif_poll(). + * If disabled, slipif_poll() polls the serial line (using sio_tryread()). + */ +#ifndef SLIP_RX_FROM_ISR +#define SLIP_RX_FROM_ISR 0 +#endif + +/** Set this to 1 (default for SLIP_RX_FROM_ISR) to queue incoming packets + * received by slipif_received_byte[s]() as long as PBUF_POOL pbufs are available. + * If disabled, packets will be dropped if more than one packet is received. + */ +#ifndef SLIP_RX_QUEUE +#define SLIP_RX_QUEUE SLIP_RX_FROM_ISR +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +err_t slipif_init(struct netif * netif); +void slipif_poll(struct netif *netif); +#if SLIP_RX_FROM_ISR +void slipif_process_rxqueue(struct netif *netif); +void slipif_received_byte(struct netif *netif, u8_t data); +void slipif_received_bytes(struct netif *netif, u8_t *data, u8_t len); +#endif /* SLIP_RX_FROM_ISR */ + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_HDR_NETIF_SLIPIF_H */ diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..b933135 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp12e] +platform = espressif8266 +board = esp12e +framework = arduino +lib_deps = diff --git a/src/connSettings.ino b/src/connSettings.ino new file mode 100644 index 0000000..aa2c022 --- /dev/null +++ b/src/connSettings.ino @@ -0,0 +1,140 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +ConnSettings::ConnSettings(int flagBitmap) +{ + petscii = (flagBitmap & FLAG_PETSCII) > 0; + telnet = (flagBitmap & FLAG_TELNET) > 0; + echo = (flagBitmap & FLAG_ECHO) > 0; + xonxoff = (flagBitmap & FLAG_XONXOFF) > 0; + rtscts = (flagBitmap & FLAG_RTSCTS) > 0; + secure = (flagBitmap & FLAG_SECURE) > 0; +} + +ConnSettings::ConnSettings(const char *dmodifiers) +{ + petscii=((strchr(dmodifiers,'p')!=null) || (strchr(dmodifiers,'P')!=null)); + telnet=((strchr(dmodifiers,'t')!=null) || (strchr(dmodifiers,'T')!=null)); + echo=((strchr(dmodifiers,'e')!=null) || (strchr(dmodifiers,'E')!=null)); + xonxoff=((strchr(dmodifiers,'x')!=null) || (strchr(dmodifiers,'X')!=null)); + rtscts=((strchr(dmodifiers,'r')!=null) || (strchr(dmodifiers,'R')!=null)); + secure=((strchr(dmodifiers,'s')!=null) || (strchr(dmodifiers,'S')!=null)); +} + +ConnSettings::ConnSettings(String modifiers) : ConnSettings(modifiers.c_str()) +{ +} + +int ConnSettings::getBitmap() +{ + int flagsBitmap = 0; + if(petscii) + flagsBitmap = flagsBitmap | FLAG_PETSCII; + if(telnet) + flagsBitmap = flagsBitmap | FLAG_TELNET; + if(echo) + flagsBitmap = flagsBitmap | FLAG_ECHO; + if(xonxoff) + flagsBitmap = flagsBitmap | FLAG_XONXOFF; + if(rtscts) + flagsBitmap = flagsBitmap | FLAG_RTSCTS; + if(secure) + flagsBitmap = flagsBitmap | FLAG_SECURE; + return flagsBitmap; +} + +int ConnSettings::getBitmap(FlowControlType forceCheck) +{ + int flagsBitmap = getBitmap(); + if(((flagsBitmap & (FLAG_XONXOFF | FLAG_RTSCTS))==0) + &&(forceCheck==FCT_RTSCTS)) + flagsBitmap |= FLAG_RTSCTS; + return flagsBitmap; +} + +String ConnSettings::getFlagString() +{ + String lastOptions =(petscii?"p":""); + lastOptions += (petscii?"p":""); + lastOptions += (telnet?"t":""); + lastOptions += (echo?"e":""); + lastOptions += (xonxoff?"x":""); + lastOptions += (rtscts?"r":""); + lastOptions += (secure?"s":""); + return lastOptions; +} + +void ConnSettings::setFlag(ConnFlag flagMask, boolean newVal) +{ + switch(flagMask) + { + case FLAG_DISCONNECT_ON_EXIT: break; + case FLAG_PETSCII: petscii = newVal; break; + case FLAG_TELNET: telnet = newVal; break; + case FLAG_ECHO: echo = newVal; break; + case FLAG_XONXOFF: xonxoff = newVal; break; + case FLAG_SECURE: secure = newVal; break; + case FLAG_RTSCTS: rtscts = newVal; break; + } +} + +void ConnSettings::IPtoStr(IPAddress *ip, String &str) +{ + if(ip == null) + { + str=""; + return; + } + char temp[20]; + sprintf(temp,"%d.%d.%d.%d",(*ip)[0],(*ip)[1],(*ip)[2],(*ip)[3]); + str = temp; +} + +IPAddress *ConnSettings::parseIP(const char *ipStr) +{ + uint8_t dots[4]; + int dotDex=0; + char *le = (char *)ipStr; + const char *ld = ipStr+strlen(ipStr); + if(strlen(ipStr)<7) + return null; + for(char *e=le;e<=ld;e++) + { + if((*e=='.')||(e==ld)) + { + if(le==e) + break; + *e=0; + String sdot = le; + sdot.trim(); + if((sdot.length()==0)||(dotDex>3)) + { + dotDex=99; + break; + } + dots[dotDex++]=(uint8_t)atoi(sdot.c_str()); + if(e==ld) + le=e; + else + le=e+1; + } + } + if((dotDex!=4)||(*le != 0)) + return null; + return new IPAddress(dots[0],dots[1],dots[2],dots[3]); +} + diff --git a/src/filelog.ino b/src/filelog.ino new file mode 100644 index 0000000..d5e9930 --- /dev/null +++ b/src/filelog.ino @@ -0,0 +1,284 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +static char HD[3]; +static char HDL[17]; + +static unsigned long logStartTime = millis(); +static unsigned long lastLogTime = millis(); +static unsigned long logCurCount = 0; +static LogOutputState logOutputState = LOS_NADA; + +static uint8_t FROMHEXDIGIT(uint8_t a1) +{ + a1 = lc(a1); + if((a1 >= '0')&&(a1 <= '9')) + return a1-'0'; + if((a1 >= 'a')&&(a1 <= 'f')) + return 10 + (a1-'a'); + return 0; +} + +static uint8_t FROMHEX(uint8_t a1, uint8_t a2) +{ + return (FROMHEXDIGIT(a1) * 16) + FROMHEXDIGIT(a2); +} + +static char *FROMHEX(const char *hex, char *s, const size_t len) +{ + int i=0; + for(const char *h=hex;*h != 0 && (*(h+1)!=0) && (i> 4) & 0x0f]; + HD[1] = "0123456789ABCDEF"[a & 0x0f]; + HD[2] = 0; + return HD; +} + +static char *tohex(uint8_t a) +{ + HD[0] = "0123456789abcdef"[(a >> 4) & 0x0f]; + HD[1] = "0123456789abcdef"[a & 0x0f]; + HD[2] = 0; + return HD; +} + +static char *TOHEX(unsigned long a) +{ + for(int i=7;i>=0;i--) + { + HDL[i] = "0123456789ABCDEF"[a & 0x0f]; + a = a >> 4; + } + HDL[8] = 0; + char *H=HDL; + if((strlen(H)>2) && (strstr(H,"00")==H)) + H+=2; + return H; +} + +static char *TOHEX(unsigned int a) +{ + for(int i=3;i>=0;i--) + { + HDL[i] = "0123456789ABCDEF"[a & 0x0f]; + a = a >> 4; + } + HDL[4] = 0; + char *H=HDL; + if((strlen(H)>2) && (strstr(H,"00")==H)) + H+=2; + return H; +} + +static char *TOHEX(int a) +{ + return TOHEX((unsigned int)a); +} + +static char *TOHEX(long a) +{ + return TOHEX((unsigned long)a); +} + +static void logInternalOut(const LogOutputState m, const uint8_t c) +{ + if(logFileOpen) + { + if((m != logOutputState) + ||(++logCurCount > DBG_BYT_CTR) + ||((millis()-lastLogTime)>expectedSerialTime)) + { + logCurCount=0; + + logOutputState = m; + rawLogPrintln(""); + switch(m) + { + case LOS_NADA: + break; + case LOS_SocketIn: + rawLogPrintf("%s SocI: ",TOHEX(millis()-logStartTime)); + break; + case LOS_SocketOut: + rawLogPrintf("%s SocO: ",TOHEX(millis()-logStartTime)); + break; + case LOS_SerialIn: + rawLogPrintf("%s SerI: ",TOHEX(millis()-logStartTime)); + break; + case LOS_SerialOut: + rawLogPrintf("%s SerO: ",TOHEX(millis()-logStartTime)); + break; + } + } + lastLogTime=millis(); + rawLogPrint(TOHEX(c)); + rawLogPrint(" "); + } +} + +static void logSerialOut(const uint8_t c) +{ + logInternalOut(LOS_SerialOut,c); +} + +static void logSocketOut(const uint8_t c) +{ + logInternalOut(LOS_SocketOut,c); +} + +static void logSerialIn(const uint8_t c) +{ + logInternalOut(LOS_SerialIn,c); +} + +static void logSocketIn(const uint8_t c) +{ + logInternalOut(LOS_SocketIn,c); +} + +static void logSocketIn(const uint8_t *c, int n) +{ + if(logFileOpen) + { + for(int i=0;i +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// courtesy Craig Bruce, 1995 + +unsigned char petToAscTable[256] PROGMEM = { +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x14,0x09,0x0d,0x11,0x93,0x0a,0x0e,0x0f, +0x10,0x0b,0x12,0x13,0x08,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, +0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x5b,0x5c,0x5d,0x5e,0x5f, +0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x7b,0x7c,0x7d,0x7e,0x7f, +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, +0x90,0x91,0x92,0x0c,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, +0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, +0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, +0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0xda,0xdb,0xdc,0xdd,0xde,0xdf, +0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, +0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf +}; + +unsigned char ascToPetTable[256] PROGMEM = { +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x14,0x20,0x0a,0x11,0x93,0x0d,0x0e,0x0f, +0x10,0x0b,0x12,0x13,0x08,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, +0x40,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, +0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0x5b,0x5c,0x5d,0x5e,0x5f, +0xc0,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0xdb,0xdc,0xdd,0xde,0xdf, +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, +0x90,0x91,0x92,0x0c,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, +0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, +0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, +0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, +0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, +0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, +0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff +}; + +#define TELNET_BINARY 0 + /** TELNET CODE: echo */ +#define TELNET_ECHO 1 + /** TELNET CODE: echo */ +#define TELNET_LOGOUT 18 + /** TELNET CODE: supress go ahead*/ +#define TELNET_SUPRESS_GO_AHEAD 3 + /** TELNET CODE: sending terminal type*/ +#define TELNET_TERMTYPE 24 + /** TELNET CODE: Negotiate About Window Size.*/ +#define TELNET_NAWS 31 + /** TELNET CODE: Remote Flow Control.*/ +#define TELNET_TOGGLE_FLOW_CONTROL 33 + /** TELNET CODE: Linemode*/ +#define TELNET_LINEMODE 34 + /** TELNET CODE: MSDP protocol*/ +#define TELNET_MSDP 69 + /** TELNET CODE: MSSP Server Status protocol*/ +#define TELNET_MSSP 70 + /** TELNET CODE: text compression, protocol 1*/ +#define TELNET_COMPRESS 85 + /** TELNET CODE: text compression, protocol 2*/ +#define TELNET_COMPRESS2 86 + /** TELNET CODE: MSP SOund protocol*/ +#define TELNET_MSP 90 + /** TELNET CODE: MXP Extended protocol*/ +#define TELNET_MXP 91 + /** TELNET CODE: AARD protocol*/ +#define TELNET_AARD 102 + /** TELNET CODE: End of subnegotiation parameters*/ +#define TELNET_SE 240 + /** TELNET CODE: Are You There*/ +#define TELNET_AYT 246 + /** TELNET CODE: Erase character*/ +#define TELNET_EC 247 + /** TELNET CODE: ATCP protocol*/ +#define TELNET_ATCP 200 + /** TELNET CODE: GMCP protocol*/ +#define TELNET_GMCP 201 + /** TELNET CODE: Indicates that what follows is subnegotiation of the indicated option*/ +#define TELNET_SB 250 + /** TELNET CODE: Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option*/ +#define TELNET_WILL 251 + /** TELNET CODE: Indicates the refusal to perform, or continue performing, the indicated option*/ +#define TELNET_WONT 252 + /** TELNET CODE: Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option*/ +#define TELNET_DO 253 + /** TELNET CODE: 253 doubles as fake ansi telnet code*/ +#define TELNET_ANSI 253 + /** TELNET CODE: Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option.*/ +#define TELNET_DONT 254 + /** TELNET CODE: Indicates that the other party can go ahead and transmit -- I'm done.*/ +#define TELNET_GA 249 + /** TELNET CODE: Indicates that there is nothing to do?*/ +#define TELNET_NOP 241 + /** TELNET CODE: IAC*/ +#define TELNET_IAC 255 + + +uint8_t streamAvailRead(Stream *stream) +{ + int ct=0; + while((stream->available()==0) + &&(ct++)<250) + delay(1); + return stream->read(); +} + +bool handleAsciiIAC(char *c, Stream *stream) +{ + if(*c == 255) + { + *c=streamAvailRead(stream); + logSocketIn(*c); + if(*c==TELNET_IAC) + { + *c = 255; + return true; + } + if(*c==TELNET_WILL) + { + char what=streamAvailRead(stream); + logSocketIn(what); + uint8_t iacDont[] = {TELNET_IAC, TELNET_DONT, what}; + if(what == TELNET_TERMTYPE) + iacDont[1] = TELNET_DO; + for(int i=0;i<3;i++) + logSocketOut(iacDont[i]); + stream->write(iacDont,3); + return false; + } + if(*c==TELNET_DONT) + { + char what=streamAvailRead(stream); + logSocketIn(what); + uint8_t iacWont[] = {TELNET_IAC, TELNET_WONT, what}; + for(int i=0;i<3;i++) + logSocketOut(iacWont[i]); + stream->write(iacWont,3); + return false; + } + if(*c==TELNET_WONT) + { + char what=streamAvailRead(stream); + logSocketIn(what); + return false; + } + if(*c==TELNET_DO) + { + char what=streamAvailRead(stream); + logSocketIn(what); + uint8_t iacWont[] = {TELNET_IAC, TELNET_WONT, what}; + if(what == TELNET_TERMTYPE) + iacWont[1] = TELNET_WILL; + for(int i=0;i<3;i++) + logSocketOut(iacWont[i]); + stream->write(iacWont,3); + return false; + } + if(*c==TELNET_SB) + { + char what=streamAvailRead(stream); + logSocketIn(what); + char lastC=*c; + while(((lastC!=TELNET_IAC)||(*c!=TELNET_SE))&&(*c>=0)) + { + lastC=*c; + *c=streamAvailRead(stream); + logSocketIn(*c); + } + if(what == TELNET_TERMTYPE) + { + int respLen = termType.length() + 6; + uint8_t buf[respLen]; + buf[0]=TELNET_IAC; + buf[1]=TELNET_SB; + buf[2]=TELNET_TERMTYPE; + buf[3]=(uint8_t)0; + sprintf((char *)buf+4,termType.c_str()); + buf[respLen-2]=TELNET_IAC; + buf[respLen-1]=TELNET_SE; + for(int i=0;iwrite(buf,respLen); + return false; + } + } + return false; + } + return true; +} + +bool ansiColorToPetsciiColor(char *c, Stream *stream) +{ + if(*c == 27) + { + *c=streamAvailRead(stream); + logSocketIn(*c); + if(*c=='[') + { + int code1=0; + int code2=-1; + *c=streamAvailRead(stream); + logSocketIn(*c); + while((*c>='0')&&(*c<='9')) + { + code1=(code1*10) + (*c-'0'); + *c=streamAvailRead(stream); + logSocketIn(*c); + } + while(*c==';') + { + *c=streamAvailRead(stream); + logSocketIn(*c); + if((*c>='0')&&(*c<='9')) + code2=0; + while((*c>='0')&&(*c<='9')) + { + code2=(code2*10) + (*c-'0'); + *c=streamAvailRead(stream); + logSocketIn(*c); + } + } + switch(code1) + { + case 0: + // dark... + switch(code2) + { + case -1: + case 0: + *c= 146; // rvs off + return true; + case 30: // black + *c= 155;//144; + return true; + case 31: // red + *c= 28; + return true; + case 32: // green + *c= 30; + return true; + case 33: // yellow + *c= 129; + return true; + case 34: // blue + *c= 31; + return true; + case 35: // purple + *c= 149; + return true; + case 36: // cyan + *c= 152; + return true; + case 37: // white/grey + default: + *c= 155; + return true; + } + break; + case 1: + // light.. + switch(code2) + { + case -1:/* bold */ + *c= 0; + return true; + case 0: + *c= 146; // rvs off + return true; + case 30: // black + *c= 151; + return true; + case 31: // red + *c= 150; + return true; + case 32: // green + *c= 153; + return true; + case 33: // yellow + *c= 158; + return true; + case 34: // blue + *c= 154; + return true; + case 35: // purple + *c= 156; + return true; + case 36: // cyan + *c= 159; + return true; + case 37: // white/grey + default: + *c= 5; + return true; + } + break; + case 2: /*?*/ + case 3: /*?*/ + case 4: /*underline*/ + case 5: /*blink*/ + case 6: /*italics*/ + *c=0; + return true; + case 40: case 41: case 42: case 43: case 44: + case 45: case 46: case 47: case 48: case 49: + *c=18; // rvs on + return true; + } + } + *c = 0; + return true; + } + return false; +} + +char petToAsc(char c) +{ + return pgm_read_byte_near(petToAscTable + (int)c); +} + +bool ascToPet(char *c, Stream *stream) +{ + if(!ansiColorToPetsciiColor(c,stream)) + *c = pgm_read_byte_near(ascToPetTable + (int)(*c)); + return true; +} + +char ascToPetcii(char c) +{ + return pgm_read_byte_near(ascToPetTable + (int)c); +} + diff --git a/src/phonebook.ino b/src/phonebook.ino new file mode 100644 index 0000000..6380793 --- /dev/null +++ b/src/phonebook.ino @@ -0,0 +1,191 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +PhoneBookEntry::PhoneBookEntry(unsigned long phnum, const char *addr, const char *mod, const char *note) +{ + number=phnum; + address = new char[strlen(addr)+1]; + strcpy((char *)address,addr); + modifiers = new char[strlen(mod)+1]; + strcpy((char *)modifiers,mod); + notes = new char[strlen(note)+1]; + strcpy((char *)notes,note); + + if(phonebook == null) + { + phonebook = this; + return; + } + PhoneBookEntry *last = phonebook; + if(last->number > number) + { + phonebook = this; + next = last; + return; + } + while(last->next != null) + { + if(last->next->number > number) + { + next = last->next; + last->next = this; + return; + } + last = last->next; + } + last->next = this; +} + +PhoneBookEntry::~PhoneBookEntry() +{ + if(phonebook == this) + phonebook = next; + else + { + PhoneBookEntry *last = phonebook; + while((last != null) && (last->next != this)) // don't change this! + last = last->next; + if(last != null) + last->next = next; + } + freeCharArray((char **)&address); + freeCharArray((char **)&modifiers); + freeCharArray((char **)¬es); + next=null; +} + +void PhoneBookEntry::loadPhonebook() +{ + clearPhonebook(); + if(SPIFFS.exists("/zphonebook.txt")) + { + File f = SPIFFS.open("/zphonebook.txt", "r"); + while(f.available()>0) + { + String str=""; + char c=f.read(); + while((c != '\n') && (f.available()>0)) + { + str += c; + c=f.read(); + } + int argn=0; + String configArguments[4]; + for(int i=0;i<4;i++) + configArguments[i]=""; + for(int i=0;i9)) + return false; + return true; +} + +PhoneBookEntry *PhoneBookEntry::findPhonebookEntry(long number) +{ + PhoneBookEntry *p = phonebook; + while(p != null) + { + if(p->number == number) + return p; + p=p->next; + } + return null; +} + +PhoneBookEntry *PhoneBookEntry::findPhonebookEntry(String number) +{ + if(!checkPhonebookEntry(number)) + return null; + return findPhonebookEntry(atol(number.c_str())); +} + +void PhoneBookEntry::clearPhonebook() +{ + PhoneBookEntry *phb = phonebook; + while(phonebook != null) + delete phonebook; +} + +void PhoneBookEntry::savePhonebook() +{ + SPIFFS.remove("/zphonebook.txt"); + delay(500); + File f = SPIFFS.open("/zphonebook.txt", "w"); + int ct=0; + PhoneBookEntry *phb=phonebook; + while(phb != null) + { + f.printf("%ul,%s,%s,%s\n",phb->number,phb->address,phb->modifiers,phb->notes); + phb = phb->next; + ct++; + } + f.close(); + delay(500); + if(SPIFFS.exists("/zphonebook.txt")) + { + File f = SPIFFS.open("/zphonebook.txt", "r"); + while(f.available()>0) + { + String str=""; + char c=f.read(); + while((c != '\n') && (f.available()>0)) + { + str += c; + c=f.read(); + } + int argn=0; + if(str.length()>0) + { + for(int i=0;i +/* + Copyright 2018-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifdef INCLUDE_SD_SHELL + +FTPHost::~FTPHost() +{ + freeCharArray(&hostIp); + freeCharArray(&username); + freeCharArray(&pw); + freeCharArray(&path); + freeCharArray(&file); +} + +bool FTPHost::doGet(FS *fs, const char *localpath, const char *remotepath) +{ + fixPath(remotepath); + return doFTPGet(fs,hostIp,port,localpath,file,username,pw,doSSL); +} + +bool FTPHost::doPut(File &f, const char *remotepath) +{ + fixPath(remotepath); + return doFTPPut(f,hostIp,port,file,username,pw,doSSL); +} + +bool FTPHost::doLS(ZSerial *serial, const char *remotepath) +{ + fixPath(remotepath); + bool kaplah = doFTPLS(serial,hostIp,port,file,username,pw,doSSL); + if((kaplah) + && (strcmp(file,path) != 0) + && (strlen(file)>0)) + { + if(file[strlen(file)-1]=='/') + setCharArray(&path, file); + else + { + char buf[strlen(file)+2]; + sprintf(buf,"%s/",file); + setCharArray(&path, buf); + } + } + return kaplah; +} + +void FTPHost::fixPath(const char *remotepath) +{ + if(remotepath == 0) + return; + char *end = strrchr(remotepath, '/'); + int buflen = ((path == 0) ? 0 : strlen(path)) + strlen(remotepath) + 3; + char fbuf[buflen]; + char pbuf[buflen]; + if(remotepath[0] == '/') + { + strcpy(fbuf, remotepath); + if(end > 0) + { + *end = 0; + sprintf(pbuf,"%s/",remotepath); + } + else + strcpy(pbuf, "/"); + } + else + { + sprintf(fbuf,"%s%s",path,remotepath); + if(end > 0) + { + *end = 0; + sprintf(pbuf,"%s%s/",path,remotepath); + } + else + strcpy(pbuf, path); + } + setCharArray(&path, pbuf); + setCharArray(&file, fbuf); +} + +bool FTPHost::parseUrl(uint8_t *vbuf, char **req) +{ + char *newHostIp; + int newPort; + bool newDoSSL; + char *newUsername; + char *newPassword; + if(parseFTPUrl(vbuf,&newHostIp,req,&newPort,&newDoSSL,&newUsername,&newPassword)) + { + setCharArray(&hostIp,newHostIp); + port = newPort; + doSSL = newDoSSL; + setCharArray(&username,newUsername); + setCharArray(&pw,newPassword); + setCharArray(&path,"/"); + setCharArray(&file,""); + return true; + } + return false; +} + +FTPHost *makeFTPHost(bool isUrl, FTPHost *host, uint8_t *buf, char **req) +{ + if(isUrl) + { + if(host != 0) + delete host; + host = new FTPHost(); + if(!(host->parseUrl(buf,req))) + { + delete host; + host=0; + *req=0; + } + } + else + *req=(char *)buf; + return host; +} + +bool parseFTPUrl(uint8_t *vbuf, char **hostIp, char **req, int *port, bool *doSSL, char **username, char **pw) +{ + *doSSL = false; + if(strstr((char *)vbuf,"ftp:")==(char *)vbuf) + vbuf = vbuf + 4; + else + if(strstr((char *)vbuf,"ftps:")==(char *)vbuf) + { + vbuf = vbuf + 5; + *doSSL = true; + } + while(*vbuf == '/') + vbuf++; + + *port= 21; + *hostIp = (char *)vbuf; + char *atSign=strchr((char *)vbuf,'@'); + *username=NULL; + *pw = NULL; + if(atSign != NULL) + { + *hostIp = atSign + 1; + *atSign = 0; + *username = (char *)vbuf; + vbuf = (uint8_t *)(atSign + 1); + char *pwB = strchr(*username, ':'); + if(pwB != NULL) + { + *pw = pwB+1; + *pwB=0; + } + } + char *portB=strchr((char *)vbuf,':'); + int len=strlen((char *)vbuf); + *req = strchr((char *)vbuf,'/'); + if(*req != NULL) + { + *(*req)=0; + if((*req) != (char *)(vbuf+len-1)) + (*req)++; + } + else + *req = (char *)(vbuf + len); + if(portB != NULL) + { + *portB = 0; + portB++; + *port = atoi(portB); + if(port <= 0) + return ZERROR; + } + return true; +} + +static bool doFTPQuit(WiFiClient **c) +{ + if((*c)->connected()) + { + (*c)->printf("QUIT\r\n"); + delay(500); + } + (*c)->stop(); + delete (*c); + return false; +} + +static String readLine(WiFiClient *c, int timeout) +{ + unsigned long now=millis(); + String line = ""; + while(((millis()-now < timeout) || (c->available()>0)) + && (c->connected()|| (c->available()>0))) + { + yield(); + if(c->available()>0) + { + char ch=c->read(); + if((ch=='\n')||(ch=='\r')) + { + if(line.length()>0) + return line; + } + else + if((ch >= 32 ) && (ch <= 127)) + line += ch; + now=millis(); + } + } + return line; +} + +static int getFTPResponseCode(WiFiClient *c, char *buf) +{ + String resp = readLine(c,5000); + if(resp.length() == 0) + return -1; // timeout total + while((resp.length()<3) + ||(resp[0] < '0')||(resp[0] > '9') + ||(resp[1] < '0')||(resp[1] > '9') + ||(resp[2] < '0')||(resp[2] > '9')) + { + yield(); + resp = readLine(c,1000); + if(resp.length()==0) + return -1; + } + if((buf != NULL)&&(resp.length()<132)) + strcpy(buf,resp.substring(4).c_str()); + return atoi(resp.substring(0,3).c_str()); +} + +void readBytesToSilence(WiFiClient *cc) +{ + unsigned long now = millis(); // just eat the intro for 1 second of silence + while(millis()-now < 1000) + { + yield(); + if(cc->available()>0) + { + cc->read(); + now=millis(); + } + } +} + +bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const char *remotepath, const char *username, const char *pw, const bool doSSL) +{ + WiFiClient *cc = createWiFiClient(doSSL); + if(WiFi.status() != WL_CONNECTED) + return false; + if(!cc->connect(hostIp, port)) + return doFTPQuit(&cc); + cc->setNoDelay(DEFAULT_NO_DELAY); + readBytesToSilence(cc); + if(username == NULL) + cc->printf("USER anonymous\r\n"); + else + cc->printf("USER %s\r\n",username); + int respCode = getFTPResponseCode(cc, NULL); + if(respCode != 331) + return doFTPQuit(&cc); + if(pw == NULL) + cc->printf("PASS zimodem@zimtime.net\r\n"); + else + cc->printf("PASS %s\r\n",pw); + respCode = getFTPResponseCode(cc, NULL); + if(respCode != 230) + return doFTPQuit(&cc); + readBytesToSilence(cc); + cc->printf("TYPE I\r\n"); + respCode = getFTPResponseCode(cc, NULL); + if(respCode < 0) + return doFTPQuit(&cc); + char ipbuf[129]; + cc->printf("PASV\r\n"); + respCode = getFTPResponseCode(cc, ipbuf); + if(respCode != 227) + return doFTPQuit(&cc); + // now parse the pasv result in .* (ipv4,ipv4,ipv4,ipv4, + char *ipptr = strchr(ipbuf,'('); + while((ipptr != NULL) && (strchr(ipptr+1,'(')!= NULL)) + ipptr=strchr(ipptr+1,'('); + if(ipptr == NULL) + return doFTPQuit(&cc); + int digitCount=0; + int digits[10]; + char *commaPtr=strchr(ipptr+1,','); + while((commaPtr != NULL)&&(digitCount < 10)) + { + *commaPtr = 0; + digits[digitCount++] = atoi(ipptr+1); + ipptr=commaPtr; + commaPtr=strchr(ipptr+1,','); + if(commaPtr == NULL) + commaPtr=strchr(ipptr+1,')'); + } + if(digitCount < 6) + return doFTPQuit(&cc); + sprintf(ipbuf,"%d.%d.%d.%d",digits[0],digits[1],digits[2],digits[3]); + int dataPort = (256 * digits[4]) + digits[5]; + // ok, now we are ready for DATA! + if(WiFi.status() != WL_CONNECTED) + return doFTPQuit(&cc); + WiFiClient *c = createWiFiClient(doSSL); + if(!c->connect(ipbuf, dataPort)) + { + doFTPQuit(&c); + return doFTPQuit(&cc); + } + c->setNoDelay(DEFAULT_NO_DELAY); + cc->printf("RETR %s\r\n",remotepath); + respCode = getFTPResponseCode(cc, NULL); + if((respCode < 0)||(respCode > 400)) + return doFTPQuit(&cc); + File f = fs->open(localpath, "w"); + unsigned long now=millis(); + while((c->connected()||(c->available()>0)) + && ((millis()-now) < 30000)) // loop for data, with nice long timeout + { + if(c->available()>=0) + { + now=millis(); + uint8_t ch=c->read(); + //logSocketIn(ch); // this is ALSO not socket input! + f.write(ch); + } + else + yield(); + } + f.flush(); + f.close(); + c->stop(); + delete c; + doFTPQuit(&cc); + return true; +} + +bool doFTPPut(File &f, const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, const bool doSSL) +{ + WiFiClient *cc = createWiFiClient(doSSL); + if(WiFi.status() != WL_CONNECTED) + return false; + if(!cc->connect(hostIp, port)) + return doFTPQuit(&cc); + cc->setNoDelay(DEFAULT_NO_DELAY); + readBytesToSilence(cc); + if(username == NULL) + cc->printf("USER anonymous\r\n"); + else + cc->printf("USER %s\r\n",username); + int respCode = getFTPResponseCode(cc, NULL); + if(respCode != 331) + return doFTPQuit(&cc); + if(pw == NULL) + cc->printf("PASS zimodem@zimtime.net\r\n"); + else + cc->printf("PASS %s\r\n",pw); + respCode = getFTPResponseCode(cc, NULL); + if(respCode != 230) + return doFTPQuit(&cc); + readBytesToSilence(cc); + cc->printf("TYPE I\r\n"); + respCode = getFTPResponseCode(cc, NULL); + if(respCode < 0) + return doFTPQuit(&cc); + char ipbuf[129]; + cc->printf("PASV\r\n"); + debugPrintf("PASV\r\n"); + respCode = getFTPResponseCode(cc, ipbuf); + if(respCode != 227) + return doFTPQuit(&cc); + // now parse the pasv result in .* (ipv4,ipv4,ipv4,ipv4, + char *ipptr = strchr(ipbuf,'('); + while((ipptr != NULL) && (strchr(ipptr+1,'(')!= NULL)) + ipptr=strchr(ipptr+1,'('); + if(ipptr == NULL) + return doFTPQuit(&cc); + int digitCount=0; + int digits[10]; + char *commaPtr=strchr(ipptr+1,','); + while((commaPtr != NULL)&&(digitCount < 10)) + { + *commaPtr = 0; + digits[digitCount++] = atoi(ipptr+1); + ipptr=commaPtr; + commaPtr=strchr(ipptr+1,','); + if(commaPtr == NULL) + commaPtr=strchr(ipptr+1,')'); + } + if(digitCount < 6) + return doFTPQuit(&cc); + sprintf(ipbuf,"%d.%d.%d.%d",digits[0],digits[1],digits[2],digits[3]); + debugPrintf(ipbuf,"%d.%d.%d.%d",digits[0],digits[1],digits[2],digits[3]); + int dataPort = (256 * digits[4]) + digits[5]; + // ok, now we are ready for DATA! + if(WiFi.status() != WL_CONNECTED) + return doFTPQuit(&cc); + WiFiClient *c = createWiFiClient(doSSL); + if(!c->connect(ipbuf, dataPort)) + { + doFTPQuit(&c); + return doFTPQuit(&cc); + } + c->setNoDelay(DEFAULT_NO_DELAY); + debugPrintf(" STOR %s\r\n",remotepath); + cc->printf("STOR %s\r\n",remotepath); + respCode = getFTPResponseCode(cc, NULL); + if((respCode < 0)||(respCode > 400)) + return doFTPQuit(&cc); + unsigned long now=millis(); + debugPrintf(" Storing... %d\r\n",f.available()); + while((c->connected()) + && (f.available()>0) && ((millis()-now) < 30000)) // loop for data, with nice long timeout + { + if(f.available()>=0) + { + now=millis(); + uint8_t ch=f.read(); + //logSocketIn(ch); // this is ALSO not socket input! + c->write(ch); + } + else + yield(); + } + debugPrintf("FPUT: Done\r\n"); + c->flush(); + c->stop(); + delete c; + doFTPQuit(&cc); + return true; +} + +bool doFTPLS(ZSerial *serial, const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, const bool doSSL) +{ + WiFiClient *cc = createWiFiClient(doSSL); + if(WiFi.status() != WL_CONNECTED) + return false; + if(!cc->connect(hostIp, port)) + return doFTPQuit(&cc); + cc->setNoDelay(DEFAULT_NO_DELAY); + readBytesToSilence(cc); + if(username == NULL) + cc->printf("USER anonymous\r\n"); + else + cc->printf("USER %s\r\n",username); + int respCode = getFTPResponseCode(cc, NULL); + if(respCode != 331) + return doFTPQuit(&cc); + if(pw == NULL) + cc->printf("PASS zimodem@zimtime.net\r\n"); + else + cc->printf("PASS %s\r\n",pw); + respCode = getFTPResponseCode(cc, NULL); + if(respCode != 230) + return doFTPQuit(&cc); + readBytesToSilence(cc); + cc->printf("TYPE A\r\n"); + respCode = getFTPResponseCode(cc, NULL); + if(respCode < 0) + return doFTPQuit(&cc); + if((remotepath != NULL)&& (*remotepath != NULL)) + { + cc->printf("CWD %s\r\n",remotepath); + respCode = getFTPResponseCode(cc, NULL); + if((respCode < 0)||(respCode > 400)) + return doFTPQuit(&cc); + readBytesToSilence(cc); + } + char ipbuf[129]; + cc->printf("PASV\r\n"); + respCode = getFTPResponseCode(cc, ipbuf); + if(respCode != 227) + return doFTPQuit(&cc); + // now parse the pasv result in .* (ipv4,ipv4,ipv4,ipv4, + char *ipptr = strchr(ipbuf,'('); + while((ipptr != NULL) && (strchr(ipptr+1,'(')!= NULL)) + ipptr=strchr(ipptr+1,'('); + if(ipptr == NULL) + return doFTPQuit(&cc); + int digitCount=0; + int digits[10]; + char *commaPtr=strchr(ipptr+1,','); + while((commaPtr != NULL)&&(digitCount < 10)) + { + *commaPtr = 0; + digits[digitCount++] = atoi(ipptr+1); + ipptr=commaPtr; + commaPtr=strchr(ipptr+1,','); + if(commaPtr == NULL) + commaPtr=strchr(ipptr+1,')'); + } + if(digitCount < 6) + return doFTPQuit(&cc); + sprintf(ipbuf,"%d.%d.%d.%d",digits[0],digits[1],digits[2],digits[3]); + int dataPort = (256 * digits[4]) + digits[5]; + // ok, now we are ready for DATA! + if(WiFi.status() != WL_CONNECTED) + return doFTPQuit(&cc); + WiFiClient *c = createWiFiClient(doSSL); + if(!c->connect(ipbuf, dataPort)) + { + doFTPQuit(&c); + return doFTPQuit(&cc); + } + c->setNoDelay(DEFAULT_NO_DELAY); + cc->printf("LIST\r\n",remotepath); + respCode = getFTPResponseCode(cc, NULL); + if((respCode < 0)||(respCode > 400)) + return doFTPQuit(&cc); + unsigned long now=millis(); + while((c->connected()||(c->available()>0)) + && ((millis()-now) < 30000)) // loop for data, with nice long timeout + { + if(c->available()>=0) + { + now=millis(); + uint8_t ch=c->read(); + //logSocketIn(ch); // this is ALSO not socket input! + serial->print((char)ch); + } + else + yield(); + } + c->stop(); + delete c; + doFTPQuit(&cc); + return true; +} +#endif diff --git a/src/proto_hostcm.ino b/src/proto_hostcm.ino new file mode 100644 index 0000000..af5fb45 --- /dev/null +++ b/src/proto_hostcm.ino @@ -0,0 +1,691 @@ +#include +#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_HOSTCM +/* Converted from Rob Ferguson's code by Bo Zimmerman + * + * Copyright (c) 2013, Robert Ferguson All rights reserved. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +char *basename(char *path) +{ + char *base = strrchr(path, '/'); + return base ? base+1 : path; +} + +char *dirname(char *path) +{ + char *base = strrchr(path, '/'); + if(base) + { + *base = 0; + return path; + } + return ""; +} + +HostCM::HostCM(FS *fs) +{ + hFS = fs; + for(int i=0;if != 0) + e->f.close(); + char d = e->descriptor; + memset(e,sizeof(struct _HCMFile),0); + e->descriptor = d; +} + + +HCMFile *HostCM::getFileByDescriptor(char c) +{ + for(int i=0;i0)||((tm-lastNonPlusTm)>800)) + { + plussesInARow++; + if(plussesInARow > 2) + plusTimeExpire = tm + 800; + } + } + else + { + plusTimeExpire = 0; + lastNonPlusTm = tm; + plussesInARow = 0; + } +} + +bool HostCM::checkPlusPlusPlusExpire(const unsigned long tm) +{ + if(aborted) + return true; + if((plusTimeExpire>0)&&(tm>plusTimeExpire)&&(plussesInARow>2)) + { + aborted = true; + plusTimeExpire = 0; + lastNonPlusTm = tm; + plussesInARow = 0; + return true; + } + return false; +} + +bool HostCM::isAborted() +{ + return aborted; +} + +void HostCM::protoCloseFile() +{ + HCMFile *h = getFileByDescriptor((char)inbuf[1]); + if (h==0) { + sendError("error: invalid descriptor %c", inbuf[1]); + return; + } + + if (h->f == 0) + { + sendError("error: file not open %c", inbuf[1]); // should this be an error though? + return; + } + delFileEntry(h); + sendACK(); +} + +void HostCM::protoPutToFile() +{ + HCMFile *h = getFileByDescriptor((char)inbuf[1]); + if (h==0) { + sendError("error: invalid descriptor %c", inbuf[1]); + return; + } + uint8_t eor = (uint8_t)lc((char)inbuf[2]); + if (h->f == 0) + { + sendError("error: file not open %c", inbuf[1]); + return; + } + /*if((h->mode)=='r')||(h->mode=='l')) + { + sendError("error: read-only file %c", inbuf[1]); + return; + }*/ + if(h->format == 'b') + { + FROMHEX(&inbuf[3], idex - 4); + idex = ((idex - 4) / 2) + 4; + } + if((eor=='z') + &&((h->format == 't') + ||(h->type != 'f'))) + { + inbuf[idex-1] = opt.lineend; + idex++; + } + if(h->f.write(&inbuf[3],idex-4) != idex-4) + { + sendError("error: write failed to file %c", inbuf[1]); + return; + } + sendACK(); +} + +void HostCM::protoGetFileBytes() +{ + int c; + HCMFile *h = getFileByDescriptor((char)inbuf[1]); + if (h==0) + { + sendError("error: invalid descriptor %c", inbuf[1]); + return; + } + if((h->f == 0) + ||((h->mode != 'r') && (h->mode != 'u') && (h->mode != 'l'))) + { + sendError("error: file not open/readable %c", inbuf[1]); + return; + } + + odex = 0; + outbuf[odex++] = opt.response; + if(h->format == 't') + { + outbuf[odex++] = 'b'; + outbuf[odex] = 'z'; + do + { + odex++; + c = h->f.read(); + outbuf[odex] = (uint8_t)(c & 0xff); + } + while((c >= 0) && (odex < (HCM_SENDBUF - 2)) && (outbuf[odex] != 0xd)); + + if(c<0) + { + outbuf[1] = 'e'; + outbuf[2] = checksum(&outbuf[1], 1); + odex=3; // abandon anything between EOL and EOF + } + else + { + if (odex >= (HCM_SENDBUF - 2)) + outbuf[2] = 'n'; + outbuf[odex] = checksum(&outbuf[1], odex - 1); + odex++; + } + } + else + if (h->format == 'b') + { + int rdcount = HCM_SENDBUF; + char eor = 'n'; + if(h->reclen) + { + if (h->reclen < HCM_SENDBUF) + { + rdcount = h->reclen; + eor = 'z'; + } + else + { + int pos = h->f.position(); + if((pos & h->reclen) > ((pos + rdcount) & h->reclen)) + { + rdcount = ((pos + rdcount) / h->reclen) * h->reclen - pos; + eor = 'z'; + } + } + } + uint8_t rdbuf[rdcount]; + int n = h->f.read(rdbuf, rdcount); + if (n <= 0) + { + outbuf[odex++] = 'e'; + outbuf[odex] = checksum(&outbuf[1], odex - 1); + odex++; + } + else + { + outbuf[odex++] = 'b'; + outbuf[odex++] = eor; + for(int i=0;i= HCM_MAXFN) + { + sendError("error: too many open files"); + return; + } + if(idex<8) + { + sendError("error: command too short"); + return; + } + uint8_t mode = (uint8_t)lc((char)inbuf[1]); + if(strchr("rlwsua",(char)mode)==0) + { + sendError("error: illegal mode %c",(char)mode); + return; + } + bool isRead = strchr("rl",(char)mode)!=0; + uint8_t format = (uint8_t)lc((char)inbuf[2]); + uint8_t *ptr = (uint8_t *)memchr(inbuf+3, '(', idex-3); + if(ptr == 0) + { + sendError("error: missing ("); + return; + } + uint8_t type = (uint8_t)lc((char)ptr[1]); + uint8_t reclen = 0; + if(ptr[2]==':') + reclen=atoi((char *)(ptr+3)); + ptr = (uint8_t *)memchr(ptr+2, ')', eobuf-(ptr+1)); + if(ptr == 0) + { + sendError("error: missing )"); + return; + } + inbuf[idex - 1] = 0; + uint8_t *fnptr = ptr + 1; + HCMFile *newF = addNewFileEntry(); + if (type == 'f') + { + char *bn = basename((char *)ptr+1); + char *dn = dirname((char *)ptr+1); + if(reclen == 0) + reclen = 80; + snprintf(newF->filename, sizeof(newF->filename), "%s/(f:%d)%s", dn, reclen, bn); + } + else + strncpy(newF->filename, (char *)ptr+1, sizeof(newF->filename)); + if(isRead) + { + newF->f = SD.open(newF->filename); + if(newF->f == 0) + { + if(strchr(basename(newF->filename), ',') == 0) + { + if(strlen(newF->filename) + 5 < HCM_FNSIZ) + { + if((mode == 'l') || (mode == 's')) + strcat(newF->filename, ",prg"); + else + { + if(type == 'f') + strcat(newF->filename, ",rel"); + else + strcat(newF->filename, ",seq"); + } + newF->f = SD.open(newF->filename); + if(newF == 0) + { + sendError("error: file '%s' not found", newF->filename); + return; + } + } + } + else + { + sendError("error: file '%s' not found", newF->filename); + return; + } + } + } + else + { + newF->f = SD.open(newF->filename,FILE_WRITE); + if(newF->f == 0) + { + sendError("error: failed to open '%s'", newF->filename); + return; + } + if(newF->f && (mode == 'a')) + newF->f.seek(EOF); + } + newF->mode = mode; + newF->format = format; + newF->type = type; + newF->reclen = reclen; + + odex = 0; + outbuf[odex++] = opt.response; + outbuf[odex++] = 'b'; + outbuf[odex++] = newF->descriptor; + outbuf[odex++] = checksum(&(outbuf[1]), 2); + outbuf[odex++] = opt.lineend; + outbuf[odex++] = opt.prompt; + hserial.write(outbuf, odex); + hserial.flush(); +} + +void HostCM::protoOpenDir() +{ + if(openDirF != 0) + { + sendError("error: directory open"); + return; + } + if(idex > 2) + inbuf[idex - 1] = 0; + else + { + strcpy((char *)&inbuf[1], "/"); + idex++; + } + + openDirF = SD.open((char *)&inbuf[1]); + if((openDirF == 0)||(!openDirF.isDirectory())) + { + sendError("error: directory not found %s",(char *)&inbuf[1]); + return; + } + sendACK(); +} + +void HostCM::protoCloseDir() +{ + if(openDirF == 0) + { + sendError("error: directory not open"); // should this really be an error? + return; + } + openDirF.close(); + openDirF = (File)0; + sendACK(); +} + +void HostCM::protoNextDirFile() +{ + if(openDirF == 0) + { + sendError("error: directory not open"); // should this really be an error? + return; + } + + odex = 0; + outbuf[odex++] = opt.response; + + File nf = openDirF.openNextFile(); + if(nf == 0) + outbuf[odex++] = 'e'; + else + { + char *fname = (char *)nf.name(); + char *paren = strchr(fname,')'); + int reclen = 0; + if((strncmp("(f:", fname, 3) == 0) && (paren != 0)) + { + fname = paren + 1; + reclen = atoi((char *)&fname[3]); + } + outbuf[odex++] = 'b'; + + if(reclen) + odex += snprintf((char *)&outbuf[odex], HCM_BUFSIZ - odex, "%-20s %8llu (%d)", + fname, (unsigned long long)nf.size(), reclen); + else + { + if(nf.isDirectory()) + odex += snprintf((char *)&outbuf[odex], HCM_BUFSIZ - odex, "%s/", fname); + else + { + odex += snprintf((char *)&outbuf[odex], HCM_BUFSIZ - odex, "%-20s %8llu", + fname, (unsigned long long)nf.size()); + } + } + } + nf.close(); + outbuf[odex] = checksum(&(outbuf[1]), odex - 1); + odex++; + outbuf[odex++] = opt.lineend; + outbuf[odex++] = opt.prompt; + hserial.write(outbuf, odex); + hserial.flush(); +} + +void HostCM::protoSetRenameFile() +{ + if (renameF != 0) + { + sendError("error: rename in progress"); + return; + } + + if (idex > 2) + { + inbuf[idex - 1] = 0; + renameF = SD.open((char *)&inbuf[1]); + } + else + { + sendError("error: missing filename"); + return; + } + sendACK(); +} + +void HostCM::protoFinRenameFile() +{ + if (renameF == 0) + { + sendError("error: rename not started"); + return; + } + + if (idex > 2) + inbuf[idex - 1] = 0; + else + { + sendError("error: missing filename"); + return; + } + + String on = renameF.name(); + renameF.close(); + if(!SD.rename(on,(char *)&inbuf[1])) + { + renameF = (File)0; + sendError("error: rename %s failed",on); + return; + } + renameF = (File)0; + sendACK(); +} + +void HostCM::protoEraseFile() +{ + if (idex > 2) + inbuf[idex - 1] = 0; + else + { + sendError("error: missing filename"); + return; + } + if(!SD.remove((char *)&inbuf[1])) + { + sendError("error: erase %s failed",(char *)&inbuf[1] ); + return; + } + sendACK(); +} + +void HostCM::protoSeekFile() +{ + HCMFile *h = getFileByDescriptor((char)inbuf[1]); + if (h==0) + { + sendError("error: invalid descriptor %c", inbuf[1]); + return; + } + if(h->f == 0) + { + sendError("error: file not open/readable %c", inbuf[1]); + return; + } + + inbuf[idex - 1] = 0; + + unsigned long offset = atoi((char *)&inbuf[2]) * ((h->reclen == 0)? 1 : h->reclen); + if(!h->f.seek(offset)) + { + sendError("error: seek failed on %s @ %ul", h->f.name(),offset); + return; + } + sendACK(); +} + +char HostCM::checksum(uint8_t *b, int n) +{ + int i, s = 0; + + for (i = 0; i < n; i++) + s += b[i] & 0xf; + return ('A' + (s&0xf)); +} + +void HostCM::sendError(const char* format, ...) +{ + odex = 0; + outbuf[odex++] = opt.response; + outbuf[odex++] = 'x'; + va_list arglist; + va_start(arglist, format); + odex += vsnprintf((char *)&outbuf[2], 80, format, arglist); + va_end(arglist); + outbuf[odex] = checksum(&outbuf[1], odex - 1); + odex++; + outbuf[odex++] = opt.lineend; + outbuf[odex++] = opt.prompt; + hserial.write(outbuf, odex); + hserial.flush(); +} + +void HostCM::sendNAK() +{ + odex = 0; + outbuf[odex++] = opt.response; + outbuf[odex++] = 'N'; + outbuf[odex++] = opt.lineend; + outbuf[odex++] = opt.prompt; + hserial.write(outbuf, odex); + hserial.flush(); +} + +void HostCM::sendACK() +{ + odex = 0; + outbuf[odex++] = opt.response; + outbuf[odex++] = 'b'; + outbuf[odex++] = checksum(&(outbuf[1]), 1); + outbuf[odex++] = opt.lineend; + outbuf[odex++] = opt.prompt; + hserial.write(outbuf, odex); + hserial.flush(); +} + +void HostCM::receiveLoop() +{ + serialOutDeque(); + unsigned long tm = millis(); + if(checkPlusPlusPlusExpire(tm)) + return; + int c; + while(hserial.available() > 0) + { + c=hserial.read(); + if(idex1)&&(inbuf[idex-1]!=checksum(inbuf,idex-1))) + sendNAK(); + else + { + logPrintf("HOSTCM received: %c\n",inbuf[0]); + switch(inbuf[0]) + { + case 'N': + hserial.write(outbuf, odex); + hserial.flush(); + break; + case 'v': sendACK(); break; + case 'q': aborted = closeAllFiles(); break; + case 'o': protoOpenFile(); break; + case 'c': protoCloseFile(); break; + case 'p': protoPutToFile(); break; + case 'g': protoGetFileBytes(); break; + case 'd': protoOpenDir(); break; + case 'f': protoNextDirFile(); break; + case 'k': protoCloseDir(); break; + case 'w': protoSetRenameFile(); break; + case 'b': protoFinRenameFile(); break; + case 'y': protoEraseFile(); break; + case 'r': protoSeekFile(); break; + default: + sendNAK(); + break; + } + } + idex=0; // we are ready for the next packet! + serialOutDeque(); +} +#endif +#endif diff --git a/src/proto_http.ino b/src/proto_http.ino new file mode 100644 index 0000000..56efd57 --- /dev/null +++ b/src/proto_http.ino @@ -0,0 +1,399 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +bool parseWebUrl(uint8_t *vbuf, char **hostIp, char **req, int *port, bool *doSSL) +{ + *doSSL = false; + if(strstr((char *)vbuf,"http:")==(char *)vbuf) + vbuf = vbuf + 5; + else + if(strstr((char *)vbuf,"https:")==(char *)vbuf) + { + vbuf = vbuf + 6; + *doSSL = true; + } + while(*vbuf == '/') + vbuf++; + + *port= (*doSSL) ? 443 : 80; + *hostIp = (char *)vbuf; + char *portB=strchr((char *)vbuf,':'); + *req = strchr((char *)vbuf,'/'); + if(*req != NULL) + { + *(*req)=0; + (*req)++; + } + else + { + int len=strlen((char *)vbuf); + *req = (char *)(vbuf + len); + } + if(portB != NULL) + { + *portB = 0; + portB++; + *port = atoi(portB); + if(port <= 0) + return false; + } + return true; +} +/* + * It just breaks too many things to allow a stream to go forward without + * a determined length. For example: firmware updates, and even at&g returns + * a page length for the client. Let true clients use sockets and handle + * their own chunked encoding. +class ChunkedStream : public WiFiClient +{ +private: + WiFiClient *wifi = null; + int chunkCount = 0; + int chunkSize = 0; + uint8_t state = 0; //0 + +public: + + ChunkedStream(WiFiClient *s) + { + wifi = s; + } + + ~ChunkedStream() + { + if(wifi != null) + { + wifi->stop(); + delete wifi; + } + } + + virtual int read() + { + if(available()==0) + return -1; + char c=wifi->read(); + bool gotC = false; + int errors = 0; + while((!gotC) && (errors < 5000)) + { + switch(state) + { + case 0: + if(c=='0') + return '\0'; + if((c>='0')&&(c<='9')) + { + chunkSize = (c - '0'); + state=1; + } + else + if((c>='a')&&(c<='f')) + { + chunkSize = 10 + (c-'a'); + state=1; + } + break; + case 1: + { + if((c>='0')&&(c<='9')) + chunkSize = (chunkSize * 16) + (c - '0'); + else + if((c>='a')&&(c<='f')) + chunkSize = (chunkSize * 16) + (c-'a'); + else + if(c=='\r') + state=2; + break; + } + case 2: + if(c == '\n') + { + state = 3; + chunkCount=0; + } + else + state = 0; + break; + case 3: + if(chunkCount < chunkSize) + { + gotC = true; + chunkCount++; + } + else + if(c == '\r') + state = 4; + else + state = 0; + break; + case 4: + if(c == '\n') + state = 0; + else + state = 0; // what else is there to do?! + break; + } + while((!gotC) && (errors < 5000)) + { + if(available()>0) + { + c=wifi->read(); + break; + } + else + if(++errors > 5000) + break; + else + delay(1); + } + } + return c; + } + virtual int peek() + { + return wifi->peek(); + } + + virtual int read(uint8_t *buf, size_t size) + { + if(size == 0) + return 0; + int num = available(); + if(num > size) + num=size; + for(int i=0;igetNoDelay(); + } + void setNoDelay(bool nodelay) + { + wifi->setNoDelay(nodelay); + } + + virtual int available() + { + return wifi->available(); + } + + virtual void stop() + { + wifi->stop(); + } + virtual uint8_t connected() + { + return wifi->connected(); + } +}; + */ +WiFiClient *doWebGetStream(const char *hostIp, int port, const char *req, bool doSSL, uint32_t *responseSize) +{ + *responseSize = 0; + if(WiFi.status() != WL_CONNECTED) + return null; + + WiFiClient *c = createWiFiClient(doSSL); + if(port == 0) + port = 80; + if(!c->connect(hostIp, port)) + { + c->stop(); + delete c; + return null; + } + c->setNoDelay(DEFAULT_NO_DELAY); + + const char *root = ""; + if(req == NULL) + req=root; + if(*req == '/') + req++; + + c->printf("GET /%s HTTP/1.1\r\n",req); + c->printf("User-Agent: Zimodem Firmware\r\n"); + c->printf("Host: %s\r\n",hostIp); + c->printf("Connection: close\r\n\r\n"); + + String ln = ""; + String reUrl = ""; + uint32_t respLength = 0; + int respCode = -1; + bool chunked = false; + while(c->connected() || (c->available()>0)) + { + yield(); + if(c->available()==0) + continue; + + char ch = (char)c->read(); + logSocketIn(ch); // this is very much socket input! + if(ch == '\r') + continue; + else + if(ch == '\n') + { + if(ln.length()==0) + break; + if(respCode < 0) + { + int sp = ln.indexOf(' '); + if(sp<=0) + break; + ln.remove(0,sp+1); + sp = ln.indexOf(' '); + if(sp<=0) + break; + ln.remove(sp); + respCode = atoi(ln.c_str()); + } + else + { + int x=ln.indexOf(':'); + if(x>0) + { + String header = ln.substring(0,x); + header.toLowerCase(); + if(header == "content-length") + { + ln.remove(0,16); + respLength = atoi(ln.c_str()); + } + else + if(header == "location") + { + reUrl = ln; + reUrl.remove(0,10); + } + else + if(header == "transfer-encoding") + { + ln.toLowerCase(); + chunked = ln.indexOf("chunked") > 0; + } + } + } + ln = ""; + } + else + ln.concat(ch); + } + + if((respCode >= 300) + && (respCode <= 399) + && (reUrl.length() > 0) + && (reUrl.length() < 1024)) + { + char newUrlBuf[reUrl.length()+1]; + strcpy(newUrlBuf,reUrl.c_str()); + char *hostIp2; + char *req2; + int port2; + bool doSSL2; + if(parseWebUrl((uint8_t *)newUrlBuf, &hostIp2,&req2,&port2,&doSSL2)) + { + c->stop(); + delete c; + return doWebGetStream(hostIp2,port2,req2,doSSL2,responseSize); + + } + } + + *responseSize = respLength; + if(((!c->connected())&&(c->available()==0)) + ||(respCode != 200) + ||(respLength <= 0)) + { + c->stop(); + delete c; + return null; + } + //if(chunked) // technically, if a length was returned, chunked would be ok, but that's not in the cards. + // return new ChunkedStream(c); + return c; +} + +bool doWebGet(const char *hostIp, int port, FS *fs, const char *filename, const char *req, const bool doSSL) +{ + uint32_t respLength=0; + WiFiClient *c = doWebGetStream(hostIp, port, req, doSSL, &respLength); + if(c==null) + return false; + uint32_t bytesRead = 0; + File f = fs->open(filename, "w"); + unsigned long now = millis(); + while((bytesRead < respLength) // this can be removed for chunked encoding support + && (c->connected()||(c->available()>0)) + && ((millis()-now)<10000)) + { + if(c->available()>0) + { + now=millis(); + uint8_t ch=c->read(); + logSocketIn(ch); // this is very much socket input! + f.write(ch); + bytesRead++; + } + else + yield(); + } + f.flush(); + f.close(); + c->stop(); + delete c; + return (respLength == 0) || (bytesRead == respLength); +} + +bool doWebGetBytes(const char *hostIp, int port, const char *req, const bool doSSL, uint8_t *buf, int *bufSize) +{ + *bufSize = -1; + uint32_t respLength=0; + WiFiClient *c = doWebGetStream(hostIp, port, req, doSSL, &respLength); + if(c==null) + return false; + if(((!c->connected())&&(c->available()==0)) + ||(respLength > *bufSize)) + { + c->stop(); + delete c; + return false; + } + *bufSize = (int)respLength; + int index=0; + unsigned long now = millis(); + while((index < respLength) // this can be removed for chunked encoding support + &&(c->connected()||(c->available()>0)) + && ((millis()-now)<10000)) + { + if(c->available()>0) + { + uint8_t ch=c->read(); + now = millis(); + logSocketIn(ch); // how is this not socket input -- it's coming from a WiFiClient -- that's THE SOCKET! + buf[index++] = ch; + } + else + yield(); + } + *bufSize = index; + c->stop(); + delete c; + return (respLength == 0) || (index == respLength); +} diff --git a/src/proto_kermit.ino b/src/proto_kermit.ino new file mode 100644 index 0000000..efcc073 --- /dev/null +++ b/src/proto_kermit.ino @@ -0,0 +1,818 @@ +#include +#ifdef INCLUDE_SD_SHELL +/* + * K e r m i t File Transfer Utility + * + * UNIX Kermit, Columbia University, 1981, 1982, 1983 + * Bill Catchings, Bob Cattani, Chris Maio, Frank da Cruz, Alan Crosswell + * + * Also: Jim Guyton, Rand Corporation + * Walter Underwood, Ford Aerospace + * Lauren Weinstein + * + * Adapted for Zimodem by Bo Zimmerman + */ + +KModem::KModem(FlowControlType commandFlow, + int (*recvChar)(ZSerial *ser, int msDelay), + void (*sendChar)(ZSerial *ser, char sym), + bool (*dataHandler)(File *kfp, unsigned long number, char *buffer, int len), + String &errors) +{ + this->errStr = &errors; + this->sendChar = sendChar; + this->recvChar = recvChar; + this->dataHandler = dataHandler; + this->kserial.setFlowControlType(FCT_DISABLED); + if(commandFlow==FCT_RTSCTS) + this->kserial.setFlowControlType(FCT_RTSCTS); + this->kserial.setPetsciiMode(false); + this->kserial.setXON(true); +} + +void KModem::flushinput() +{ + while(kserial.available()>0) + logSerialIn(kserial.read()); +} + +bool KModem::receive() +{ + state = 'R'; /* Receive-Init is the start state */ + n = 0; /* Initialize message number */ + numtry = 0; /* Say no tries yet */ + + while(TRUE) + { + if (debug) + debugPrintf(" recsw state: %c\n",state); + switch(state) /* Do until done */ + { + case 'R': + state = rinit(); + break; /* Receive-Init */ + case 'F': + state = rfile(); + break; /* Receive-File */ + case 'D': + state = rdata(); + break; /* Receive-Data */ + case 'C': + kserial.flushAlways(); + return true; /* Complete state */ + case 'A': + kserial.flushAlways(); + return false; /* "Abort" state */ + } + } +} + + +bool KModem::transmit() +{ + if (gnxtfl() == FALSE) /* No more files go? */ + { + kserial.flushAlways(); + return false; /* if not, break, EOT, all done */ + } + state = 'S'; /* Send initiate is the start state */ + n = 0; /* Initialize message number */ + numtry = 0; /* Say no tries yet */ + while(ZTRUE) /* Do this as long as necessary */ + { + if (debug) + debugPrintf("sendsw state: %c\n",state); + switch(state) + { + case 'S': + state = sinit(); + break; /* Send-Init */ + case 'F': + state = sfile(); + break; /* Send-File */ + case 'D': + state = sdata(); + break; /* Send-Data */ + case 'Z': + state = seof(); + break; /* Send-End-of-File */ + case 'B': + state = sbreak(); + break; /* Send-Break */ + case 'C': + kserial.flushAlways(); + return true; /* Complete */ + case 'A': + kserial.flushAlways(); + return false; /* "Abort" */ + default: + kserial.flushAlways(); + return false; /* Unknown, fail */ + } + } +} + +/* + * s i n i t + * + * Send Initiate: send this host's parameters and get other side's back. + */ + +char KModem::sinit() +{ + int num, len; /* Packet number, length */ + + if (numtry++ > MAXTRY) + return('A'); /* If too many tries, give up */ + spar(packet); /* Fill up init info packet */ + + flushinput(); /* Flush pending input */ + + spack('S',n,6,packet); /* Send an S packet */ + switch(rpack(&len,&num,recpkt)) /* What was the reply? */ + { + case 'N': + return(state); /* NAK, try it again */ + + case 'Y': /* ACK */ + if (n != num) /* If wrong ACK, stay in S state */ + return(state); /* and try again */ + rpar(recpkt); /* Get other side's init info */ + + if (eol == 0) + eol = '\n'; /* Check and set defaults */ + if (quote == 0) + quote = '#'; + + numtry = 0; /* Reset try counter */ + n = (n+1)%64; /* Bump packet count */ + return('F'); /* OK, switch state to F */ + + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + + case FALSE: + return(state); /* Receive failure, try again */ + + default: + return('A'); /* Anything else, just "abort" */ + } +} + + +/* + * s f i l e + * + * Send File Header. + */ + +char KModem::sfile() +{ + int num, len; /* Packet number, length */ + char filnam1[MAX_PATH],/* Converted file name */ + *newfilnam, /* Pointer to file name to send */ + *cp; /* char pointer */ + if (numtry++ > MAXTRY) + return('A'); /* If too many tries, give up */ + + if (kfpClosed) /* If not already open, */ + { + if (debug) + debugPrintf(" Opening %s for sending.\n",filnam); + kfp = kfileSystem->open(filnam,"r"); /* open the file to be sent */ + if (!kfp) /* If bad file pointer, give up */ + { + debugPrintf("Cannot open file %s",filnam); + return('A'); + } + kfpClosed=false; + } + + strcpy(filnam1, filnamo); /* Copy file name */ + newfilnam = cp = filnam1; + if (!xflg) + while (*cp != '\0') /* Strip off all leading directory */ + if (*cp++ == '/') /* names (ie. up to the last /). */ + newfilnam = cp; + + len = strlen(newfilnam); /* Compute length of new filename */ + + debugPrintf("Sending %s as %s",filnam,newfilnam); + + spack('F',n,len,newfilnam); /* Send an F packet */ + switch(rpack(&len,&num,recpkt)) /* What was the reply? */ + { + case 'N': /* NAK, just stay in this state, */ + num = (--num<0 ? 63:num); /* unless it's NAK for next packet */ + if (n != num) /* which is just like an ACK for */ + return(state); /* this packet so fall thru to... */ + case 'Y': /* ACK */ + if (n != num) + return(state); /* If wrong ACK, stay in F state */ + numtry = 0; /* Reset try counter */ + n = (n+1)%64; /* Bump packet count */ + size = bufill(packet); /* Get first data from file */ + return('D'); /* Switch state to D */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: + return(state); /* Receive failure, stay in F state */ + default: + return('A'); /* Something else, just "abort" */ + } +} + + +/* + * s d a t a + * + * Send File Data + */ + +char KModem::sdata() +{ + int num, len; /* Packet number, length */ + + if (numtry++ > MAXTRY) + return('A'); /* If too many tries, give up */ + spack('D',n,size,packet); /* Send a D packet */ + switch(rpack(&len,&num,recpkt)) /* What was the reply? */ + { + case 'N': /* NAK, just stay in this state, */ + num = (--num<0 ? 63:num); /* unless it's NAK for next packet */ + if (n != num) /* which is just like an ACK for */ + return(state); /* this packet so fall thru to... */ + case 'Y': /* ACK */ + if (n != num) + return(state); /* If wrong ACK, fail */ + numtry = 0; /* Reset try counter */ + n = (n+1)%64; /* Bump packet count */ + if ((size = bufill(packet)) == EOF) /* Get data from file */ + return('Z'); /* If EOF set state to that */ + return('D'); /* Got data, stay in state D */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: + return(state); /* Receive failure, stay in D */ + default: + return('A'); /* Anything else, "abort" */ + } +} + + +/* + * s e o f + * + * Send End-Of-File. + */ + +char KModem::seof() +{ + int num, len; /* Packet number, length */ + if (numtry++ > MAXTRY) + return('A'); /* If too many tries, "abort" */ + + spack('Z',n,0,packet); /* Send a 'Z' packet */ + switch(rpack(&len,&num,recpkt)) /* What was the reply? */ + { + case 'N': /* NAK, just stay in this state, */ + num = (--num<0 ? 63:num); /* unless it's NAK for next packet, */ + if (n != num) /* which is just like an ACK for */ + return(state); /* this packet so fall thru to... */ + case 'Y': /* ACK */ + if (n != num) + return(state); /* If wrong ACK, hold out */ + numtry = 0; /* Reset try counter */ + n = (n+1)%64; /* and bump packet count */ + if (debug) + debugPrintf(" Closing input file %s, ",filnam); + kfp.close(); /* Close the input file */ + kfpClosed = true; /* Set flag indicating no file open */ + if (debug) + debugPrintf("looking for next file...\n"); + if (gnxtfl() == FALSE) /* No more files go? */ + return('B'); /* if not, break, EOT, all done */ + if (debug) + debugPrintf(" New file is %s\n",filnam); + return('F'); /* More files, switch state to F */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: + return(state); /* Receive failure, stay in Z */ + default: + return('A'); /* Something else, "abort" */ + } +} + + +/* + * s b r e a k + * + * Send Break (EOT) + */ + +char KModem::sbreak() +{ + int num, len; /* Packet number, length */ + if (numtry++ > MAXTRY) + return('A'); /* If too many tries "abort" */ + + spack('B',n,0,packet); /* Send a B packet */ + switch (rpack(&len,&num,recpkt)) /* What was the reply? */ + { + case 'N': /* NAK, just stay in this state, */ + num = (--num<0 ? 63:num); /* unless NAK for previous packet, */ + if (n != num) /* which is just like an ACK for */ + return(state); /* this packet so fall thru to... */ + case 'Y': /* ACK */ + if (n != num) + return(state); /* If wrong ACK, fail */ + numtry = 0; /* Reset try counter */ + n = (n+1)%64; /* and bump packet count */ + return('C'); /* Switch state to Complete */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: + return(state); /* Receive failure, stay in B */ + default: + return ('A'); /* Other, "abort" */ + } +} + +/* + * r i n i t + * + * Receive Initialization + */ + +char KModem::rinit() +{ + int len, num; /* Packet length, number */ + + if (numtry++ > MAXTRY) + return('A'); /* If too many tries, "abort" */ + + char rs=rpack(&len,&num,packet); + if (debug) + debugPrintf(" recsw-rinit state: %c\n",rs); + switch(rs) /* Get a packet */ + { + case 'S': /* Send-Init */ + rpar(packet); /* Get the other side's init data */ + spar(packet); /* Fill up packet with my init info */ + spack('Y',n,6,packet); /* ACK with my parameters */ + oldtry = numtry; /* Save old try count */ + numtry = 0; /* Start a new counter */ + n = (n+1)%64; /* Bump packet number, mod 64 */ + return('F'); /* Enter File-Receive state */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: /* Didn't get packet */ + spack('N',n,0,0); /* Return a NAK */ + return(state); /* Keep trying */ + default: + return('A'); /* Some other packet type, "abort" */ + } +} + +/* + * r f i l e + * + * Receive File Header + */ + +char KModem::rfile() +{ + int num, len; /* Packet number, length */ + + if (numtry++ > MAXTRY) + return('A'); /* "abort" if too many tries */ + + char rs = rpack(&len,&num,packet); + if (debug) + debugPrintf(" recsw-rfile state: %c\n",rs); + switch(rs) /* Get a packet */ + { + case 'S': /* Send-Init, maybe our ACK lost */ + if (oldtry++ > MAXTRY) + return('A'); /* If too many tries abort */ + if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */ + { /* Yes, ACK it again with */ + spar(packet); /* our Send-Init parameters */ + spack('Y',num,6,packet); + numtry = 0; /* Reset try counter */ + return(state); /* Stay in this state */ + } + else + return('A'); /* Not previous packet, "abort" */ + case 'Z': /* End-Of-File */ + if (oldtry++ > MAXTRY) + return('A'); + if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */ + { /* Yes, ACK it again. */ + spack('Y',num,0,0); + numtry = 0; + return(state); /* Stay in this state */ + } + else + return('A'); /* Not previous packet, "abort" */ + case 'F': /* File Header (just what we want) */ + if (num != n) + return('A'); /* The packet number must be right */ + { + char filnam1[MAX_PATH]; /* Holds the converted file name */ + char *subNam=filnam1; + if(rootpath.length()>0) + { + strcpy(filnam1, rootpath.c_str()); + subNam += rootpath.length(); + if(filnam1[strlen(filnam1)-1]!='/') + { + filnam1[strlen(filnam1)]='/'; + filnam1[strlen(filnam1)+1]=0; + subNam++; + } + } + strcpy(subNam, packet); /* Copy the file name */ + kfp = kfileSystem->open(filnam1,FILE_WRITE); + if (!kfp) /* Try to open a new file */ + { + if(errStr != 0) + (*errStr) += ("Cannot create %s\n",filnam1); /* Give up if can't */ + return('A'); + } + else /* OK, give message */ + { + debugPrintf("Receiving %s as %s\n",packet,filnam1); + kfpClosed=false; + } + } + + spack('Y',n,0,0); /* Acknowledge the file header */ + oldtry = numtry; /* Reset try counters */ + numtry = 0; /* ... */ + n = (n+1)%64; /* Bump packet number, mod 64 */ + return('D'); /* Switch to Data state */ + case 'B': /* Break transmission (EOT) */ + if (num != n) + return ('A'); /* Need right packet number here */ + spack('Y',n,0,0); /* Say OK */ + return('C'); /* Go to complete state */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: /* Didn't get packet */ + spack('N',n,0,0); /* Return a NAK */ + return(state); /* Keep trying */ + default: + return ('A'); /* Some other packet, "abort" */ + } +} + +/* + * r d a t a + * + * Receive Data + */ + +char KModem::rdata() +{ + int num, len; /* Packet number, length */ + if (numtry++ > MAXTRY) + return('A'); /* "abort" if too many tries */ + + char rs=rpack(&len,&num,packet); + if (debug) + debugPrintf(" recsw-rdata state: %c\n",rs); + switch(rs) /* Get packet */ + { + case 'D': /* Got Data packet */ + if (num != n) /* Right packet? */ + { /* No */ + if (oldtry++ > MAXTRY) + return('A'); /* If too many tries, abort */ + if (num == ((n==0) ? 63:n-1)) /* Else check packet number */ + { /* Previous packet again? */ + spack('Y',num,6,packet); /* Yes, re-ACK it */ + numtry = 0; /* Reset try counter */ + return(state); /* Don't write out data! */ + } + else + return('A'); /* sorry, wrong number */ + } + /* Got data with right packet number */ + bufemp(packet,len); /* Write the data to the file */ + spack('Y',n,0,0); /* Acknowledge the packet */ + oldtry = numtry; /* Reset the try counters */ + numtry = 0; /* ... */ + n = (n+1)%64; /* Bump packet number, mod 64 */ + return('D'); /* Remain in data state */ + case 'F': /* Got a File Header */ + if (oldtry++ > MAXTRY) + return('A'); /* If too many tries, "abort" */ + if (num == ((n==0) ? 63:n-1)) /* Else check packet number */ + { /* It was the previous one */ + spack('Y',num,0,0); /* ACK it again */ + numtry = 0; /* Reset try counter */ + return(state); /* Stay in Data state */ + } + else + return('A'); /* Not previous packet, "abort" */ + case 'Z': /* End-Of-File */ + if (num != n) + return('A'); /* Must have right packet number */ + spack('Y',n,0,0); /* OK, ACK it. */ + kfp.close(); /* Close the file */ + kfpClosed=true; + n = (n+1)%64; /* Bump packet number */ + return('F'); /* Go back to Receive File state */ + case 'E': /* Error packet received */ + prerrpkt(recpkt); /* Print it out and */ + return('A'); /* abort */ + case FALSE: /* Didn't get packet */ + spack('N',n,0,0); /* Return a NAK */ + return(state); /* Keep trying */ + default: + return('A'); /* Some other packet, "abort" */ + } +} + +/* + * s p a c k + * + * Send a Packet + */ + +int KModem::spack(char type, int num, int len, char *data) +{ + int i; /* Character loop counter */ + char chksum, buffer[100]; /* Checksum, packet buffer */ + char *bufp; /* Buffer pointer */ + + if (debug>1) /* Display outgoing packet */ + { + if (data != NULL) + data[len] = '\0'; /* Null-terminate data to print it */ + debugPrintf("\n spack type: %c\n",type); + debugPrintf(" num: %d\n",num); + debugPrintf(" len: %d\n",len); + if (data != NULL) + debugPrintf(" data: \"%s\"\n",data); + } + + bufp = buffer; /* Set up buffer pointer */ + for (i=1; i<=pad; i++) + sendChar(&kserial,padchar); /* Issue any padding */ + + *bufp++ = SOH; /* Packet marker, ASCII 1 (SOH) */ + *bufp++ = (len+3+' '); /* Send the character count */ + chksum = (len+3+' '); /* Initialize the checksum */ + *bufp++ = (num+' '); /* Packet number */ + chksum += (num+' '); /* Update checksum */ + *bufp++ = type; /* Packet type */ + chksum += type; /* Update checksum */ + + for (i=0; i> 6)+chksum)&077; /* Compute final checksum */ + *bufp++ = (chksum+' '); /* Put it in the packet */ + if ( mflg ) + *bufp++ = eol; /* MacKermit needs this */ + *bufp = eol; /* Extra-packet line terminator */ + for(i=0;i MAXTIM) || (timint < MINTIM)) + timint = MYTIME; + + while (t != SOH) /* Wait for packet header */ + { + if((t=recvChar(&kserial,timint))<0) + return('A'); + t &= 0177; /* Handle parity */ + } + + done = FALSE; /* Got SOH, init loop */ + while (!done) /* Loop to get a packet */ + { + if((t=recvChar(&kserial,timint))<0) + return('A'); + if (!image) + t &= 0177; /* Handle parity */ + if (t == SOH) + continue; /* Resynchronize if SOH */ + cchksum = t; /* Start the checksum */ + *len = (t-' ')-3; /* Character count */ + + if((t=recvChar(&kserial,timint))<0) + return('A'); + if (!image) + t &= 0177; /* Handle parity */ + if (t == SOH) + continue; /* Resynchronize if SOH */ + cchksum = cchksum + t; /* Update checksum */ + *num = (t-' '); /* Packet number */ + + if((t=recvChar(&kserial,timint))<0) + return('A'); + if (!image) + t &= 0177; /* Handle parity */ + if (t == SOH) + continue; /* Resynchronize if SOH */ + cchksum = cchksum + t; /* Update checksum */ + type = t; /* Packet type */ + + for (i=0; i<*len; i++) /* The data itself, if any */ + { /* Loop for character count */ + if((t=recvChar(&kserial,timint))<0) + return('A'); + if (!image) + t &= 0177; /* Handle parity */ + if (t == SOH) + continue; /* Resynch if SOH */ + cchksum = cchksum + t; /* Update checksum */ + data[i] = t; /* Put it in the data buffer */ + } + data[*len] = 0; /* Mark the end of the data */ + + if((t=recvChar(&kserial,timint))<0) + return('A'); + rchksum = (t-' '); /* Convert to numeric */ + if((t=recvChar(&kserial,timint))<0) + return('A'); + if (!image) + t &= 0177; /* Handle parity */ + if (t == SOH) + continue; /* Resynchronize if SOH */ + done = TRUE; /* Got checksum, done */ + } + + if (debug>1) /* Display incoming packet */ + { + if (data != NULL) + data[*len] = '\0'; /* Null-terminate data to print it */ + debugPrintf("\n rpack type: %c\n",type); + debugPrintf(" num: %d\n",*num); + debugPrintf(" len: %d\n",*len); + if (data != NULL) + debugPrintf(" data: \"%s\"\n",data); + } + /* Fold in bits 7,8 to compute */ + cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */ + + if (cchksum != rchksum) + return(FALSE); + + return(type); /* All OK, return packet type */ +} + +/* + * b u f i l l + * + * Get a bufferful of data from the file that's being sent. + * Only control-quoting is done; 8-bit & repeat count prefixes are + * not handled. + */ + +int KModem::bufill(char buffer[]) +{ + int i; /* Loop index */ + char t; /* Char read from file */ + char t7; /* 7-bit version of above */ + + i = 0; /* Init data buffer pointer */ + while(dataHandler(&kfp,0,&t,1)) /* Get the next character */ + { + t7 = t & 0177; /* Get low order 7 bits */ + if (t7 < SP || t7==DEL || t7==quote) /* Does this char require */ + { /* special handling? */ + if (t=='\n' && !image) + { /* Do LF->CRLF mapping if !image */ + buffer[i++] = quote; + buffer[i++] = '\r' ^ 64; + } + buffer[i++] = quote; /* Quote the character */ + if (t7 != quote) + { + t = (t ^ 64); /* and uncontrolify */ + t7 = (t7 ^ 64); + } + } + if (image) + buffer[i++] = t; /* Deposit the character itself */ + else + buffer[i++] = t7; + if (i >= spsiz-8) + return(i); /* Check length */ + } + if (i==0) + return(EOF); /* Wind up here only on EOF */ + return(i); /* Handle partial buffer */ +} + +void KModem::bufemp(char buffer[], int len) +{ + int i; /* Counter */ + char t; /* Character holder */ + + for (i=0; ic_str(); + filnamo = filnam; + if (debug) + debugPrintf(" gnxtfl: filelist = \"%s\"\n",filnam); + return TRUE; /* else succeed */ +} + +void KModem::spar(char data[]) +{ + data[0] = (MAXPACKSIZ + ' '); /* Biggest packet I can receive */ + data[1] = (MYTIME + ' '); /* When I want to be timed out */ + data[2] = (MYPAD + ' '); /* How much padding I need */ + data[3] = (MYPCHAR ^ 64); /* Padding character I want */ + data[4] = (MYEOL + ' '); /* End-Of-Line character I want */ + data[5] = MYQUOTE; /* Control-Quote character I send */ +} + + +void KModem::setTransmitList(String **fileList, int numFiles) +{ + filelist = fileList; + filecount = numFiles; + filenum = 0; +} + + +/* r p a r + * + * Get the other host's send-init parameters + * + */ + +void KModem::rpar(char data[]) +{ + spsiz = (data[0]- ' '); /* Maximum send packet size */ + timint = (data[1]- ' '); /* When I should time out */ + pad = (data[2]- ' '); /* Number of pads to send */ + padchar = (data[3] ^ 64); /* Padding character to send */ + eol = (data[4]- ' '); /* EOL character I must send */ + quote = data[5]; /* Incoming data quote character */ +} + +/* + * p r e r r p k t + * + * Print contents of error packet received from remote host. + */ +void KModem::prerrpkt(char *msg) +{ + if(errStr != 0) + { + (*errStr)+=msg; + debugPrintf("kermit: %s\n",msg); + } +} + + +#endif diff --git a/src/proto_ping.ino b/src/proto_ping.ino new file mode 100644 index 0000000..f9487d2 --- /dev/null +++ b/src/proto_ping.ino @@ -0,0 +1,90 @@ +#include +#ifdef INCLUDE_PING +/* + Copyright 2023-2023 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#include "lwip/netdb.h" +#include "lwip/sockets.h" +#include "lwip/ip.h" +#include "lwip/icmp.h" +#include "lwip/inet_chksum.h" +#include "lwip/inet.h" + +static int ping(char *host) +{ + IPAddress hostIp((uint32_t)0); + if(!WiFiGenericClass::hostByName(host, hostIp)){ + return 1; + } + + const int socketfd = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP); + if(socketfd < 0) + return socketfd; + + const size_t pingpktLen = 10 + sizeof(struct icmp_echo_hdr); + struct icmp_echo_hdr *pingpkt = (struct icmp_echo_hdr *)malloc(pingpktLen); + ICMPH_TYPE_SET(pingpkt, ICMP_ECHO); + ICMPH_CODE_SET(pingpkt, 0); + pingpkt->id = 65535; + pingpkt->seqno = htons(1); + pingpkt->chksum = 0; + pingpkt->chksum = inet_chksum(pingpkt, pingpktLen); + + ip4_addr_t outaddr; + outaddr.addr = hostIp; + + struct sockaddr_in sockout; + sockout.sin_len = sizeof(sockout); + sockout.sin_family = AF_INET; + inet_addr_from_ip4addr(&sockout.sin_addr, &outaddr); + int ok = sendto(socketfd, pingpkt, pingpktLen, 0, (struct sockaddr*)&sockout, sizeof(sockout)); + free(pingpkt); + if (ok == 0) + { + closesocket(socketfd); + return -1; + } + + struct timeval timev; + timev.tv_sec = 5; + timev.tv_usec = 0; + if(setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)) < 0) + { + closesocket(socketfd); + return -1; + } + + uint8_t recvBuf[256]; + struct sockaddr_in inaddr; + socklen_t inlen = 0; + unsigned long time = millis(); + + if(recvfrom(socketfd, recvBuf, 256, 0, (struct sockaddr*)&inaddr, &inlen) > 0) + { + unsigned long now = millis(); + if(now > time) + time = now - time; + else + time = now; + if(time > 65536) + time = 65536; + } else { + closesocket(socketfd); + return -1; + } + closesocket(socketfd); + return (int)time; +} +#endif diff --git a/src/proto_xmodem.ino b/src/proto_xmodem.ino new file mode 100644 index 0000000..6ff8e8b --- /dev/null +++ b/src/proto_xmodem.ino @@ -0,0 +1,364 @@ +#include +/* + Copyright 2018-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifdef INCLUDE_SD_SHELL + +XModem::XModem(File &f, + FlowControlType commandFlow, + int (*recvChar)(ZSerial *ser, int msDelay), + void (*sendChar)(ZSerial *ser, char sym), + bool (*dataHandler)(File *xfile, unsigned long number, char *buffer, int len)) +{ + this->xfile = &f; + this->sendChar = sendChar; + this->recvChar = recvChar; + this->dataHandler = dataHandler; + this->xserial.setFlowControlType(FCT_DISABLED); + if(commandFlow==FCT_RTSCTS) + this->xserial.setFlowControlType(FCT_RTSCTS); + this->xserial.setPetsciiMode(false); + this->xserial.setXON(true); +} + +bool XModem::dataAvail(int delay) +{ + if (this->byte != -1) + return true; + if ((this->byte = this->recvChar(&xserial,delay)) != -1) + return true; + else + return false; +} + +int XModem::dataRead(int delay) +{ + int b; + if(this->byte != -1) + { + b = this->byte; + this->byte = -1; + return b; + } + return this->recvChar(&xserial,delay); +} + +void XModem::dataWrite(char symbol) +{ + this->sendChar(&xserial,symbol); +} + +bool XModem::receiveFrameNo() +{ + unsigned char num = (unsigned char)this->dataRead(XModem::receiveDelay); + unsigned char invnum = (unsigned char)this-> dataRead(XModem::receiveDelay); + this->repeatedBlock = false; + //check for repeated block + if (invnum == (255-num) && num == this->blockNo-1) { + this->repeatedBlock = true; + return true; + } + + if(num != this-> blockNo || invnum != (255-num)) + return false; + else + return true; +} + +bool XModem::receiveData() +{ + for(int i = 0; i < 128; i++) { + int byte = this->dataRead(XModem::receiveDelay); + if(byte != -1) + this->buffer[i] = (unsigned char)byte; + else + return false; + } + return true; +} + +bool XModem::checkCrc() +{ + unsigned short frame_crc = ((unsigned char)this->dataRead(XModem::receiveDelay)) << 8; + + frame_crc |= (unsigned char)this->dataRead(XModem::receiveDelay); + //now calculate crc on data + unsigned short crc = this->crc16_ccitt(this->buffer, 128); + + if(frame_crc != crc) + return false; + else + return true; +} + +bool XModem::checkChkSum() +{ + unsigned char frame_chksum = (unsigned char)this->dataRead(XModem::receiveDelay); + //calculate chksum + unsigned char chksum = 0; + for(int i = 0; i< 128; i++) { + chksum += this->buffer[i]; + } + if(frame_chksum == chksum) + return true; + else + return false; +} + +bool XModem::sendNack() +{ + this->dataWrite(XModem::XMO_NACK); + this->retries++; + if(this->retries < XModem::rcvRetryLimit) + return true; + else + return false; +} + +bool XModem::receiveFrames(transfer_t transfer) +{ + this->blockNo = 1; + this->blockNoExt = 1; + this->retries = 0; + while (1) { + char cmd = this->dataRead(1000); + switch(cmd){ + case XModem::XMO_SOH: + if (!this->receiveFrameNo()) { + if (this->sendNack()) + break; + else + return false; + } + if (!this->receiveData()) { + if (this->sendNack()) + break; + else + return false; + }; + if (transfer == Crc) { + if (!this->checkCrc()) { + if (this->sendNack()) + break; + else + return false; + } + } else { + if(!this->checkChkSum()) { + if (this->sendNack()) + break; + else + return false; + } + } + //callback + if(this->dataHandler != NULL && this->repeatedBlock == false) + if(!this->dataHandler(xfile,this->blockNoExt, this->buffer, 128)) { + return false; + } + //ack + this->dataWrite(XModem::XMO_ACK); + if(this->repeatedBlock == false) + { + this->blockNo++; + this->blockNoExt++; + } + break; + case XModem::XMO_EOT: + this->dataWrite(XModem::XMO_ACK); + return true; + case XModem::XMO_CAN: + //wait second CAN + if(this->dataRead(XModem::receiveDelay) ==XModem::XMO_CAN) + { + this->dataWrite(XModem::XMO_ACK); + //this->flushInput(); + return false; + } + //something wrong + this->dataWrite(XModem::XMO_CAN); + this->dataWrite(XModem::XMO_CAN); + this->dataWrite(XModem::XMO_CAN); + return false; + default: + //something wrong + this->dataWrite(XModem::XMO_CAN); + this->dataWrite(XModem::XMO_CAN); + this->dataWrite(XModem::XMO_CAN); + return false; + } + + } +} + +void XModem::init() +{ + //set preread byte + this->byte = -1; +} + +bool XModem::receive() +{ + this->init(); + + for (int i =0; i < 16; i++) + { + this->dataWrite('C'); + if (this->dataAvail(1500)) + { + bool ok = receiveFrames(Crc); + xserial.flushAlways(); + return ok; + } + + } + for (int i =0; i < 16; i++) + { + this->dataWrite(XModem::XMO_NACK); + if (this->dataAvail(1500)) + { + bool ok = receiveFrames(ChkSum); + xserial.flushAlways(); + return ok; + } + } +} + +unsigned short XModem::crc16_ccitt(char *buf, int size) +{ + unsigned short crc = 0; + while (--size >= 0) { + int i; + crc ^= (unsigned short) *buf++ << 8; + for (i = 0; i < 8; i++) + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc <<= 1; + } + return crc; +} + +unsigned char XModem::generateChkSum(void) +{ + //calculate chksum + unsigned char chksum = 0; + for(int i = 0; i< 128; i++) { + chksum += this->buffer[i]; + } + return chksum; +} + +bool XModem::transmitFrames(transfer_t transfer) +{ + this->blockNo = 1; + this->blockNoExt = 1; + // use this only in unit tetsing + //memset(this->buffer, 'A', 128); + while(1) + { + //get data + if (this->dataHandler != NULL) + { + if( false == this->dataHandler(xfile,this->blockNoExt, this->buffer, 128)) + { + //end of transfer + this->sendChar(&xserial,XModem::XMO_EOT); + //wait ACK + if (this->dataRead(XModem::receiveDelay) == XModem::XMO_ACK) + return true; + else + return false; + } + } + else + { + //cancel transfer - send CAN twice + this->sendChar(&xserial,XModem::XMO_CAN); + this->sendChar(&xserial,XModem::XMO_CAN); + //wait ACK + if (this->dataRead(XModem::receiveDelay) == XModem::XMO_ACK) + return true; + else + return false; + } + //send SOH + this->sendChar(&xserial,XModem::XMO_SOH); + //send frame number + this->sendChar(&xserial,this->blockNo); + //send inv frame number + this->sendChar(&xserial,(unsigned char)(255-(this->blockNo))); + //send data + for(int i = 0; i <128; i++) + this->sendChar(&xserial,this->buffer[i]); + //send checksum or crc + if (transfer == ChkSum) { + this->sendChar(&xserial,this->generateChkSum()); + } else { + unsigned short crc; + crc = this->crc16_ccitt(this->buffer, 128); + + this->sendChar(&xserial,(unsigned char)(crc >> 8)); + this->sendChar(&xserial,(unsigned char)(crc)); + + } + //TO DO - wait NACK or CAN or ACK + int ret = this->dataRead(XModem::receiveDelay); + switch(ret) + { + case XModem::XMO_ACK: //data is ok - go to next chunk + this->blockNo++; + this->blockNoExt++; + continue; + case XModem::XMO_NACK: //resend data + continue; + case XModem::XMO_CAN: //abort transmision + return false; + } + } + return false; +} + +bool XModem::transmit() +{ + int retry = 0; + int sym; + this->init(); + + //wait for CRC transfer + while(retry < 32) + { + if(this->dataAvail(1000)) + { + sym = this->dataRead(1); //data is here - no delay + if(sym == 'C') + { + bool ok = this->transmitFrames(Crc); + xserial.flushAlways(); + return ok; + } + if(sym == XModem::XMO_NACK) + { + bool ok = this->transmitFrames(ChkSum); + xserial.flushAlways(); + return ok; + } + } + retry++; + } + return false; +} + +#endif diff --git a/src/proto_zmodem.ino b/src/proto_zmodem.ino new file mode 100644 index 0000000..42a56ac --- /dev/null +++ b/src/proto_zmodem.ino @@ -0,0 +1,2433 @@ +#include +/* zmodem.c */ + +/* Synchronet ZMODEM Functions */ + +/* $Id: zmodem.c,v 1.120 2018/02/01 08:20:19 deuce Exp $ */ + +/******************************************************************************/ +/* Project : Unite! File : zmodem general Version : 1.02 */ +/* */ +/* (C) Mattheij Computer Service 1994 */ +/* + * Date: Thu, 19 Nov 2015 10:10:02 +0100 + * From: Jacques Mattheij + * Subject: Re: zmodem license + * To: Stephen Hurd, Fernando Toledo + * CC: Rob Swindell + * + * Hello there to all of you, + * + * So, this email will then signify as the transfer of any and all rights I + * held up to this point with relation to the copyright of the zmodem + * package as released by me many years ago and all associated files to + * Stephen Hurd. Fernando Toledo and Rob Swindell are named as + * witnesses to this transfer. + * + * ... + * + * best regards, + * + * Jacques Mattheij + ******************************************************************************/ + +#ifdef INCLUDE_SD_SHELL + +/* + * zmodem primitives and other code common to zmtx and zmrx + */ +#define ENDOFFRAME 2 +#define FRAMEOK 1 +#define TIMEOUT -1 /* rx routine did not receive a character within timeout */ +#define INVHDR -2 /* invalid header received; but within timeout */ +#define ABORTED -3 /* Aborted *or* disconnected */ +#define SUBPKTOVERFLOW -4 /* Subpacket received more than block length */ +#define CRCFAILED -5 /* Failed CRC comparison */ +#define INVALIDSUBPKT -6 /* Invalid Subpacket Type */ +#define ZDLEESC 0x8000 /* one of ZCRCE; ZCRCG; ZCRCQ or ZCRCW was received; ZDLE escaped */ + +#define BADSUBPKT 0x80 + +#define HDRLEN 5 /* size of a zmodem header */ + +ZModem::~ZModem() +{ + if(zm != 0) + free(zm); + zm = 0; +} +ZModem::ZModem(FS *zfs, void* cbdata) +{ + zfileSystem=zfs; + zm = (zmodem_t *)malloc(sizeof(zmodem_t)); + memset(zm,0,sizeof(zmodem_t)); + int log_level=LOG_DEBUG; + zm->log_level=&log_level; + zm->recv_bufsize = (ulong)1024; + zm->no_streaming = ZFALSE; + zm->want_fcs_16 =ZFALSE; + zm->escape_telnet_iac = ZTRUE; + zm->escape_8th_bit = ZFALSE; + zm->escape_ctrl_chars = ZFALSE; + + /* Use sane default values */ + zm->init_timeout=10; /* seconds */ + zm->send_timeout=10; /* seconds (reduced from 15) */ + zm->recv_timeout=10; /* seconds (reduced from 20) */ + zm->crc_timeout=120; /* seconds */ + zm->block_size=ZBLOCKLEN; + zm->max_block_size=ZBLOCKLEN; + zm->max_errors=9; + + zm->cbdata=cbdata; +} + +#if 0 + int ZModem::lputs(void* unused, int level, const char* str) + { + debugPrintf("%s\n",str); // debug off -- seems like can't print to both serials too near each other. + return ZTRUE; + } + + int ZModem::lprintf(int level, const char *fmt, ...) + { + char sbuf[1024]; + va_list argptr; + + va_start(argptr,fmt); + vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); + sbuf[sizeof(sbuf)-1]=0; + va_end(argptr); + return(lputs(NULL,level,sbuf)); + } +#else + int ZModem::lputs(void *unused, int level, const char * str) {} + int ZModem::lprintf(int level, const char *fmt, ...) {} + + #define lprintf //lprintf +#endif + +char* ZModem::getfname(const char* path) +{ + const char* fname; + const char* bslash; + + fname=strrchr(path,'/'); + bslash=strrchr(path,'\\'); + if(bslash>fname) + fname=bslash; + if(fname!=NULL) + fname++; + else + fname=(char*)path; + return((char*)fname); +} + +BOOL ZModem::is_connected() +{ + return(ZTRUE); +} + +BOOL ZModem::is_cancelled() +{ + return(zm->cancelled); +} + +int ZModem::data_waiting(unsigned timeout) +{ + timeout *= 1000; + unsigned long startTime = millis(); + while(zserial.available()==0) + { + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - startTime; + if(elapsedTime >= timeout) + return ZFALSE; + delay(1); + } + return ZTRUE; +} + +/* + * show the progress of the transfer like this: + * zmtx: sending file "garbage" 4096 bytes ( 20%) + */ +void ZModem::progress(void* cbdata, int64_t current_pos) +{ + //debugPrintf("POGRESS %lld\n",current_pos); + // do nothing? +} + +int ZModem::send_byte(void* unused, uchar ch, unsigned timeout) +{ + lprintf(LOG_DEBUG, "Send: %d", ch); + zserial.printb(ch); + //zserial.flush(); // safe flush + return(0); +} + +int ZModem::recv_byte(void* unused, unsigned timeout /* seconds */) +{ + unsigned long startTime = millis(); + timeout *= 1000; + while(zserial.available()==0) + { + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - startTime; + if(elapsedTime >= timeout) + return(NOINP); + delay(1); + yield(); + } + int ch = zserial.read(); + lprintf(LOG_DEBUG, "Recvd: %d", ch); + return ch; +} + +static char *chr(int ch) +{ + static char str[25]; + + switch(ch) { + case TIMEOUT: return("TIMEOUT"); + case ABORTED: return("ABORTED"); + case SUBPKTOVERFLOW: return "Subpacket Overflow"; + case CRCFAILED: return "CRC Failure"; + case INVALIDSUBPKT: return "Invalid Subpacket"; + case ZRQINIT: return("ZRQINIT"); + case ZRINIT: return("ZRINIT"); + case ZSINIT: return("ZSINIT"); + case ZACK: return("ZACK"); + case ZFILE: return("ZFILE"); + case ZSKIP: return("ZSKIP"); + case ZCRC: return("ZCRC"); + case ZNAK: return("ZNAK"); + case ZABORT: return("ZABORT"); + case ZFIN: return("ZFIN"); + case ZRPOS: return("ZRPOS"); + case ZDATA: return("ZDATA"); + case ZEOF: return("ZEOF"); + case ZFERR: return("ZFERR"); + case ZPAD: return("ZPAD"); + case ZCAN: return("ZCAN"); + case ZDLE: return("ZDLE"); + case ZDLEE: return("ZDLEE"); + case ZBIN: return("ZBIN"); + case ZHEX: return("ZHEX"); + case ZBIN32: return("ZBIN32"); + case ZRESC: return("ZRESC"); + case ZCRCE: return("ZCRCE"); + case ZCRCG: return("ZCRCG"); + case ZCRCQ: return("ZCRCQ"); + case ZCRCW: return("ZCRCW"); + + } + if(ch<0) + sprintf(str,"%d",ch); + else if(ch>=' ' && ch<='~') + sprintf(str,"'%c' (%02Xh)",(uchar)ch,(uchar)ch); + else + sprintf(str,"%u (%02Xh)",(uchar)ch,(uchar)ch); + return(str); +} + +static char* frame_desc(int frame) +{ + static char str[25]; + + if(frame==TIMEOUT) + return "TIMEOUT"; + + if(frame==INVHDR) + return "Invalid Header"; + + if(frame==ABORTED) + return "Aborted"; + + if(frame >= 0 && (frame&BADSUBPKT)) { + strcpy(str,"BAD "); + switch(frame&~BADSUBPKT) { + case ZRQINIT: strcat(str,"ZRQINIT"); break; + case ZRINIT: strcat(str,"ZRINIT"); break; + case ZSINIT: strcat(str,"ZSINIT"); break; + case ZACK: strcat(str,"ZACK"); break; + case ZFILE: strcat(str,"ZFILE"); break; + case ZSKIP: strcat(str,"ZSKIP"); break; + case ZNAK: strcat(str,"ZNAK"); break; + case ZABORT: strcat(str,"ZABORT"); break; + case ZFIN: strcat(str,"ZFIN"); break; + case ZRPOS: strcat(str,"ZRPOS"); break; + case ZDATA: strcat(str,"ZDATA"); break; + case ZEOF: strcat(str,"ZEOF"); break; + case ZFERR: strcat(str,"ZFERR"); break; + case ZCRC: strcat(str,"ZCRC"); break; + case ZCHALLENGE: strcat(str,"ZCHALLENGE"); break; + case ZCOMPL: strcat(str,"ZCOMPL"); break; + case ZCAN: strcat(str,"ZCAN"); break; + case ZFREECNT: strcat(str,"ZFREECNT"); break; + case ZCOMMAND: strcat(str,"ZCOMMAND"); break; + case ZSTDERR: strcat(str,"ZSTDERR"); break; + default: + sprintf(str,"Unknown (%08X)", frame); + break; + } + } else + sprintf(str,"%d",frame); + return(str); +} + +ulong ZModem::frame_pos(int type) +{ + switch(type) { + case ZRPOS: + case ZACK: + case ZEOF: + case ZDATA: + return(zm->rxd_header_pos); + } + + return 0; +} + +/* + * read bytes as long as rdchk indicates that + * more data is available. + */ + +void ZModem::recv_purge() +{ + while(recv_byte(zm->cbdata,0)>=0); +} + +/* + * Flush the output buffer + */ +void ZModem::flush() +{ + zserial.flush(); +} + +uint32_t ucrc32(unsigned char p, uint32_t crc) +{ + int c; + crc ^= p; + for(int c = 8; c-- > 0; ) + crc = (crc >> 1) ^ (0xEDB88320UL & ~((crc & 1l)-1l)); + return crc; +} + +uint32_t ucrc32(char *buf, int size) +{ + uint32_t crc = 0xffffffffL; + while (--size >= 0) { + crc = ucrc32(*buf++, crc); + } + return crc; +} + +uint16_t ucrc16(unsigned char p, uint16_t crc) +{ + int i; + crc ^= (uint16_t) p << 8; + for (i = 0; i < 8; i++) + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc <<= 1; + return crc; +} + +uint16_t ucrc16(char *buf, int size) +{ + uint16_t crc = 0; + while (--size >= 0) { + crc = ucrc16(*buf++, crc); + } + return crc; +} + +uint32_t fcrc32(File* fp, unsigned long len) +{ + int ch; + uint32_t crc=0xffffffff; + unsigned long l; + + fp->seek(0); + for(l=0;(len==0 || lavailable()>0);l++) { + if((ch=fp->read())<0) + break; + crc=ucrc32(ch,crc); + } + return(~crc); +} + +/* + * transmit a character. + * this is the raw modem interface + */ +/* Returns 0 on success */ +int ZModem::send_raw(unsigned char ch) +{ + int result; + + if((result=send_byte(zm->cbdata,ch,zm->send_timeout))!=0) + { + lprintf(LOG_ERR,"send_raw SEND ERROR: %d",result); + } + + zm->last_sent = ch; + + return result; +} + +/* + * transmit a character ZDLE escaped + */ +int ZModem::send_esc(unsigned char c) +{ + int result; + + if((result=send_raw( ZDLE))!=0) + return(result); + /* + * exclusive or; not an or so ZDLE becomes ZDLEE + */ + return send_raw( (uchar)(c ^ 0x40)); +} + +/* + * transmit a character; ZDLE escaping if appropriate + */ +int ZModem::tx(unsigned char c) +{ + int result; + + switch (c) { + case ZMO_DLE: + case ZMO_DLE|0x80: /* even if high-bit set */ + case ZMO_XON: + case ZMO_XON|0x80: + case ZMO_XOFF: + case ZMO_XOFF|0x80: + case ZDLE: + return send_esc( c); + case 0x0d: + case 0x0d|0x80: + if(zm->escape_ctrl_chars && (zm->last_sent&0x7f) == '@') + return send_esc( c); + break; + case TELNET_IAC: + if(zm->escape_telnet_iac) { + if((result=send_raw( ZDLE))!=0) + return(result); + return send_raw( ZRUB1); + } + break; + default: + if(zm->escape_ctrl_chars && (c&0x60)==0) + return send_esc( c); + break; + } + /* + * anything that ends here is so normal we might as well transmit it. + */ + return send_raw( c); +} + +/**********************************************/ +/* Output single byte as two hex ASCII digits */ +/**********************************************/ +int ZModem::send_hex(uchar val) +{ + char* xdigit="0123456789abcdef"; + int result; + + lprintf(LOG_DEBUG,"send_hex: %02X ",val); + + if((result=send_raw( xdigit[val>>4]))!=0) + return result; + return send_raw( xdigit[val&0xf]); +} + +int ZModem::send_padded_zdle() +{ + int result; + delay(100); //dammit, this might fix something. + if((result=send_raw( ZPAD))!=0) + return result; + if((result=send_raw( ZPAD))!=0) + return result; + return send_raw( ZDLE); +} + +/* + * transmit a hex header. + * these routines use tx_raw because we're sure that all the + * characters are not to be escaped. + */ +int ZModem::send_hex_header(unsigned char * p) +{ + int i; + int result; + uchar type=*p; + unsigned short int crc; + + lprintf(LOG_DEBUG,"send_hex_header: %s", chr(type)); + + if((result=send_padded_zdle())!=0) + return result; + + if((result=send_raw( ZHEX))!=0) + return result; + + /* + * initialise the crc + */ + + crc = 0; + + /* + * transmit the header + */ + + for(i=0;i>8)))!=0) + return result; + if((result=send_hex( (uchar)(crc&0xff)))!=0) + return result; + + /* + * end of line sequence + */ + + if((result=send_raw( '\r'))!=0) + return result; + if((result=send_raw( '\n'))!=0) /* FDSZ sends 0x8a instead of 0x0a */ + return result; + + if(type!=ZACK && type!=ZFIN) + result=send_raw( ZMO_XON); //TODO:BZ:SUSPICIOUS + + flush(); + + return(result); +} + +/* + * Send ZMODEM binary header hdr + */ +int ZModem::send_bin32_header(unsigned char * p) +{ + int i; + int result; + uint32_t crc; + + lprintf(LOG_DEBUG,"send_bin32_header: %s", chr(*p)); + + if((result=send_padded_zdle())!=0) + return result; + + if((result=send_raw( ZBIN32))!=0) + return result; + + crc = 0xffffffffL; + + for(i=0;i> 8) & 0xff)))!=0) + return result; + if((result= tx( (uchar)((crc >> 16) & 0xff)))!=0) + return result; + return tx( (uchar)((crc >> 24) & 0xff)); +} + +int ZModem::send_bin16_header(unsigned char * p) +{ + int i; + int result; + unsigned int crc; + + lprintf(LOG_DEBUG,"send_bin16_header: %s", chr(*p)); + + if((result=send_padded_zdle())!=0) + return result; + + if((result=send_raw( ZBIN))!=0) + return result; + + crc = 0; + + for(i=0;i> 8)))!=0) + return result; + return tx( (uchar)(crc&0xff)); +} + + +/* + * transmit a header using either hex 16 bit crc or binary 32 bit crc + * depending on the receivers capabilities + * we dont bother with variable length headers. I dont really see their + * advantage and they would clutter the code unneccesarily + */ +int ZModem::send_bin_header(unsigned char * p) +{ + if(zm->can_fcs_32 && !zm->want_fcs_16) + return send_bin32_header( p); + return send_bin16_header( p); +} + +/* + * data subpacket transmission + */ +int ZModem::send_data32(uchar subpkt_type, unsigned char * p, size_t l) +{ + int result; + uint32_t crc; + + lprintf(LOG_DEBUG,"send_data32: %s (%u bytes)", chr(subpkt_type), l); + + crc = 0xffffffffl; + + while(l > 0) { + crc = ucrc32(*p,crc); + if((result=tx( *p++))!=0) + return result; + l--; + } + + crc = ucrc32(subpkt_type, crc); + + if((result=send_raw( ZDLE))!=0) + return result; + if((result=send_raw( subpkt_type))!=0) + return result; + + crc = ~crc; + + if((result= tx( (uchar) ((crc ) & 0xff)))!=0) + return result; + if((result= tx( (uchar) ((crc >> 8 ) & 0xff)))!=0) + return result; + if((result= tx( (uchar) ((crc >> 16) & 0xff)))!=0) + return result; + return tx( (uchar) ((crc >> 24) & 0xff)); +} + +int ZModem::send_data16(uchar subpkt_type,unsigned char * p, size_t l) +{ + int result; + unsigned short crc; + + lprintf(LOG_DEBUG,"send_data16: %s (%u bytes)", chr(subpkt_type), l); + + crc = 0; + + while(l > 0) { + crc = ucrc16(*p,crc); + if((result=tx( *p++))!=0) + return result; + l--; + } + + crc = ucrc16(subpkt_type,crc); + + if((result=send_raw( ZDLE))!=0) + return result; + if((result=send_raw( subpkt_type))!=0) + return result; + + if((result= tx( (uchar)(crc >> 8)))!=0) + return result; + return tx( (uchar)(crc&0xff)); +} + +/* + * send a data subpacket using crc 16 or crc 32 as desired by the receiver + */ +int ZModem::send_data_subpkt(uchar subpkt_type, unsigned char * p, size_t l) +{ + int result; + + if(subpkt_type == ZCRCW || subpkt_type == ZCRCE) /* subpacket indicating 'end-of-frame' */ + zm->frame_in_transit=ZFALSE; + else /* other subpacket (mid-frame) */ + zm->frame_in_transit=ZTRUE; + + if(!zm->want_fcs_16 && zm->can_fcs_32) { + if((result=send_data32( subpkt_type,p,l))!=0) + return result; + } + else { + if((result=send_data16( subpkt_type,p,l))!=0) + return result; + } + + if(subpkt_type == ZCRCW) + result=send_raw( 0x11/*XON*/); + + flush(); + + return result; +} + +int ZModem::send_data(uchar subpkt_type, unsigned char * p, size_t l) +{ + if(!zm->frame_in_transit) { /* Start of frame, include ZDATA header */ + lprintf(LOG_DEBUG,"send_data: start of frame, offset %u" ,zm->current_file_pos); + send_pos_header( ZDATA, (uint32_t)zm->current_file_pos, /* Hex? */ ZFALSE); + } + + return send_data_subpkt( subpkt_type, p, l); +} + +int ZModem::send_pos_header(int type, int32_t pos, BOOL hex) +{ + uchar header[5]; + + header[0] = type; + header[ZP0] = (uchar) (pos & 0xff); + header[ZP1] = (uchar)((pos >> 8) & 0xff); + header[ZP2] = (uchar)((pos >> 16) & 0xff); + header[ZP3] = (uchar)((pos >> 24) & 0xff); + + if(hex) + return send_hex_header( header); + else + return send_bin_header( header); +} + +int ZModem::send_ack(int32_t pos) +{ + return send_pos_header( ZACK, pos, /* Hex? */ ZTRUE); +} + +int ZModem::send_zfin() +{ + unsigned char zfin_header[] = { ZFIN, 0, 0, 0, 0 }; + + lprintf(LOG_NOTICE,"Finishing Session (Sending ZFIN)"); + return send_hex_header(zfin_header); +} + +int ZModem::send_zabort() +{ + lprintf(LOG_WARNING,"Aborting Transfer (Sending ZABORT)"); + return send_pos_header( ZABORT, 0, /* Hex? */ ZTRUE); +} + +int ZModem::send_znak() +{ + lprintf(LOG_INFO,"Sending ZNAK"); + return send_pos_header( ZNAK, 0, /* Hex? */ ZTRUE); +} + +int ZModem::send_zskip() +{ + lprintf(LOG_INFO,"Sending ZSKIP"); + return send_pos_header( ZSKIP, 0L, /* Hex? */ ZTRUE); +} + +int ZModem::send_zeof(uint32_t pos) +{ + lprintf(LOG_INFO,"Sending End-of-File (ZEOF) frame (pos=%lu)", pos); + return send_pos_header( ZEOF, pos, /* Hex? */ ZTRUE); +} + + +/* + * rx_raw ; receive a single byte from the line. + * reads as many are available and then processes them one at a time + * check the data stream for 5 consecutive 0x18 CAN characters; + * and if you see them abort. this saves a lot of clutter in + * the rest of the code; even though it is a very strange place + * for an exit. (but that was wat session abort was all about.) + */ + +int ZModem::recv_raw() +{ + int c; + unsigned attempt; + + for(attempt=0;attempt<=zm->recv_timeout;attempt++) { + if((c=recv_byte(zm->cbdata,1)) >= 0) /* second timeout */ + break; + if(is_cancelled()) + return(ZCAN); + if(!is_connected()) + return(ABORTED); + } + if(attempt>zm->recv_timeout) + return(TIMEOUT); + + if(c == ZMO_CAN) { + zm->n_cans++; + if(zm->n_cans == 5) { + zm->cancelled=ZTRUE; + lprintf(LOG_WARNING,"recv_raw: Cancelled remotely"); + /*return(TIMEOUT); removed June-12-2005 */ + } + } + else { + zm->n_cans = 0; + } + + return c; +} + +/* + * rx; receive a single byte undoing any escaping at the + * sending site. this bit looks like a mess. sorry for that + * but there seems to be no other way without incurring a lot + * of overhead. at least like this the path for a normal character + * is relatively short. + */ + +int ZModem::rx() +{ + int c; + + /* + * outer loop for ever so for sure something valid + * will come in; a timeout will occur or a session abort + * will be received. + */ + + while(is_connected() && !is_cancelled()) { + + do { + switch(c = recv_raw()) { + case ZDLE: + break; + case ZMO_XON: + case ZMO_XON|0x80: + case ZMO_XOFF: + case ZMO_XOFF|0x80: + lprintf(LOG_WARNING,"rx: dropping flow ctrl char: %s" ,chr(c)); + continue; + default: + /* + * if all control characters should be escaped and + * this one wasnt then its spurious and should be dropped. + */ + if(zm->escape_ctrl_chars && (c >= 0) && (c & 0x60) == 0) { + lprintf(LOG_WARNING,"rx: dropping unescaped ctrl char: %s" ,chr(c)); + continue; + } + /* + * normal character; return it. + */ + return c; + } + break; + } while(!is_cancelled()); + + /* + * ZDLE encoded sequence or session abort. + * (or something illegal; then back to the top) + */ + + while(!is_cancelled()) { + + switch(c=recv_raw()) { + case ZMO_XON: + case ZMO_XON|0x80: + case ZMO_XOFF: + case ZMO_XOFF|0x80: + case ZDLE: + lprintf(LOG_WARNING,"rx: dropping escaped flow ctrl char: %s" ,chr(c)); + continue; + /* + * these four are really nasty. + * for convenience we just change them into + * special characters by setting a bit outside the + * first 8. that way they can be recognized and still + * be processed as characters by the rest of the code. + */ + case ZCRCE: + case ZCRCG: + case ZCRCQ: + case ZCRCW: + lprintf(LOG_DEBUG,"rx: encoding data subpacket type: %s" ,chr(c)); + return (c | ZDLEESC); + case ZRUB0: + return 0x7f; + case ZRUB1: + return 0xff; + default: + if(c < 0) + return c; + + if(zm->escape_ctrl_chars && (c & 0x60) == 0) { + /* + * a not escaped control character; probably + * something from a network. just drop it. + */ + lprintf(LOG_WARNING,"rx: dropping unescaped ctrl char: %s" ,chr(c)); + continue; + } + /* + * legitimate escape sequence. + * rebuild the orignal and return it. + */ + if((c & 0x60) == 0x40) { + return c ^ 0x40; + } + lprintf(LOG_WARNING,"rx: illegal sequence: ZDLE %s" ,chr(c)); + break; + } + break; + } + } + + /* + * not reached (unless cancelled). + */ + + return ABORTED; +} + +/* + * receive a data subpacket as dictated by the last received header. + * return 2 with correct packet and end of frame + * return 1 with correct packet frame continues + * return 0 with incorrect frame. + * return TIMEOUT with a timeout + * if an acknowledgement is requested it is generated automatically + * here. + */ + +/* + * data subpacket reception + */ + +int ZModem::recv_data32(unsigned char * p, unsigned maxlen, unsigned* l) +{ + int c; + uint32_t rxd_crc; + uint32_t crc; + int subpkt_type; + + lprintf(LOG_DEBUG,"recv_data32"); + + crc = 0xffffffffl; + + do { + c = rx(); + + if(c < 0) + return c; + + if(c > 0xff) + break; + + if(*l >= maxlen) + return SUBPKTOVERFLOW; + crc = ucrc32(c,crc); + *p++ = c; + (*l)++; + } while(1); + + subpkt_type = c & 0xff; + + crc = ucrc32(subpkt_type, crc); + + crc = ~crc; + + rxd_crc = rx(); + rxd_crc |= rx() << 8; + rxd_crc |= rx() << 16; + rxd_crc |= rx() << 24; + + if(rxd_crc != crc) { + lprintf(LOG_WARNING,"CRC32 ERROR (%08lX, expected: %08lX) Bytes=%u, subpacket-type=%s" ,rxd_crc, crc, *l, chr(subpkt_type)); + return CRCFAILED; + } + lprintf(LOG_DEBUG,"GOOD CRC32: %08lX (Bytes=%u, subpacket-type=%s)" ,crc, *l, chr(subpkt_type)); + + zm->ack_file_pos += *l; + + return subpkt_type; +} + +int ZModem::recv_data16(register unsigned char* p, unsigned maxlen, unsigned* l) +{ + int c; + int subpkt_type; + unsigned short crc; + unsigned short rxd_crc; + + lprintf(LOG_DEBUG,"recv_data16"); + + crc = 0; + + do { + c = rx(); + + if(c < 0) + return c; + + if(c > 0xff) + break; + + if(*l >= maxlen) + return SUBPKTOVERFLOW; + crc = ucrc16(c,crc); + *p++ = c; + (*l)++; + } while(1); + + subpkt_type = c & 0xff; + + crc = ucrc16(subpkt_type,crc); + + rxd_crc = rx() << 8; + rxd_crc |= rx(); + + if(rxd_crc != crc) { + lprintf(LOG_WARNING,"CRC16 ERROR (%04hX, expected: %04hX) Bytes=%d" ,rxd_crc, crc, *l); + return CRCFAILED; + } + lprintf(LOG_DEBUG,"GOOD CRC16: %04hX (Bytes=%d)", crc, *l); + + zm->ack_file_pos += *l; + + return subpkt_type; +} + +int ZModem::recv_data(unsigned char* p, size_t maxlen, unsigned* l, BOOL ack) +{ + int subpkt_type; + unsigned n=0; + + if(l==NULL) + l=&n; + + lprintf(LOG_DEBUG,"recv_data (%u-bit)", zm->receive_32bit_data ? 32:16); + + /* + * receive the right type of frame + */ + + *l = 0; + + if(zm->receive_32bit_data) { + subpkt_type = recv_data32( p, maxlen, l); + } + else { + subpkt_type = recv_data16( p, maxlen, l); + } + + if(subpkt_type <= 0) /* e.g. TIMEOUT, SUBPKTOVERFLOW, CRCFAILED */ + return(subpkt_type); + + lprintf(LOG_DEBUG,"recv_data received subpacket-type: %s" ,chr(subpkt_type)); + + switch(subpkt_type) { + /* + * frame continues non-stop + */ + case ZCRCG: + return FRAMEOK; + /* + * frame ends + */ + case ZCRCE: + return ENDOFFRAME; + /* + * frame continues; ZACK expected + */ + case ZCRCQ: + if(ack) + send_ack( zm->ack_file_pos); + return FRAMEOK; + /* + * frame ends; ZACK expected + */ + case ZCRCW: + if(ack) + send_ack( zm->ack_file_pos); + return ENDOFFRAME; + } + + lprintf(LOG_WARNING,"Received invalid subpacket-type: %s", chr(subpkt_type)); + + return INVALIDSUBPKT; +} + +BOOL ZModem::recv_subpacket(BOOL ack) +{ + int type; + + type=recv_data(zm->rx_data_subpacket,sizeof(zm->rx_data_subpacket),NULL,ack); + if(type!=FRAMEOK && type!=ENDOFFRAME) { + send_znak(); + return(ZFALSE); + } + + return(ZTRUE); +} + +int ZModem::recv_nibble() +{ + int c; + + c = rx(); + + if(c < 0) + return c; + + if(c > '9') { + if(c < 'a' || c > 'f') { + /* + * illegal hex; different than expected. + * we might as well time out. + */ + return -1; + } + + c -= 'a' - 10; + } + else { + if(c < '0') { + /* + * illegal hex; different than expected. + * we might as well time out. + */ + return -1; + } + c -= '0'; + } + + return c; +} + +int ZModem::recv_hex() +{ + int n1; + int n0; + int ret; + + n1 = recv_nibble(); + + if(n1 < 0) + return n1; + + n0 = recv_nibble(); + + if(n0 < 0) + return n0; + + ret = (n1 << 4) | n0; + + lprintf(LOG_DEBUG,"recv_hex returning: 0x%02X", ret); + + return ret; +} + +/* + * receive routines for each of the six different styles of header. + * each of these leaves zm->rxd_header_len set to 0 if the end result is + * not a valid header. + */ +BOOL ZModem::recv_bin16_header() +{ + int c; + int n; + unsigned short int crc; + unsigned short int rxd_crc; + + lprintf(LOG_DEBUG,"recv_bin16_header"); + + crc = 0; + + for(n=0;nrxd_header[n] = c; + } + + rxd_crc = rx() << 8; + rxd_crc |= rx(); + + if(rxd_crc != crc) { + lprintf(LOG_WARNING,"CRC16 ERROR: 0x%hX, expected: 0x%hX", rxd_crc, crc); + return(ZFALSE); + } + lprintf(LOG_DEBUG,"GOOD CRC16: %04hX", crc); + + zm->rxd_header_len = 5; + + return(ZTRUE); +} + +BOOL ZModem::recv_hex_header() +{ + int c; + int i; + unsigned short int crc = 0; + unsigned short int rxd_crc; + + lprintf(LOG_DEBUG,"recv_hex_header"); + + for(i=0;irxd_header[i] = c; + } + + /* + * receive the crc + */ + + c = recv_hex(); + + if(c < 0) + return ZFALSE; + + rxd_crc = c << 8; + + c = recv_hex(); + + if(c < 0 ) + return ZFALSE; + + rxd_crc |= c; + + if(rxd_crc == crc) { + lprintf(LOG_DEBUG,"GOOD CRC16: %04hX", crc); + zm->rxd_header_len = 5; + } + else { + lprintf(LOG_WARNING,"CRC16 ERROR: 0x%hX, expected: 0x%hX", rxd_crc, crc); + return ZFALSE; + } + + /* + * drop the end of line sequence after a hex header + */ + c = rx(); + if(c == '\r') { + /* + * both are expected with 0x0d + */ + rx(); /* drop 0x0a */ + } + + return ZTRUE; +} + +BOOL ZModem::recv_bin32_header() +{ + int c; + int n; + uint32_t crc; + uint32_t rxd_crc; + + lprintf(LOG_DEBUG,"recv_bin32_header"); + + crc = 0xffffffffL; + + for(n=0;nrxd_header[n] = c; + } + + crc = ~crc; + + rxd_crc = rx(); + rxd_crc |= rx() << 8; + rxd_crc |= rx() << 16; + rxd_crc |= rx() << 24; + + if(rxd_crc != crc) { + lprintf(LOG_WARNING,"CRC32 ERROR (%08lX, expected: %08lX)" ,rxd_crc, crc); + return(ZFALSE); + } + lprintf(LOG_DEBUG,"GOOD CRC32: %08lX", crc); + + zm->rxd_header_len = 5; + return(ZTRUE); +} + +/* + * receive any style header + * if the errors flag is set than whenever an invalid header packet is + * received INVHDR will be returned. otherwise we wait for a good header + * also; a flag (receive_32bit_data) will be set to indicate whether data + * packets following this header will have 16 or 32 bit data attached. + * variable headers are not implemented. + */ +int ZModem::recv_header_raw(int errors) +{ + int c; + int frame_type; + + lprintf(LOG_DEBUG,"recv_header_raw"); + + zm->rxd_header_len = 0; + + do { + do { + if((c = recv_raw()) < 0) + return(c); + if(is_cancelled()) + return(ZCAN); + } while(c != ZPAD); + + if((c = recv_raw()) < 0) + return(c); + + if(c == ZPAD) { + if((c = recv_raw()) < 0) + return(c); + } + + /* + * spurious ZPAD check + */ + + if(c != ZDLE) { + lprintf(LOG_WARNING,"recv_header_raw: Expected ZDLE, received: %s" ,chr(c)); + continue; + } + + /* + * now read the header style + */ + c = rx(); + switch (c) { + case ZBIN: + if(!recv_bin16_header()) + return INVHDR; + zm->receive_32bit_data = ZFALSE; + break; + case ZHEX: + if(!recv_hex_header()) + return INVHDR; + zm->receive_32bit_data = ZFALSE; + break; + case ZBIN32: + if(!recv_bin32_header()) + return INVHDR; + zm->receive_32bit_data = ZTRUE; + break; + default: + if(c < 0) { + lprintf(LOG_WARNING,"recv_header_raw: %s", chr(c)); + return c; + } + /* + * unrecognized header style + */ + lprintf(LOG_ERR,"recv_header_raw: UNRECOGNIZED header style: %s" ,chr(c)); + if(errors) { + return INVHDR; + } + + continue; + } + if(errors && zm->rxd_header_len == 0) { + return INVHDR; + } + + } while(zm->rxd_header_len == 0 && !is_cancelled()); + + if(is_cancelled()) + return(ZCAN); + + /* + * this appears to have been a valid header. + * return its type. + */ + + frame_type = zm->rxd_header[0]; + + zm->rxd_header_pos = zm->rxd_header[ZP0] | (zm->rxd_header[ZP1] << 8) | + (zm->rxd_header[ZP2] << 16) | (zm->rxd_header[ZP3] << 24); + + switch(frame_type) { + case ZCRC: + zm->crc_request = zm->rxd_header_pos; + break; + case ZDATA: + zm->ack_file_pos = zm->rxd_header_pos; + break; + case ZFILE: + zm->ack_file_pos = 0l; + if(!recv_subpacket(/* ack? */ZFALSE)) + frame_type |= BADSUBPKT; + break; + case ZSINIT: + case ZCOMMAND: + if(!recv_subpacket(/* ack? */ZTRUE)) + frame_type |= BADSUBPKT; + break; + case ZFREECNT: + send_pos_header( ZACK, 999999999L, /* Hex? */ ZTRUE); + break; + } + + lprintf(LOG_DEBUG,"recv_header_raw received header type: %s",frame_desc(frame_type)); + + return frame_type; +} + +int ZModem::recv_header() +{ + int ret; + + switch(ret = recv_header_raw( ZFALSE)) { + case TIMEOUT: + lprintf(LOG_WARNING,"recv_header TIMEOUT"); + break; + case INVHDR: + lprintf(LOG_WARNING,"recv_header detected an invalid header"); + break; + default: + lprintf(LOG_DEBUG,"recv_header returning: %s (pos=%lu)" ,frame_desc(ret), frame_pos( ret)); + + if(ret==ZCAN) + zm->cancelled=ZTRUE; + else if(ret==ZRINIT) + parse_zrinit(); + break; + } + + return ret; +} + +int ZModem::recv_header_and_check() +{ + int type=ABORTED; + + while(is_connected() && !is_cancelled()) { + type = recv_header_raw(ZTRUE); + + if(type == TIMEOUT) + break; + + if(type != INVHDR && (type&BADSUBPKT) == 0) + break; + + send_znak(); + } + + lprintf(LOG_DEBUG,"recv_header_and_check returning: %s (pos=%lu)",frame_desc(type), frame_pos( type)); + + if(type==ZCAN) + zm->cancelled=ZTRUE; + + return type; +} + +BOOL ZModem::request_crc(int32_t length) +{ + recv_purge(); + send_pos_header(ZCRC,length,ZTRUE); + return ZTRUE; +} + +BOOL ZModem::recv_crc(uint32_t* crc) +{ + int type; + + if(!data_waiting(zm->crc_timeout)) { + lprintf(LOG_ERR,"Timeout waiting for response (%u seconds)", zm->crc_timeout); + return(ZFALSE); + } + if((type=recv_header())!=ZCRC) { + lprintf(LOG_ERR,"Received %s instead of ZCRC", frame_desc(type)); + return(ZFALSE); + } + if(crc!=NULL) + *crc = zm->crc_request; + return ZTRUE; +} + +BOOL ZModem::get_crc(int32_t length, uint32_t* crc) +{ + if(request_crc( length)) + return recv_crc( crc); + return ZFALSE; +} + +void ZModem::parse_zrinit() +{ + zm->can_full_duplex = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_CANFDX); + zm->can_overlap_io = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_CANOVIO); + zm->can_break = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_CANBRK); + zm->can_fcs_32 = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_CANFC32); + zm->escape_ctrl_chars = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_ESCCTL); + zm->escape_8th_bit = INT_TO_BOOL(zm->rxd_header[ZF0] & ZF0_ESC8); + + /* + lprintf(LOG_INFO,"Receiver requested mode (0x%02X):\r\n" + "%s-duplex, %s overlap I/O, CRC-%u, Escape: %s" + ,zm->rxd_header[ZF0] + ,zm->can_full_duplex ? "Full" : "Half" + ,zm->can_overlap_io ? "Can" : "Cannot" + ,zm->can_fcs_32 ? 32 : 16 + ,zm->escape_ctrl_chars ? "ALL" : "Normal" + ); + */ + + if((zm->recv_bufsize = (zm->rxd_header[ZP0] | zm->rxd_header[ZP1]<<8)) != 0) + { + lprintf(LOG_INFO,"Receiver specified buffer size of: %u", zm->recv_bufsize); + } +} + +int ZModem::get_zrinit() +{ + unsigned char zrqinit_header[] = { ZRQINIT, /* ZF3: */0, 0, 0, /* ZF0: */0 }; + /* Note: sz/dsz/fdsz sends 0x80 in ZF3 because it supports var-length headers. */ + /* We do not, so we send 0x00, resulting in a CRC-16 value of 0x0000 as well. */ + + send_raw('r'); + send_raw('z'); + send_raw('\r'); + send_hex_header(zrqinit_header); + + if(!data_waiting(zm->init_timeout)) + return(TIMEOUT); + return recv_header(); +} + +int ZModem::send_zrinit() +{ + unsigned char zrinit_header[] = { ZRINIT, 0, 0, 0, 0 }; + + zrinit_header[ZF0] = ZF0_CANFDX; + + if(!zm->no_streaming) + zrinit_header[ZF0] |= ZF0_CANOVIO; + + if(zm->can_break) + zrinit_header[ZF0] |= ZF0_CANBRK; + + if(!zm->want_fcs_16) + zrinit_header[ZF0] |= ZF0_CANFC32; + + if(zm->escape_ctrl_chars) + zrinit_header[ZF0] |= ZF0_ESCCTL; + + if(zm->escape_8th_bit) + zrinit_header[ZF0] |= ZF0_ESC8; + + if(zm->no_streaming && zm->recv_bufsize==0) + zm->recv_bufsize = sizeof(zm->rx_data_subpacket); + + zrinit_header[ZP0] = zm->recv_bufsize & 0xff; + zrinit_header[ZP1] = zm->recv_bufsize >> 8; + + return send_hex_header( zrinit_header); +} + +/* Returns ZFIN on success */ +int ZModem::get_zfin() +{ + int result; + int type=ZCAN; + unsigned attempts; + + for(attempts=0; attemptsmax_errors && is_connected() && !is_cancelled(); attempts++) { + if(attempts&1) /* Alternate between ZABORT and ZFIN */ + result = send_zabort(); + else + result = send_zfin(); + if(result != 0) + return result; + if((type = recv_header()) == ZFIN) + break; + } + + /* + * these Os are formally required; but they don't do a thing + * unfortunately many programs require them to exit + * (both programs already sent a ZFIN so why bother ?) + */ + + if(type == ZFIN) { + send_raw('O'); + send_raw('O'); + } + + return type; +} + +BOOL ZModem::handle_zrpos(uint64_t* pos) +{ + if(zm->rxd_header_pos <= zm->current_file_size) { + if(*pos != zm->rxd_header_pos) { + *pos = zm->rxd_header_pos; + lprintf(LOG_INFO,"Resuming transfer from offset: %llu", *pos); + } + return ZTRUE; + } + lprintf(LOG_WARNING,"Invalid ZRPOS offset: %lu", zm->rxd_header_pos); + return ZFALSE; +} + +BOOL ZModem::handle_zack() +{ + if(zm->rxd_header_pos == zm->current_file_pos) + return ZTRUE; + lprintf(LOG_WARNING,"ZACK for incorrect offset (%lu vs %lu)" ,zm->rxd_header_pos, (ulong)zm->current_file_pos); + return ZFALSE; +} + +/* + * send from the current position in the file + * all the way to end of file or until something goes wrong. + * (ZNAK or ZRPOS received) + * returns ZRINIT on success. + */ +int ZModem::send_from(File* fp, uint64_t pos, uint64_t* sent) +{ + size_t n; + uchar type; + unsigned buf_sent=0; + unsigned subpkts_sent=0; + + if(sent!=NULL) + *sent=0; + + if(!fp->seek(pos)) { + lprintf(LOG_ERR,"ERROR %d seeking to file offset %llu" ,errno, pos); + send_pos_header( ZFERR, (uint32_t)pos, /* Hex? */ ZTRUE); + return ZFERR; + } + zm->current_file_pos=pos; + + + /* + * send the data in the file + */ + + while(is_connected()) { + + /* + * read a block from the file + */ + + n = fp->read(zm->tx_data_subpacket,zm->block_size); + + progress(zm->cbdata, fp->position()); + + type = ZCRCW; + + /** ZMODEM.DOC: + ZCRCW data subpackets expect a response before the next frame is sent. + If the receiver does not indicate overlapped I/O capability with the + CANOVIO bit, or sets a buffer size, the sender uses the ZCRCW to allow + the receiver to write its buffer before sending more data. + ***/ + /* Note: we always use ZCRCW for the first frame */ + if(subpkts_sent || n < zm->block_size) { + /* ZMODEM.DOC: + In the absence of fatal error, the sender eventually encounters end of + file. If the end of file is encountered within a frame, the frame is + closed with a ZCRCE data subpacket which does not elicit a response + except in case of error. + */ + if(n < zm->block_size) + type = ZCRCE; + else { + if(zm->can_overlap_io && !zm->no_streaming && (zm->recv_bufsize==0 || buf_sent+n < zm->recv_bufsize)) + type = ZCRCG; + else /* Send a ZCRCW frame */ + buf_sent = 0; + } + } + + /* Note: No support for sending ZCRCQ data sub-packets here */ + + if(send_data( type, zm->tx_data_subpacket, n)!=0) + return(TIMEOUT); + + zm->current_file_pos += n; + if(zm->current_file_pos > zm->current_file_size) + zm->current_file_size = zm->current_file_pos; + subpkts_sent++; + + if(type == ZCRCW || type == ZCRCE) { + lprintf(LOG_DEBUG,"Sent end-of-frame (%s sub-packet)", chr(type)); + if(type==ZCRCW) { /* ZACK expected */ + lprintf(LOG_DEBUG,"Waiting for ZACK"); + while(is_connected()) { + int ack; + if((ack = recv_header()) != ZACK) + return(ack); + + if(is_cancelled()) + return(ZCAN); + + if(handle_zack()) + break; + } + } + } + + if(sent!=NULL) + *sent+=n; + + buf_sent+=n; + + if(n < zm->block_size) { + lprintf(LOG_DEBUG,"send_from: end of file (or read error) reached at offset: %lld", zm->current_file_pos); + send_zeof( (uint32_t)zm->current_file_pos); + return recv_header(); /* If this is ZRINIT, Success */ + } + + /* + * characters from the other side + * check out that header + */ + + while(data_waiting( zm->consecutive_errors ? 1:0) + && !is_cancelled() && is_connected()) { + int rx_type; + int c; + lprintf(LOG_DEBUG,"Back-channel traffic detected:"); + if((c = recv_raw()) < 0) + return(c); + if(c == ZPAD) { + /* ZMODEM.DOC: + FULL STREAMING WITH SAMPLING + If one of these characters (0x18 CAN or ZPAD) is seen, an + empty ZCRCE data subpacket is sent. + */ + send_data( ZCRCE, NULL, 0); + rx_type = recv_header(); + lprintf(LOG_DEBUG,"Received back-channel data: %s", chr(rx_type)); + if(rx_type >= 0) { + return rx_type; + } + } + else + { + lprintf(LOG_DEBUG,"Received: %s",chr(c)); + } + } + if(is_cancelled()) + return(ZCAN); + + zm->consecutive_errors = 0; + + if(zm->block_size < zm->max_block_size) { + zm->block_size*=2; + if(zm->block_size > zm->max_block_size) + zm->block_size = zm->max_block_size; + } + } + + lprintf(LOG_DEBUG,"send_from: returning unexpectedly!"); + + /* + * end of file reached. + * should receive something... so fake ZACK + */ + + return ZACK; +} + +/* + * send a file; returns true when session is successful. (or file is skipped) + */ + +BOOL ZModem::send_file(char* fname, File* fp, BOOL request_init, time_t* start, uint64_t* sent) +{ + uint64_t pos=0; + uint64_t sent_bytes; + unsigned char * p; + uchar zfile_frame[] = { ZFILE, 0, 0, 0, 0 }; + int type; + int i; + unsigned attempts; + + if(zm->block_size == 0) + zm->block_size = ZBLOCKLEN; + + if(zm->block_size < 128) + zm->block_size = 128; + + if(zm->block_size > sizeof(zm->tx_data_subpacket)) + zm->block_size = sizeof(zm->tx_data_subpacket); + + if(zm->max_block_size < zm->block_size) + zm->max_block_size = zm->block_size; + + if(zm->max_block_size > sizeof(zm->rx_data_subpacket)) + zm->max_block_size = sizeof(zm->rx_data_subpacket); + + if(sent!=NULL) + *sent=0; + + if(start!=NULL) + *start=time(NULL); + + zm->file_skipped=ZFALSE; + + if(zm->no_streaming) + { + lprintf(LOG_WARNING,"Streaming disabled"); + } + + if(request_init) { + for(zm->errors=0; zm->errors<=zm->max_errors && !is_cancelled() && is_connected(); zm->errors++) { + if(zm->errors) + { + lprintf(LOG_NOTICE,"Sending ZRQINIT (%u of %u)" ,zm->errors+1,zm->max_errors+1); + } + else + { + lprintf(LOG_INFO,"Sending ZRQINIT"); + } + i = get_zrinit(); + if(i == ZRINIT) + break; + lprintf(LOG_WARNING,"send_file: received %s instead of ZRINIT" ,frame_desc(i)); + } + if(zm->errors>=zm->max_errors || is_cancelled() || !is_connected()) + return(ZFALSE); + } + + zm->current_file_size = fp->size(); + SAFECOPY(zm->current_file_name, getfname(fname)); + + /* + * the file exists. now build the ZFILE frame + */ + + /* + * set conversion option + * (not used; always binary) + */ + + zfile_frame[ZF0] = ZF0_ZCBIN; + + /* + * management option + */ + + if(zm->management_protect) { + zfile_frame[ZF1] = ZF1_ZMPROT; + lprintf(LOG_DEBUG,"send_file: protecting destination"); + } + else if(zm->management_clobber) { + zfile_frame[ZF1] = ZF1_ZMCLOB; + lprintf(LOG_DEBUG,"send_file: overwriting destination"); + } + else if(zm->management_newer) { + zfile_frame[ZF1] = ZF1_ZMNEW; + lprintf(LOG_DEBUG,"send_file: overwriting destination if newer"); + } + else + zfile_frame[ZF1] = ZF1_ZMCRC; + + /* + * transport options + * (just plain normal transfer) + */ + + zfile_frame[ZF2] = ZF2_ZTNOR; + + /* + * extended options + */ + + zfile_frame[ZF3] = 0; + + /* + * now build the data subpacket with the file name and lots of other + * useful information. + */ + + /* + * first enter the name and a 0 + */ + + p = zm->tx_data_subpacket; + + strncpy((char *)zm->tx_data_subpacket,getfname(fname),sizeof(zm->tx_data_subpacket)-1); + zm->tx_data_subpacket[sizeof(zm->tx_data_subpacket)-1]=0; + + p += strlen((char*)p) + 1; + + sprintf((char*)p,"%lld"" %llo 0 0 %u %lld"" 0" + ,zm->current_file_size /* use for estimating only, could be zero! */ + ,0 + ,zm->files_remaining + ,zm->bytes_remaining + ); + + p += strlen((char*)p) + 1; + + for(attempts=0;;attempts++) { + + if(attempts > zm->max_errors) + return(ZFALSE); + + /* + * send the header and the data + */ + + lprintf(LOG_DEBUG,"Sending ZFILE frame: '%s'" ,zm->tx_data_subpacket+strlen((char*)zm->tx_data_subpacket)+1); + + if((i=send_bin_header(zfile_frame))!=0) { + lprintf(LOG_DEBUG,"send_bin_header returned %d",i); + continue; + } + if((i=send_data_subpkt(ZCRCW,zm->tx_data_subpacket,p - zm->tx_data_subpacket))!=0) { + lprintf(LOG_DEBUG,"send_data_subpkt returned %d",i); + continue; + } + /* + * wait for anything but an ZACK packet + */ + + do { + type = recv_header(); + if(is_cancelled()) + return(ZFALSE); + } while(type == ZACK && is_connected()); + + if(!is_connected()) + return(ZFALSE); + +#if 0 + lprintf(LOG_INFO,"type : %d",type); +#endif + + if(type == ZSKIP) { + zm->file_skipped=ZTRUE; + lprintf(LOG_WARNING,"File skipped by receiver"); + return(ZTRUE); + } + + if(type == ZCRC) { + if(zm->crc_request==0) + { + lprintf(LOG_NOTICE,"Receiver requested CRC of entire file"); + } + else + { + lprintf(LOG_NOTICE,"Receiver requested CRC of first %lu bytes" ,zm->crc_request); + } + send_pos_header(ZCRC,fcrc32(fp,zm->crc_request),ZTRUE); + type = recv_header(); + } + + if(type == ZRPOS) + break; + } + + if(!handle_zrpos( &pos)) + return(ZFALSE); + + zm->transfer_start_pos = pos; + zm->transfer_start_time = time(NULL); + + if(start!=NULL) + *start=zm->transfer_start_time; + + fp->seek(0); + zm->errors = 0; + zm->consecutive_errors = 0; + + lprintf(LOG_DEBUG,"Sending %s from offset %llu", fname, pos); + do { + /* + * and start sending + */ + + type = send_from( fp, pos, &sent_bytes); + + if(!is_connected()) + return(ZFALSE); + + if(type == ZFERR || type == ZABORT || is_cancelled()) + break; + + if(type == ZSKIP) { + zm->file_skipped=ZTRUE; + lprintf(LOG_WARNING,"File skipped by receiver at offset: %llu", pos + sent_bytes); + /* ZOC sends a ZRINIT after mid-file ZSKIP, so consume the ZRINIT here */ + recv_header(); + return(ZTRUE); + } + + if(sent != NULL) + *sent += sent_bytes; + + if(type == ZRINIT) + return(ZTRUE); /* Success */ + + if(type==ZACK && handle_zack()) { + pos += sent_bytes; + continue; + } + + /* Error of some kind */ + + lprintf(LOG_ERR,"Received %s at offset: %lld", chr(type), zm->current_file_pos); + + if(zm->block_size == zm->max_block_size && zm->max_block_size > ZBLOCKLEN) + zm->max_block_size /= 2; + + if(zm->block_size > 128) + zm->block_size /= 2; + + zm->errors++; + if(++zm->consecutive_errors > zm->max_errors) + break; /* failure */ + + if(type==ZRPOS) { + if(!handle_zrpos( &pos)) + break; + } + } while(ZTRUE); + + lprintf(LOG_WARNING,"Transfer failed on receipt of: %s", chr(type)); + + return(ZFALSE); +} + +int ZModem::recv_files(const char* download_dir, uint64_t* bytes_received) +{ + char fpath[MAX_PATH+1]; + File* fp; + int64_t l; + BOOL skip; + BOOL loop; + uint64_t b; + uint32_t crc; + uint32_t rcrc; + int64_t bytes; + int64_t kbytes; + int64_t start_bytes; + unsigned files_received=0; + time_t t; + unsigned cps; + unsigned timeout; + unsigned errors; + + if(bytes_received!=NULL) + *bytes_received=0; + zm->current_file_num=1; + while(recv_init()==ZFILE) { + bytes=zm->current_file_size; + kbytes=bytes/1024; + if(kbytes<1) + kbytes=0; + lprintf(LOG_INFO,"Downloading %s (%lld"" KBytes) via Zmodem", zm->current_file_name, kbytes); + + do { /* try */ + skip=ZTRUE; + loop=ZFALSE; + + sprintf(fpath,"%s/%s",download_dir,zm->current_file_name); + lprintf(LOG_DEBUG,"fpath=%s",fpath); + File fileF=zfileSystem->open(fpath); + if(fileF) { + l=fileF.size(); + lprintf(LOG_WARNING,"%s already exists (%lld"" bytes)",fpath,l); + if(l>=(int32_t)bytes) { + lprintf(LOG_WARNING,"Local file size >= remote file size (%lld"")" ,bytes); + break; + /* + if(zm->duplicate_filename==NULL) + break; + else { + if(l > (int32_t)bytes) { + if(zm->duplicate_filename(zm->cbdata, zm)) { + loop=ZTRUE; + continue; + } + break; + } + } + */ + } + fp=&fileF; + if(!(*fp)) { + lprintf(LOG_ERR,"Error %d opening %s", errno, fpath); + break; + } + //setvbuf(fp,NULL,_IOFBF,0x10000); + + lprintf(LOG_NOTICE,"Requesting CRC of remote file: %s", zm->current_file_name); + if(!request_crc( (uint32_t)l)) { + fp->close(); + lprintf(LOG_ERR,"Failed to request CRC of remote file"); + break; + } + lprintf(LOG_NOTICE,"Calculating CRC of: %s", fpath); + crc=fcrc32(fp,(uint32_t)l); /* Warning: 4GB limit! */ + fp->close(); + lprintf(LOG_INFO,"CRC of %s (%lu bytes): %08lX" ,getfname(fpath), (ulong)l, crc); + lprintf(LOG_NOTICE,"Waiting for CRC of remote file: %s", zm->current_file_name); + if(!recv_crc(&rcrc)) { + lprintf(LOG_ERR,"Failed to get CRC of remote file"); + break; + } + if(crc!=rcrc) { + lprintf(LOG_WARNING,"Remote file has different CRC value: %08lX", rcrc); + /* + if(zm->duplicate_filename) { + if(zm->duplicate_filename(zm->cbdata, zm)) { + loop=ZTRUE; + continue; + } + } + */ + break; + } + if(l == (int32_t)bytes) { + lprintf(LOG_INFO,"CRC, length, and filename match."); + break; + } + lprintf(LOG_INFO,"Resuming download of %s",fpath); + } + + if(fileF) + fileF.close(); + fileF=zfileSystem->open(fpath, FILE_APPEND); + fp = &fileF; + start_bytes=fp->size(); + + skip=ZFALSE; + errors=recv_file_data(fp,start_bytes); + + l=fp->size(); + fp->close(); + if(errors && l==0) { /* aborted/failed download */ + if(zfileSystem->remove(fpath)) /* don't save 0-byte file */ + { + lprintf(LOG_ERR,"Error %d removing %s",errno,fpath); + } + else + { + lprintf(LOG_INFO,"Deleted 0-byte file %s",fpath); + } + } + else { + if(l!=bytes) { + lprintf(LOG_WARNING,"Incomplete download (%lld"" bytes received, expected %lld"")",l,bytes); + } else { + if((t=time(NULL)-zm->transfer_start_time)<=0) + t=1; + b=l-start_bytes; + if((cps=(unsigned)(b/t))==0) + cps=1; + lprintf(LOG_INFO,"Received %llu"" bytes successfully (%u CPS)",b,cps); + files_received++; + if(bytes_received!=NULL) + *bytes_received+=b; + } + //if(zm->current_file_time) //BZ: unsupported... + // setfdate(fpath,zm->current_file_time); + } + + } while(loop); + /* finally */ + + if(skip) { + lprintf(LOG_WARNING,"Skipping file"); + send_zskip(); + } + zm->current_file_num++; + } + if(zm->local_abort) + send_zabort(); + + /* wait for "over-and-out" */ + timeout=zm->recv_timeout; + zm->recv_timeout=2; + if(rx()=='O') + rx(); + zm->recv_timeout=timeout; + + return(files_received); +} + +int ZModem::recv_init() +{ + int type=0x18/*CAN*/; + unsigned errors; + + lprintf(LOG_DEBUG,"recv_init"); + +#if 0 + while(is_connected() && !is_cancelled() && (ch=recv_byte(zm->cbdata,0))!=NOINP) + { + lprintf(LOG_DEBUG,"Throwing out received: %s",chr((uchar)ch)); + } +#endif + + for(errors=0; errors<=zm->max_errors && !is_cancelled() && is_connected(); errors++) { + if(errors) + { + lprintf(LOG_NOTICE,"Sending ZRINIT (%u of %u)",errors+1, zm->max_errors+1); + } + else + { + lprintf(LOG_INFO,"Sending ZRINIT"); + } + send_zrinit(); + + type = recv_header(); + + if(zm->local_abort) + break; + + if(type==TIMEOUT) + continue; + + lprintf(LOG_DEBUG,"recv_init: Received %s",chr(type)); + + if(type==ZFILE) { + parse_zfile_subpacket(); + return(type); + } + + if(type==ZFIN) { + send_zfin(); /* 0x06 ACK */ + return(type); + } + + lprintf(LOG_WARNING,"recv_init: Received %s instead of ZFILE or ZFIN" ,frame_desc(type)); + lprintf(LOG_DEBUG,"ZF0=%02X ZF1=%02X ZF2=%02X ZF3=%02X" ,zm->rxd_header[ZF0],zm->rxd_header[ZF1],zm->rxd_header[ZF2],zm->rxd_header[ZF3]); + } + + return(type); +} + +void ZModem::parse_zfile_subpacket() +{ + int i; + int mode=0; + long serial=-1L; + ulong tmptime; + + SAFECOPY(zm->current_file_name,getfname((char*)zm->rx_data_subpacket)); + + zm->current_file_size = 0; + zm->current_file_time = 0; + zm->files_remaining = 0; + zm->bytes_remaining = 0; + + i=sscanf((char*)zm->rx_data_subpacket+strlen((char*)zm->rx_data_subpacket)+1,"%lld %lo %o %lo %u %lld" + ,&zm->current_file_size /* file size (decimal) */ + ,&tmptime /* file time (octal unix format) */ + ,&mode /* file mode */ + ,&serial /* program serial number */ + ,&zm->files_remaining /* remaining files to be sent */ + ,&zm->bytes_remaining /* remaining bytes to be sent */ + ); + zm->current_file_time=tmptime; + + lprintf(LOG_DEBUG,"Zmodem file (ZFILE) data (%u fields): %s",i, zm->rx_data_subpacket+strlen((char*)zm->rx_data_subpacket)+1); + + if(!zm->files_remaining) + zm->files_remaining = 1; + if(!zm->bytes_remaining) + zm->bytes_remaining = zm->current_file_size; + + if(!zm->total_files) + zm->total_files = zm->files_remaining; + if(!zm->total_bytes) + zm->total_bytes = zm->bytes_remaining; +} + +/* + * receive file data until the end of the file or until something goes wrong. + * the name is only used to show progress + */ + +unsigned ZModem::recv_file_data(File* fp, int64_t offset) +{ + int type=0; + unsigned errors=0; + off_t pos; + + zm->transfer_start_pos=offset; + zm->transfer_start_time=time(NULL); + + if(!(fp->seek(offset))) { + lprintf(LOG_ERR,"ERROR %d seeking to file offset %lld" ,errno, offset); + ZModem::send_pos_header( ZFERR, (uint32_t)offset, /* Hex? */ ZTRUE); + return 1; /* errors */ + } + + /* zmodem.doc: + + The zmodem receiver uses the file length [from ZFILE data] as an estimate only. + It may be used to display an estimate of the transmission time, + and may be compared with the amount of free disk space. The + actual length of the received file is determined by the data + transfer. A file may grow after transmission commences, and + all the data will be sent. + */ + while(errors<=zm->max_errors && is_connected() && !is_cancelled()) { + + if((pos=fp->position()) > zm->current_file_size) + zm->current_file_size = pos; + + if(zm->max_file_size!=0 && pos >= zm->max_file_size) { + lprintf(LOG_WARNING,"Specified maximum file size (%lld"" bytes) reached at offset %lld" ,zm->max_file_size, pos); + send_pos_header( ZFERR, (uint32_t)pos, /* Hex? */ ZTRUE); + break; + } + + if(type!=ENDOFFRAME) + send_pos_header( ZRPOS, (uint32_t)pos, /* Hex? */ ZTRUE); + + type = recv_file_frame(fp); + if(type == ZEOF || type == ZFIN) + break; + if(type==ENDOFFRAME) + { + lprintf(LOG_DEBUG,"Received complete frame at offset: %lu", (ulong)fp->position()); + } + else { + if(type>0 && !zm->local_abort) + { + lprintf(LOG_ERR,"Received %s at offset: %lu", chr(type), (ulong)fp->position()); + } + errors++; + } + } + + /* + * wait for the eof header + */ + for(;errors<=zm->max_errors && !is_cancelled() && type!=ZEOF && type!=ZFIN; errors++) + type = recv_header_and_check(); + + return(errors); +} + + +int ZModem::recv_file_frame(File* fp) +{ + unsigned n; + int type; + unsigned attempt; + + /* + * wait for a ZDATA header with the right file offset + * or a timeout or a ZFIN + */ + + for(attempt=0;;attempt++) { + if(attempt>=zm->max_errors) + return TIMEOUT; + type = recv_header(); + switch(type) { + case ZEOF: + /* ZMODEM.DOC: + If the receiver has not received all the bytes of the file, + the receiver ignores the ZEOF because a new ZDATA is coming. + */ + if(zm->rxd_header_pos==(uint32_t)fp->position()) + return type; + lprintf(LOG_WARNING,"Ignoring ZEOF as all bytes (%lu) have not been received",zm->rxd_header_pos); + continue; + case ZFIN: + return type; + case TIMEOUT: + return type; + } + if(is_cancelled() || !is_connected()) + return ZCAN; + + if(type==ZDATA) + break; + + lprintf(LOG_WARNING,"Received %s instead of ZDATA frame", frame_desc(type)); + } + + if(zm->rxd_header_pos!=(uint32_t)fp->position()) { + lprintf(LOG_WARNING,"Received wrong ZDATA frame (%lu vs %lu)" ,zm->rxd_header_pos, (ulong)fp->position()); + return ZFALSE; + } + + do { + type = recv_data(zm->rx_data_subpacket,sizeof(zm->rx_data_subpacket),&n,ZTRUE); + + lprintf(LOG_INFO,"packet len %d type %d\n",n,type); + + if (type == ENDOFFRAME || type == FRAMEOK) { + + if(fp->write(zm->rx_data_subpacket,n)!=n) { + lprintf(LOG_ERR,"ERROR %d writing %u bytes at file offset %llu" ,errno, n,(uint64_t)fp->position()); + send_pos_header( ZFERR, (uint32_t)fp->position(), /* Hex? */ ZTRUE); + return ZFALSE; + } + } + + if(type==FRAMEOK) + zm->block_size = n; + + progress(zm->cbdata, fp->position()); + + if(is_cancelled()) + return(ZCAN); + + } while(type == FRAMEOK); + + return type; +} + +const char* ZModem::source(void) +{ + return (__FILE__); +} + +char* ZModem::ver(char *buf) +{ + sscanf("$Revision: 1.120 $", "%*s %s", buf); + + return (buf); +} + +#endif diff --git a/src/rt_clock.ino b/src/rt_clock.ino new file mode 100644 index 0000000..3c5c055 --- /dev/null +++ b/src/rt_clock.ino @@ -0,0 +1,703 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#include +#define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ + ((x)<< 8 & 0x00FF0000UL) | \ + ((x)>> 8 & 0x0000FF00UL) | \ + ((x)>>24 & 0x000000FFUL) ) + +const int NTP_PACKET_SIZE = 48; + +uint8_t DAYS_IN_MONTH[13] PROGMEM = { + 31,28,31,30,31,30,31,31,30,31,30,31 +}; + +char *uintToStr( const uint64_t num, char *str ) +{ + uint8_t i = 0; + uint64_t n = num; + do + i++; + while ( n /= 10 ); + str[i] = '\0'; + n = num; + do + str[--i] = ( n % 10 ) + '0'; + while ( n /= 10 ); + return str; +} + +DateTimeClock::DateTimeClock() : DateTimeClock(0) +{ +} + + +DateTimeClock::DateTimeClock(uint32_t epochSecs) +{ + setByUnixEpoch(epochSecs); +} + +DateTimeClock::DateTimeClock(int y, int m, int d, int h, int mn, int s, int mi) +{ + year=y; + month=m; + day=d; + hour=h; + min=mn; + sec=s; + milsec=mi; +} + +RealTimeClock::RealTimeClock(uint32_t epochSecs) : DateTimeClock(epochSecs) +{ + lastMillis = millis(); + nextNTPMillis = millis(); +} + +RealTimeClock::RealTimeClock() : DateTimeClock() +{ + lastMillis = millis(); + nextNTPMillis = millis(); +} + +RealTimeClock::RealTimeClock(int y, int m, int d, int h, int mn, int s, int mi) : + DateTimeClock(y,m,d,h,mn,s,mi) +{ + lastMillis = millis(); + nextNTPMillis = millis(); +} + +void RealTimeClock::startUdp() +{ + if(!udpStarted) + { + udpStarted=udp.begin(2390); + } +} + +void RealTimeClock::tick() +{ + if(disabled) + return; + if(udpStarted) + { + int cb = udp.parsePacket(); + if (cb) + { + // adapted from code by by Michael Margolis, Tom Igoe, and Ivan Grokhotkov + //debugPrint("Packet received, length=%d\n\r",cb); + byte packetBuffer[ NTP_PACKET_SIZE]; + udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer + // combine the four bytes (two words) into a long integer + // this is NTP time (seconds since Jan 1 1900): + uint32_t secsSince1900 = htonl(*((uint32_t *)(packetBuffer + 40))); + // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: + const uint32_t seventyYears = 2208988800UL; + // subtract seventy years: + uint32_t epoch = secsSince1900 - seventyYears; + lastMillis = millis(); + // now to apply the timezone. Ugh; + setByUnixEpoch(epoch); + String tz=""; + { + char s=0; + char c=pgm_read_byte_near(&(TimeZones[tzCode][1][s])); + while(c != 0) + { + tz += c; + c=pgm_read_byte_near(&(TimeZones[tzCode][1][++s])); + } + String otz=tz; + int x=tz.indexOf("/"); + if(x > 0) + { + if(isInStandardTime()) + tz = tz.substring(0,x); + else + tz = tz.substring(x+1); + } + x=tz.indexOf(":"); + int mm=0; + if(x > 0) + { + mm = atoi(tz.substring(x+1).c_str()); + tz = tz.substring(0,x); + } + uint32_t echg = (atoi(tz.c_str()) * 3600); + echg += ((echg < 0)?(-(mm * 60)):(mm * 60)); + setByUnixEpoch(epoch + echg); + debugPrintf("Received NTP: %d/%d/%d %d:%d:%d\n\r",(int)getMonth(),(int)getDay(),(int)getYear(),(int)getHour(),(int)getMinute(),(int)getSecond()); + } + nextNTPMillis = millis() + ntpPeriodLongMillis; // one hour + } + else + { + uint32_t now=millis(); + if(nextNTPMillis >= now) + { + if(((nextNTPMillis - now) > ntpPeriodLongMillis) + ||(nextNTPMillis == now)) + forceUpdate(); + } + else + if((now - nextNTPMillis) > ntpPeriodLongMillis) + forceUpdate(); + } + } +} + +void RealTimeClock::forceUpdate() +{ + if(!disabled) + { + nextNTPMillis = millis() + ntpPeriodMillis; + startUdp(); + sendTimeRequest(); + } +} + +bool RealTimeClock::isTimeSet() +{ + return (year > 1000); +} + +bool RealTimeClock::reset() +{ + year=0; + month=0; + day=0; + hour=0; + min=0; + sec=0; + milsec=0; + lastMillis = millis(); + nextNTPMillis = millis(); + tzCode = 0; + format="%M/%d/%yyyy %h:%mm:%ss%aa %z"; + ntpServerName = "time.nist.gov"; + return true; +} + +int DateTimeClock::getYear() +{ + return year; +} + +void DateTimeClock::setYear(int y) +{ + year=y; +} + +void DateTimeClock::addYear(uint32_t y) +{ + year+=y; +} + +int DateTimeClock::getMonth() +{ + return month + 1; // because 0 based +} + +void DateTimeClock::setMonth(int m) +{ + month = m % 12; +} + +void DateTimeClock::addMonth(uint32_t m) +{ + m = month + m; + if(m > 11) + addYear(floor(m / 12)); + setMonth(m); +} + +int DateTimeClock::getDay() +{ + return day + 1; +} + +void DateTimeClock::setDay(int d) +{ + day = d % getDaysInThisMonth(); +} + +void DateTimeClock::addDay(uint32_t d) +{ + d = day + d; + if(d >= getDaysInThisMonth()) + { + while(d > (isLeapYear()?366:365)) + { + d=d-(isLeapYear()?366:365); + addYear(1); + } + while(d >= getDaysInThisMonth()) + { + d=d-getDaysInThisMonth(); + addMonth(1); + } + } + setDay(d); +} + +int DateTimeClock::getHour() +{ + return hour; +} + +void DateTimeClock::setHour(int h) +{ + hour=h % 24; +} + +void DateTimeClock::addHour(uint32_t h) +{ + h=hour + h; + if(h > 23) + addDay(floor(h / 24)); + setHour(h); +} + +int DateTimeClock::getMinute() +{ + return min; +} + +void DateTimeClock::setMinute(int mm) +{ + min=mm % 60; +} + +void DateTimeClock::addMinute(uint32_t mm) +{ + mm = min+mm; + if(mm > 59) + addHour(floor(mm / 60)); + setMinute(mm); +} + +int DateTimeClock::getSecond() +{ + return sec; +} + +void DateTimeClock::setSecond(int s) +{ + sec=s % 60; +} + +void DateTimeClock::addSecond(uint32_t s) +{ + s = sec + s; + if(s > 59) + addMinute(floor(s / 60)); + setSecond(s); +} + +int DateTimeClock::getMillis() +{ + return milsec; +} + +void DateTimeClock::setMillis(int s) +{ + milsec=s % 1000; +} + +void DateTimeClock::addMillis(uint64_t s) +{ + s = milsec+s; + if(s > 999) + addSecond(floor(s / 1000)); + setMillis(s); +} + +bool DateTimeClock::isLeapYear() +{ + if(year % 4 == 0) + { + if(year % 100 == 0) + { + if(year % 400 == 0) + return true; + return false; + } + return true; + } + return false; +} + +uint8_t DateTimeClock::getDaysInThisMonth() +{ + if(month != 1) // feb exception + return pgm_read_byte_near(DAYS_IN_MONTH + month); + return (isLeapYear() ? 29 : 28); +} + +void DateTimeClock::setTime(DateTimeClock &clock) +{ + year=clock.year; + month=clock.month; + day=clock.day; + hour=clock.hour; + min=clock.min; + sec=clock.sec; + milsec=clock.milsec; +} + + +DateTimeClock &RealTimeClock::getCurrentTime() +{ + adjClock.setTime(*this); + uint32_t now=millis(); + if(lastMillis <= now) + adjClock.addMillis(now - lastMillis); + else + adjClock.addMillis(now + (0xffffffff - lastMillis)); + return adjClock; +} + + +void DateTimeClock::setByUnixEpoch(uint32_t unisex) +{ + setYear(1970); + setMonth(0); + setDay(0); + setHour(0); + setMinute(0); + setSecond(0); + setMillis(0); + uint64_t ms = unisex; + ms *= 1000L; + addMillis(ms); +} + +uint32_t DateTimeClock::getUnixEpoch() +{ + if(year < 1970) + return 0; + uint32_t val = sec + (min * 60) + (hour * 60 * 60); + //TODO: + return val; +} + +bool RealTimeClock::sendTimeRequest() +{ + if((WiFi.status() == WL_CONNECTED)&&(udpStarted)) + { + // adapted from code by by Michael Margolis, Tom Igoe, and Ivan Grokhotkov + debugPrintf("Sending NTP Packet..."); + byte packetBuffer[ NTP_PACKET_SIZE]; + memset(packetBuffer, 0, NTP_PACKET_SIZE); + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + IPAddress timeServerIP; + String host = ntpServerName; + int port=123; + int pi=host.indexOf(':'); + if(pi>0) + { + port=atoi(host.substring(pi+1).c_str()); + host = host.substring(0,pi); + } + WiFi.hostByName(ntpServerName.c_str(), timeServerIP); + udp.beginPacket(timeServerIP, port); //NTP requests are to port 123 + udp.write(packetBuffer, NTP_PACKET_SIZE); + udp.endPacket(); + return true; + } + return false; +} + +int DateTimeClock::getDoWNumber() +{ + uint16_t x= (getMonth() + 9) % 12; + uint16_t y = getYear() - x/10; + uint32_t z= 365*y + y/4 - y/100 + y/400 + (x*306 + 5)/10 + (getDay() - 1); + z=z%7; + if(z>3) + return z-4; + else + return z+3; +} + +const char *DateTimeClock::getDoW() +{ + int num=getDoWNumber(); + switch(num) + { + case 0:return "Sunday"; + case 1:return "Monday"; + case 2:return "Tuesday"; + case 3:return "Wednesday"; + case 4:return "Thursday"; + case 5:return "Friday"; + case 6:return "Saturday"; + default: return "Broken"; + } +} + +bool DateTimeClock::isInStandardTime() +{ + uint8_t m=getMonth(); + if(m<3) + return true; + if(m==3) + { + uint8_t d=getDay(); + uint8_t dow=getDoWNumber(); + while(dow-- > 0) + d--; + if(d<14) + return true; + return false; + } + if((m>3)&&(m<11)) + return false; + if(m==11) + { + uint8_t d=getDay(); + uint8_t dow=getDoWNumber(); + while(dow-- > 0) + d--; + if(d<7) + return false; + return true; + } + return true; +} + +bool DateTimeClock::isInDaylightSavingsTime() +{ + return !isInStandardTime(); +} + +int RealTimeClock::getTimeZoneCode() +{ + return tzCode; +} + +void RealTimeClock::setTimeZoneCode(int val) +{ + if((tzCode >= 0)&&(tzCode < 243)) + { + tzCode = val; + forceUpdate(); + } +} + +bool RealTimeClock::setTimeZone(String str) +{ + str.toUpperCase(); + if(str.length()==0) + return false; + for(int i=0;i<243;i++) + { + for(int s=0;s<=str.length();s++) + { + char c=pgm_read_byte_near(&(TimeZones[i][0][s])); + if(s==str.length()) + { + if(c==0) + { + tzCode = i; + forceUpdate(); + return true; + } + } + else + if((c==0)||(c != str[s])) + break; + } + } + return false; +} + +String RealTimeClock::getFormat() +{ + return format; +} + +void RealTimeClock::setFormat(String fmt) +{ + fmt.replace(',','.'); + format = fmt; +} + +bool RealTimeClock::isDisabled() +{ + return disabled; +} + +void RealTimeClock::setDisabled(bool tf) +{ + disabled=tf; +} + +String RealTimeClock::getCurrentTimeFormatted() +{ + //String format="%M/%D/%YYYY %h:%mm:%ss%a" + DateTimeClock c = getCurrentTime(); + String f=format; + if(f.indexOf("%yyyy")>=0) + { + sprintf(str,"%04d",(int)c.getYear()); + f.replace("%yyyy",str); + } + if(f.indexOf("%yy")>=0) + { + int y=c.getYear(); + y -= (floor(y/100)*100); + sprintf(str,"%02d",y); + f.replace("%yy",str); + } + if(f.indexOf("%y")>=0) + { + sprintf(str,"%d",(int)c.getYear()); + f.replace("%y",str); + } + if(f.indexOf("%MM")>=0) + { + sprintf(str,"%02d",(int)c.getMonth()); + f.replace("%MM",str); + } + if(f.indexOf("%M")>=0) + { + sprintf(str,"%d",(int)c.getMonth()); + f.replace("%M",str); + } + if(f.indexOf("%dd")>=0) + { + sprintf(str,"%02d",(int)c.getDay()); + f.replace("%dd",str); + } + if(f.indexOf("%d")>=0) + { + sprintf(str,"%d",(int)c.getDay()); + f.replace("%d",str); + } + if(f.indexOf("%ee")>=0) + { + f.replace("%ee",c.getDoW()); + } + if(f.indexOf("%e")>=0) + { + String dow=c.getDoW(); + dow = dow.substring(0,3); + sprintf(str,"%d",dow.c_str()); + f.replace("%e",str); + } + if(f.indexOf("%HH")>=0) + { + sprintf(str,"%02d",(int)c.getHour()); + f.replace("%HH",str); + } + if(f.indexOf("%H")>=0) + { + sprintf(str,"%d",(int)c.getHour()); + f.replace("%H",str); + } + if(f.indexOf("%hh")>=0) + { + if((c.getHour()%12)==0) + strcpy(str,"12"); + else + sprintf(str,"%02d",c.getHour()%12); + f.replace("%hh",str); + } + if(f.indexOf("%h")>=0) + { + if((c.getHour()%12)==0) + strcpy(str,"12"); + else + sprintf(str,"%d",(int)(c.getHour() % 12)); + f.replace("%h",str); + } + if(f.indexOf("%mm")>=0) + { + sprintf(str,"%02d",(int)c.getMinute()); + f.replace("%mm",str); + } + if(f.indexOf("%m")>=0) + { + sprintf(str,"%d",(int)c.getMinute()); + f.replace("%m",str); + } + if(f.indexOf("%ss")>=0) + { + sprintf(str,"%02d",(int)c.getSecond()); + f.replace("%ss",str); + } + if(f.indexOf("%s")>=0) + { + sprintf(str,"%d",(int)c.getSecond()); + f.replace("%s",str); + } + if(f.indexOf("%AA")>=0) + f.replace("%AA",(c.getHour()<12)?"AM":"PM"); + if(f.indexOf("%aa")>=0) + f.replace("%aa",(c.getHour()<12)?"am":"pm"); + if(f.indexOf("%A")>=0) + f.replace("%A",(c.getHour()<12)?"A":"P"); + if(f.indexOf("%a")>=0) + f.replace("%a",(c.getHour()<12)?"a":"p"); + if(f.indexOf("%z")>=0) + { + String z=""; + char s=0; + char c=pgm_read_byte_near(&(TimeZones[tzCode][0][s])); + while(c != 0) + { + z += c; + c=pgm_read_byte_near(&(TimeZones[tzCode][0][++s])); + } + z.toLowerCase(); + f.replace("%z",z.c_str()); + } + if(f.indexOf("%Z")>=0) + { + String z=""; + char s=0; + char c=pgm_read_byte_near(&(TimeZones[tzCode][0][s])); + while(c != 0) + { + z += c; + c=pgm_read_byte_near(&(TimeZones[tzCode][0][++s])); + } + f.replace("%Z",z.c_str()); + } + return f; +} + +String RealTimeClock::getNtpServerHost() +{ + return ntpServerName; +} + +void RealTimeClock::setNtpServerHost(String newHost) +{ + newHost.replace(',','.'); + ntpServerName = newHost; +} + diff --git a/src/serout.ino b/src/serout.ino new file mode 100644 index 0000000..91dffc1 --- /dev/null +++ b/src/serout.ino @@ -0,0 +1,455 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +static void serialDirectWrite(uint8_t c) +{ + HWSerial.write(c); + if(serialDelayMs > 0) + delay(serialDelayMs); + logSerialOut(c); +} + +static void hwSerialFlush() +{ +#ifdef ZIMODEM_ESP8266 + HWSerial.flush(); +#endif +} + +static void serialOutDeque() +{ +#ifdef ZIMODEM_ESP32 + while((TBUFhead != TBUFtail) + &&((SER_BUFSIZE - HWSerial.availableForWrite())=SER_BUFSIZE)) // necessary for esp8266 flow control +#endif + { + serialDirectWrite(TBUF[TBUFhead]); + TBUFhead++; + if(TBUFhead >= SER_WRITE_BUFSIZE) + TBUFhead = 0; + } +} + +static int serialOutBufferBytesRemaining() +{ + if(TBUFtail == TBUFhead) + return SER_WRITE_BUFSIZE-1; + else + if(TBUFtail > TBUFhead) + { + int used = TBUFtail - TBUFhead; + return SER_WRITE_BUFSIZE - used -1; + } + else + return TBUFhead - TBUFtail - 1; +} + +static void enqueSerialOut(uint8_t c) +{ + TBUF[TBUFtail] = c; + TBUFtail++; + if(TBUFtail >= SER_WRITE_BUFSIZE) + TBUFtail = 0; +} + +static void clearSerialOutBuffer() +{ + TBUFtail=TBUFhead; +} + +static void ensureSerialBytes(int num) +{ + if(serialOutBufferBytesRemaining()<1) + { + serialOutDeque(); + while(serialOutBufferBytesRemaining()<1) + yield(); + } +} + +static void flushSerial() +{ + while(TBUFtail != TBUFhead) + { + serialOutDeque(); + yield(); + } + hwSerialFlush(); +} + +ZSerial::ZSerial() +{ +} + +void ZSerial::setPetsciiMode(bool petscii) +{ + petsciiMode = petscii; +} + +bool ZSerial::isPetsciiMode() +{ + return petsciiMode; +} + +void ZSerial::setFlowControlType(FlowControlType type) +{ + flowControlType = type; +#ifdef ZIMODEM_ESP32 + if(flowControlType == FCT_RTSCTS) + { + uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_DISABLE,0); + uint32_t invertMask = 0; + if(pinSupport[pinCTS]) + { + uart_set_pin(UART_NUM_2, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, /*cts_io_num*/pinCTS); + // cts is input to me, output to true RS232 + if(ctsActive == HIGH) +# ifdef UART_INVERSE_CTS + invertMask = invertMask | UART_INVERSE_CTS; +# else + invertMask = invertMask | UART_SIGNAL_CTS_INV; +# endif + } + if(pinSupport[pinRTS]) + { + uart_set_pin(UART_NUM_2, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, /*rts_io_num*/ pinRTS, UART_PIN_NO_CHANGE); + s_pinWrite(pinRTS, rtsActive); + // rts is output to me, input to true RS232 + if(rtsActive == HIGH) +# ifdef UART_INVERSE_RTS + invertMask = invertMask | UART_INVERSE_RTS; +# else + invertMask = invertMask | UART_SIGNAL_RTS_INV; +# endif + } + //debugPrintf("invert = %d magic values = %d %d, RTS_HIGH=%d, RTS_LOW=%d HIGHHIGH=%d LOWLOW=%d\n",invertMask,ctsActive,rtsActive, DEFAULT_RTS_HIGH, DEFAULT_RTS_LOW, HIGH, LOW); + if(invertMask != 0) + uart_set_line_inverse(UART_NUM_2, invertMask); + const int CUTOFF = 100; + if(pinSupport[pinRTS]) + { + if(pinSupport[pinCTS]) + uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); + else + uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); + } + else + if(pinSupport[pinCTS]) + uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); + } + else + uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_DISABLE,0); +#endif +} + +FlowControlType ZSerial::getFlowControlType() +{ + return flowControlType; +} + +void ZSerial::setXON(bool isXON) +{ + XON_STATE = isXON; +} + +int ZSerial::getConfigFlagBitmap() +{ + return + (isPetsciiMode()?FLAG_PETSCII:0) + |((getFlowControlType()==FCT_RTSCTS)?FLAG_RTSCTS:0) + |((getFlowControlType()==FCT_NORMAL)?FLAG_XONXOFF:0) + |((getFlowControlType()==FCT_AUTOOFF)?FLAG_XONXOFF:0) + |((getFlowControlType()==FCT_MANUAL)?FLAG_XONXOFF:0); +} + +bool ZSerial::isXON() +{ + return XON_STATE; +} + +bool ZSerial::isSerialOut() +{ + switch(flowControlType) + { + case FCT_RTSCTS: + if(pinSupport[pinCTS]) + { + //debugPrintf("CTS: pin %d (%d == %d)\n",pinCTS,digitalRead(pinCTS),ctsActive); + return (digitalRead(pinCTS) == ctsActive); + } + return true; + case FCT_NORMAL: + case FCT_AUTOOFF: + case FCT_MANUAL: + break; + case FCT_DISABLED: + return true; + case FCT_INVALID: + return true; + } + return XON_STATE; +} + +bool ZSerial::isSerialCancelled() +{ + if(flowControlType == FCT_RTSCTS) + { + if(pinSupport[pinCTS]) + { + //debugPrintf("CTS: pin %d (%d == %d)\n",pinCTS,digitalRead(pinCTS),ctsActive); + return (digitalRead(pinCTS) == ctsInactive); + } + } + return false; +} + +bool ZSerial::isSerialHalted() +{ + return !isSerialOut(); +} + +void ZSerial::enqueByte(uint8_t c) +{ + if(TBUFtail == TBUFhead) + { + switch(flowControlType) + { + case FCT_DISABLED: + case FCT_INVALID: +#ifndef ZIMODEM_ESP32 + if((HWSerial.availableForWrite() > 0) + &&(HWSerial.available() == 0)) +#endif + { + serialDirectWrite(c); + return; + } + break; + case FCT_RTSCTS: +#ifdef ZIMODEM_ESP32 + if(isSerialOut()) +#else + if((HWSerial.availableForWrite() >= SER_BUFSIZE) // necessary for esp8266 flow control + &&(isSerialOut())) +#endif + { + serialDirectWrite(c); + return; + } + break; + case FCT_NORMAL: + case FCT_AUTOOFF: + case FCT_MANUAL: + if((HWSerial.availableForWrite() >= SER_BUFSIZE) + &&(HWSerial.available() == 0) + &&(XON_STATE)) + { + serialDirectWrite(c); + return; + } + break; + } + } + // the car jam of blocked bytes stops HERE + //debugPrintf("%d\n",serialOutBufferBytesRemaining()); + while(serialOutBufferBytesRemaining()<1) + { + if(!isSerialOut()) + delay(1); + else + serialOutDeque(); + yield(); + } + enqueSerialOut(c); +} + +void ZSerial::prints(const char *expr) +{ + if(!petsciiMode) + { + for(int i=0;expr[i]!=0;i++) + { + enqueByte(expr[i]); + } + } + else + { + for(int i=0;expr[i]!=0;i++) + { + enqueByte(ascToPetcii(expr[i])); + } + } +} + +void ZSerial::printi(int i) +{ + char buf[12]; + prints(itoa(i, buf, 10)); +} + +void ZSerial::printd(double f) +{ + char buf[12]; + prints(dtostrf(f, 2, 2, buf)); +} + +void ZSerial::printc(const char c) +{ + if(!petsciiMode) + enqueByte(c); + else + enqueByte(ascToPetcii(c)); +} + +void ZSerial::printc(uint8_t c) +{ + if(!petsciiMode) + enqueByte(c); + else + enqueByte(ascToPetcii(c)); +} + +void ZSerial::printb(uint8_t c) +{ + enqueByte(c); +} + +size_t ZSerial::write(uint8_t c) +{ + enqueByte(c); + return 1; +} + +size_t ZSerial::write(uint8_t *buf, int bufSz) +{ + for(int i=0;i0) + { + ch=HWSerial.read(); + logSerialIn(ch); + if(ch == 3) + break; + switch(flowControlType) + { + case FCT_NORMAL: + if((!XON_STATE) && (ch == 17)) + XON_STATE=true; + else + if((XON_STATE) && (ch == 19)) + XON_STATE=false; + break; + case FCT_AUTOOFF: + case FCT_MANUAL: + if((!XON_STATE) && (ch == 17)) + XON_STATE=true; + else + XON_STATE=false; + break; + case FCT_INVALID: + break; + case FCT_RTSCTS: + break; + } + } + return ch; +} + +int ZSerial::available() +{ + int avail = HWSerial.available(); + if(avail == 0) + { + if((TBUFtail != TBUFhead) && isSerialOut()) + serialOutDeque(); + } + return avail; +} + +int ZSerial::read() +{ + int c=HWSerial.read(); + if(c == -1) + { + if((TBUFtail != TBUFhead) && isSerialOut()) + serialOutDeque(); + } + else + logSerialIn(c); + return c; +} + +int ZSerial::peek() +{ + return HWSerial.peek(); +} diff --git a/src/wificlientnode.ino b/src/wificlientnode.ino new file mode 100644 index 0000000..7a52139 --- /dev/null +++ b/src/wificlientnode.ino @@ -0,0 +1,506 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +void WiFiClientNode::finishConnectionLink() +{ + wasConnected=true; + if(conns == null) + conns = this; + else + { + WiFiClientNode *last = conns; + while(last->next != null) + last = last->next; + last->next = this; + } + checkOpenConnections(); +} + +void WiFiClientNode::constructNode() +{ + id=++WiFiNextClientId; + setCharArray(&delimiters,""); + setCharArray(&maskOuts,""); + setCharArray(&stateMachine,""); + machineState = stateMachine; +} + +void WiFiClientNode::constructNode(char *hostIp, int newport, int flagsBitmap, int ringDelay) +{ + constructNode(); + port=newport; + host=new char[strlen(hostIp)+1]; + strcpy(host,hostIp); + this->flagsBitmap = flagsBitmap; + answered=(ringDelay == 0); + if(ringDelay > 0) + { + ringsRemain = ringDelay; + nextRingMillis = millis() + 3000; + } +} + +void WiFiClientNode::constructNode(char *hostIp, int newport, char *username, char *password, int flagsBitmap, int ringDelay) +{ + constructNode(hostIp, newport, flagsBitmap, ringDelay); +# ifdef INCLUDE_SSH + if(((flagsBitmap&FLAG_SECURE)==FLAG_SECURE) + && (username != 0)) + { + WiFiSSHClient *c = new WiFiSSHClient(); + c->setLogin(username, password); + clientPtr = c; + } + else +#endif + clientPtr = createWiFiClient((flagsBitmap&FLAG_SECURE)==FLAG_SECURE); + client = *clientPtr; + if(!clientPtr->connect(hostIp, newport)) + { + // deleted when it returns and is deleted + } + else + { + clientPtr->setNoDelay(DEFAULT_NO_DELAY); + finishConnectionLink(); + } +} + +WiFiClientNode::WiFiClientNode(char *hostIp, int newport, char *username, char *password, int flagsBitmap) +{ + constructNode(hostIp, newport, username, password, flagsBitmap, 0); +} + +WiFiClientNode::WiFiClientNode(char *hostIp, int newport, int flagsBitmap) +{ + constructNode(hostIp, newport, 0, 0, flagsBitmap, 0); +} + +void WiFiClientNode:: setNoDelay(bool tf) +{ + if(clientPtr != 0) + clientPtr->setNoDelay(tf); + else + client.setNoDelay(tf); +} + +WiFiClientNode::WiFiClientNode(WiFiClient newClient, int flagsBitmap, int ringDelay) +{ + constructNode(); + this->flagsBitmap = flagsBitmap; + clientPtr=null; + newClient.setNoDelay(DEFAULT_NO_DELAY); + port=newClient.localPort(); + String remoteIPStr = newClient.remoteIP().toString(); + const char *remoteIP=remoteIPStr.c_str(); + host=new char[remoteIPStr.length()+1]; + strcpy(host,remoteIP); + client = newClient; + answered=(ringDelay == 0); + if(ringDelay > 0) + { + ringsRemain = ringDelay; + nextRingMillis = millis() + 3000; + } + finishConnectionLink(); + serverClient=true; +} + +WiFiClientNode::~WiFiClientNode() +{ + lastPacket[0].len=0; + lastPacket[1].len=0; + if(host!=null) + { + if(clientPtr != null) + clientPtr->stop(); + else + client.stop(); + delete host; + host=null; + } + if(clientPtr != null) + { + delete clientPtr; + clientPtr = null; + } + if(conns == this) + conns = next; + else + { + WiFiClientNode *last = conns; + while((last != null) && (last->next != this)) // don't change this! + last = last->next; + if(last != null) + last->next = next; + } + if(commandMode.current == this) + commandMode.current = conns; + if(commandMode.nextConn == this) + commandMode.nextConn = conns; + //underflowBuf.len = 0; + freeCharArray(&delimiters); + freeCharArray(&maskOuts); + freeCharArray(&stateMachine); + machineState = NULL; + next=null; + checkOpenConnections(); +} + +bool WiFiClientNode::isConnected() +{ + return (host != null) && (clientPtr != null) && clientPtr->connected(); +} + +bool WiFiClientNode::isPETSCII() +{ + return (flagsBitmap & FLAG_PETSCII) == FLAG_PETSCII; +} + +bool WiFiClientNode::isEcho() +{ + return (flagsBitmap & FLAG_ECHO) == FLAG_ECHO; +} + +FlowControlType WiFiClientNode::getFlowControl() +{ + if((flagsBitmap & FLAG_RTSCTS) == FLAG_RTSCTS) + return FCT_RTSCTS; + if((flagsBitmap & FLAG_XONXOFF) == FLAG_XONXOFF) + return FCT_NORMAL; + return FCT_DISABLED; +} + +bool WiFiClientNode::isTelnet() +{ + return (flagsBitmap & FLAG_TELNET) == FLAG_TELNET; +} + +bool WiFiClientNode::isDisconnectedOnStreamExit() +{ + return (flagsBitmap & FLAG_DISCONNECT_ON_EXIT) == FLAG_DISCONNECT_ON_EXIT; +} + +void WiFiClientNode::setDisconnectOnStreamExit(bool tf) +{ + if(tf) + flagsBitmap = flagsBitmap | FLAG_DISCONNECT_ON_EXIT; + else + flagsBitmap = flagsBitmap & ~FLAG_DISCONNECT_ON_EXIT; +} + +void WiFiClientNode::fillUnderflowBuf() +{ + /* + //underflow buf is deprecated + int newAvail = client.available(); + if(newAvail > 0) + { + int maxBufAvail = PACKET_BUF_SIZE-underflowBuf.len; + if(newAvail>maxBufAvail) + newAvail=maxBufAvail; + if(newAvail > 0) + underflowBuf.len += client.read(underflowBuf.buf+underflowBuf.len, newAvail); + } + */ +} + +int WiFiClientNode::read() +{ + if((host == null)||(!answered)) + return 0; + /* + // underflow buf is no longer needed + if(underflowBuf.len > 0) + { + int b = underflowBuf.buf[0]; + memcpy(underflowBuf.buf,underflowBuf.buf+1,--underflowBuf.len); + return b; + } + */ + int c; + if(clientPtr != null) + c = clientPtr->read(); + else + c= client.read(); + //fillUnderflowBuf(); + return c; +} + +int WiFiClientNode::peek() +{ + if((host == null)||(!answered)) + return 0; + /* + // underflow buf is no longer needed + if(underflowBuf.len > 0) + return underflowBuf.buf[0]; + */ + if(clientPtr != null) + return clientPtr->peek(); + else + return client.peek(); +} + +void WiFiClientNode::flush() +{ + if((host != null)&&(clientPtr != null) && (clientPtr->available()==0)) + flushAlways(); +} + +void WiFiClientNode::flushAlways() +{ + if(host != null) + { + flushOverflowBuffer(); + if(clientPtr != null) + clientPtr->flush(); + else + client.flush(); + } +} + +int WiFiClientNode::available() +{ + if((host == null)||(!answered)) + return 0; + if(clientPtr != null) + return clientPtr->available(); + else + return client.available(); // +underflowBuf.len; +} + +int WiFiClientNode::read(uint8_t *buf, size_t size) +{ + if((host == null)||(!answered)) + return 0; + // this whole underflow buf len thing is to get around yet another + // problem in the underlying library where a socket disconnection + // eats away any stray available bytes in their buffers. + /* + // this was changed to be handled a different, so underBuf is also deprecated; + int previouslyRead = 0; + if(underflowBuf.len > 0) + { + if(underflowBuf.len <= size) + { + previouslyRead += underflowBuf.len; + memcpy(buf,underflowBuf.buf,underflowBuf.len); + size -= underflowBuf.len; + underflowBuf.len = 0; + buf += previouslyRead; + } + else + { + memcpy(buf,underflowBuf.buf,size); + underflowBuf.len -= size; + memcpy(underflowBuf.buf,underflowBuf.buf+size,underflowBuf.len); + return size; + } + } + if(size == 0) + return previouslyRead; + */ + + int bytesRead; + if(clientPtr != null) + bytesRead = clientPtr->read(buf,size); + else + bytesRead = client.read(buf,size); + //fillUnderflowBuf(); + return bytesRead;// + previouslyRead; +} + +int WiFiClientNode::flushOverflowBuffer() +{ + /* + * I've never gotten any of this to trigger, and could use those + * extra 260 bytes per connection + if(overflowBufLen > 0) + { + // because overflowBuf is not a const char* for some reason + // we need to explicitly declare that we want one + // The simplest thing to do is pin down the first character of the + // array and call it a day. + // This avoids client.write(buffer, length) from being seen by the + // compiler as a better way to poke at it. + const uint8_t* overflowbuf_ptr = &overflowBuf[0]; + int bufWrite=client.write(overflowbuf_ptr,overflowBufLen); + if(bufWrite >= overflowBufLen) + { + overflowBufLen = 0; + s_pinWrite(pinRTS,rtsActive); + // fall-through + } + else + { + if(bufWrite > 0) + { + for(int i=bufWrite;i0) + { + s_pinWrite(pinRTS,rtsActive); + } + overflowBufLen=0; + return 0; + } + written += flushOverflowBuffer(); + if(written > 0) + { + for(int i=0;iwrite(buf, size); + else + written += client.write(buf, size); + /* + if(written < size) + { + for(int i=written;i0))) + { + yield(); + if(available()>0) + { + char c=read(); + if((c=='\n')||(c=='\r')) + { + if(line.length()>0) + return line; + } + else + if((c >= 32 ) && (c <= 127)) + line += c; + } + } + return line; +} + +void WiFiClientNode::answer() +{ + answered=true; + ringsRemain=0; + nextRingMillis=0; +} + +bool WiFiClientNode::isAnswered() +{ + return answered; +} + +int WiFiClientNode::ringsRemaining(int delta) +{ + ringsRemain += delta; + return ringsRemain; +} + +unsigned long WiFiClientNode::nextRingTime(long delta) +{ + nextRingMillis += delta; + return nextRingMillis; +} + +size_t WiFiClientNode::write(uint8_t c) +{ + const uint8_t one[] = {c}; + write(one,1); + return 1; +} + +int WiFiClientNode::getNumOpenWiFiConnections() +{ + int num = 0; + WiFiClientNode *conn = conns; + while(conn != null) + { + WiFiClientNode *chkConn = conn; + conn = conn->next; + if((chkConn->nextDisconnect != 0) + &&(millis() > chkConn->nextDisconnect)) + delete(chkConn); + else + if((chkConn->isConnected() + ||(chkConn->available()>0) + ||((chkConn == conns) + &&((serialOutBufferBytesRemaining() isAnswered()) + num++; + } + return num; +} + +void WiFiClientNode::markForDisconnect() +{ + if(nextDisconnect == 0) + nextDisconnect = millis() + 5000; // 5 sec +} + +bool WiFiClientNode::isMarkedForDisconnect() +{ + return nextDisconnect != 0; +} + +void WiFiClientNode::checkForAutoDisconnections() +{ + WiFiClientNode *conn = conns; + while(conn != null) + { + WiFiClientNode *chkConn = conn; + conn = conn->next; + if((chkConn->nextDisconnect != 0) + &&(millis() > chkConn->nextDisconnect)) + delete(chkConn); + } +} diff --git a/src/wifiservernode.ino b/src/wifiservernode.ino new file mode 100644 index 0000000..4d00777 --- /dev/null +++ b/src/wifiservernode.ino @@ -0,0 +1,291 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +WiFiServerSpec::WiFiServerSpec() +{ + setCharArray(&delimiters,""); + setCharArray(&maskOuts,""); + setCharArray(&stateMachine,""); +} + +WiFiServerSpec::~WiFiServerSpec() +{ + freeCharArray(&delimiters); + freeCharArray(&maskOuts); + freeCharArray(&stateMachine); +} + +WiFiServerSpec::WiFiServerSpec(WiFiServerSpec ©) +{ + port=copy.port; + flagsBitmap = copy.flagsBitmap; + setCharArray(&delimiters,copy.delimiters); + setCharArray(&maskOuts,copy.maskOuts); + setCharArray(&stateMachine,copy.stateMachine); +} + +WiFiServerSpec& WiFiServerSpec::operator=(const WiFiServerSpec ©) +{ + if(this != ©) + { + port=copy.port; + flagsBitmap = copy.flagsBitmap; + setCharArray(&delimiters,copy.delimiters); + setCharArray(&maskOuts,copy.maskOuts); + setCharArray(&stateMachine,copy.stateMachine); + } + return *this; +} + +WiFiServerNode::WiFiServerNode(int newport, int flagsBitmap) +{ + id=++WiFiNextClientId; + port=newport; + this->flagsBitmap = flagsBitmap; + server = new WiFiServer((uint16_t)newport); + //BZ:server->setNoDelay(DEFAULT_NO_DELAY); + server->begin(); + if(servs==null) + servs=this; + else + { + WiFiServerNode *s=servs; + while(s->next != null) + s=s->next; + s->next=this; + } +} + +WiFiServerNode::~WiFiServerNode() +{ + if(server != null) + { + server->stop(); + server->close(); + delete server; + } + if(servs == this) + servs = next; + else + { + WiFiServerNode *last = servs; + while((last != null) && (last->next != this)) // don't change this! + last = last->next; + if(last != null) + last->next = next; + } +} + +bool WiFiServerNode::hasClient() +{ + if(server != null) + return server->hasClient(); + return false; +} + +bool WiFiServerNode::ReadWiFiServer(File &f, WiFiServerSpec &node) +{ + if(f.available()>0) + { + String str=""; + char c=f.read(); + while((c != ',') && (f.available()>0)) + { + str += c; + c=f.read(); + } + if(str.length()==0) + return false; + node.port = atoi(str.c_str()); + str = ""; + c=f.read(); + while((c != '\n') && (f.available()>0)) + { + str += c; + c=f.read(); + } + if(str.length()==0) + return false; + node.flagsBitmap = atoi(str.c_str()); + str = ""; + c=f.read(); + while((c != ',') && (f.available()>0)) + { + str += c; + c=f.read(); + } + if(str.length()==0) + return false; + int chars=atoi(str.c_str()); + str = ""; + for(int i=0;i0;i++) + { + c=f.read(); + str += c; + } + setCharArray(&node.maskOuts,str.c_str()); + if(f.available()<=0 || f.read()!='\n') + return false; + str = ""; + c=f.read(); + while((c != ',') && (f.available()>0)) + { + str += c; + c=f.read(); + } + if(str.length()==0) + return false; + chars=atoi(str.c_str()); + str = ""; + for(int i=0;i0;i++) + { + c=f.read(); + str += c; + } + setCharArray(&node.delimiters,str.c_str()); + if(f.available()<=0 || f.read()!='\n') + return true; + str = ""; + c=f.read(); + while((c != ',') && (f.available()>0)) + { + str += c; + c=f.read(); + } + if(str.length()==0) + return false; + chars=atoi(str.c_str()); + str = ""; + for(int i=0;i0;i++) + { + str += c; + c=f.read(); + } + setCharArray(&node.stateMachine,str.c_str()); + } + return true; +} + +void WiFiServerNode::SaveWiFiServers() +{ + SPIFFS.remove("/zlisteners.txt"); + delay(500); + File f = SPIFFS.open("/zlisteners.txt", "w"); + int ct=0; + WiFiServerNode *serv = servs; + while(serv != null) + { + f.printf("%d,%d\n",serv->port,serv->flagsBitmap); + if(serv->maskOuts == NULL) + f.printf("0,\n"); + else + f.printf("%d,%s\n",strlen(serv->maskOuts),serv->maskOuts); + if(serv->delimiters == NULL) + f.printf("0,\n"); + else + f.printf("%d,%s\n",strlen(serv->delimiters),serv->delimiters); + if(serv->stateMachine == NULL) + f.printf("0,\n"); + else + f.printf("%d,%s\n",strlen(serv->stateMachine),serv->stateMachine); + ct++; + serv=serv->next; + } + f.close(); + delay(500); + if(SPIFFS.exists("/zlisteners.txt")) + { + File f = SPIFFS.open("/zlisteners.txt", "r"); + bool fail=false; + while(f.available()>5) + { + WiFiServerSpec snode; + if(!ReadWiFiServer(f,snode)) + { + fail=true; + break; + } + } + f.close(); + if(fail) + { + delay(100); + SaveWiFiServers(); + } + } +} + +void WiFiServerNode::DestroyAllServers() +{ + while(servs != null) + { + WiFiServerNode *s=servs; + delete s; + } +} + +WiFiServerNode *WiFiServerNode::FindServer(int port) +{ + WiFiServerNode *s=servs; + while(s != null) + { + if(s->port == port) + return s; + s=s->next; + } + return null; +} + + +void WiFiServerNode::RestoreWiFiServers() +{ + if(SPIFFS.exists("/zlisteners.txt")) + { + File f = SPIFFS.open("/zlisteners.txt", "r"); + bool fail=false; + while(f.available()>0) + { + WiFiServerSpec snode; + if(!ReadWiFiServer(f,snode)) + { + debugPrintf("Server: FAIL\n"); + fail=true; + break; + } + WiFiServerNode *s=servs; + while(s != null) + { + if(s->port == snode.port) + break; + s=s->next; + } + if(s==null) + { + WiFiServerNode *node = new WiFiServerNode(snode.port, snode.flagsBitmap); + setCharArray(&node->delimiters,snode.delimiters); + setCharArray(&node->maskOuts,snode.maskOuts); + setCharArray(&node->stateMachine,snode.stateMachine); + debugPrintf("Server: %d, %d: '%s' '%s'\n",node->port,node->flagsBitmap,node->delimiters,node->maskOuts); + } + else + debugPrintf("Server: DUP %d\n",snode.port); + } + f.close(); + } +} + + diff --git a/src/wifisshclient.ino b/src/wifisshclient.ino new file mode 100644 index 0000000..aa3eae5 --- /dev/null +++ b/src/wifisshclient.ino @@ -0,0 +1,302 @@ +#include +#ifdef INCLUDE_SSH +/* + Copyright 2023-2023 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +WiFiSSHClient::WiFiSSHClient() +{ + libssh2_init(0); +} + +WiFiSSHClient::~WiFiSSHClient() +{ + closeSSH(); + libssh2_exit(); +} + +void WiFiSSHClient::closeSSH() +{ + if(channel) { + libssh2_channel_send_eof(channel); + libssh2_channel_close(channel); + libssh2_channel_free(channel); + channel = null; + } + if(session) { + libssh2_session_disconnect(session, "exit"); + libssh2_session_free(session); + session = null; + } + if(sock) { + close(sock); + sock = null; + } +} + +WiFiSSHClient &WiFiSSHClient::operator=(const WiFiSSHClient &other) +{ + _username = other._username; + _password = other._password; + sock = other.sock; + session = other.session; + channel = other.channel; + ibufSz = other.ibufSz; + memcpy(ibuf, other.ibuf, INTERNAL_BUF_SIZE); + return *this; +} + +void WiFiSSHClient::stop() +{ + closeSSH(); +} + + +int WiFiSSHClient::connect(IPAddress ip, uint16_t port) +{ + sock = socket(AF_INET, SOCK_STREAM, 0); + + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = ip; + if(::connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) + return false; + session = libssh2_session_init(); + if(libssh2_session_handshake(session, sock)) + { + debugPrintf("wifisshclient: failed handshake\n\r"); + closeSSH(); + return false; + } + if(!finishLogin()) + { + debugPrintf("wifisshclient: failed login\n\r"); + closeSSH(); + return false; + } + channel = libssh2_channel_open_session(session); + if(!channel) + { + debugPrintf("wifisshclient: failed session\n\r"); + closeSSH(); + return false; + } + if(libssh2_channel_request_pty(channel, "vanilla")) + { + debugPrintf("wifisshclient: failed pty\n\r"); + closeSSH(); + return false; + } + if(libssh2_channel_shell(channel)) { + debugPrintf("wifisshclient: failed shell\n\r"); + closeSSH(); + return false; + } + ssize_t err = libssh2_channel_read(channel, ibuf, INTERNAL_BUF_SIZE); + if((err > 0) && (err != LIBSSH2_ERROR_EAGAIN)) { + ibufSz += err; + } +} +int WiFiSSHClient::connect(IPAddress ip, uint16_t port, int32_t timeout_ms) +{ + return connect(ip, port); +} + +int WiFiSSHClient::connect(const char *host, uint16_t port) +{ + if(host == null) + return false; + IPAddress hostIp((uint32_t)0); + if(!WiFiGenericClass::hostByName(host, hostIp)) + return false; + return connect(hostIp, port); +} + +int WiFiSSHClient::connect(const char *host, uint16_t port, int32_t timeout_ms) +{ + return connect(host, port); +} + + +static char _secret[256]; +static void kbd_callback(const char *name, int name_len, const char *instruction, int instruction_len, + int num_prompts, const struct _LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) +{ + (void)name; + (void)name_len; + (void)instruction; + (void)instruction_len; + if(num_prompts == 1) { + responses[0].text = strdup(_secret); + responses[0].length = strlen(_secret); + } + (void)prompts; + (void)abstract; +} + +bool WiFiSSHClient::finishLogin() +{ + if(_username.length()==0) + return true; + char *userauthlist; + /* check what authentication methods are available */ + userauthlist = libssh2_userauth_list(session, _username.c_str(), strlen(_username.c_str())); + if(strstr(userauthlist, "password") != NULL) + { + if(libssh2_userauth_password(session, _username.c_str(), _password.c_str())) + return false; + return true; + } + else + if(strstr(userauthlist, "keyboard-interactive") != NULL) + { + strcpy(_secret,_password.c_str()); + if(libssh2_userauth_keyboard_interactive(session, _username.c_str(), &kbd_callback) ) + return false; + else + return true; + } + else + if(strstr(userauthlist, "publickey") != NULL) + { + /* + size_t fn1sz, fn2sz; + char *fn1, *fn2; + char const *h = getenv("HOME"); + if(!h || !*h) + h = "."; + fn1sz = strlen(h) + strlen(keyfile1) + 2; + fn2sz = strlen(h) + strlen(keyfile2) + 2; + fn1 = (char *)malloc(fn1sz); + fn2 = (char *)malloc(fn2sz); + // Using asprintf() here would be much cleaner, but less portable + snprintf(fn1, fn1sz, "%s/%s", h, keyfile1); + snprintf(fn2, fn2sz, "%s/%s", h, keyfile2); + + if(libssh2_userauth_publickey_fromfile(session, username, fn1, + fn2, password)) { + free(fn2); + free(fn1); + return false; + goto shutdown; + } + free(fn2); + free(fn1); + return true; + */ + return false; + } + else + return false; +} + +void WiFiSSHClient::setLogin(String username, String password) +{ + _username = username; + _password = password; +} + +void WiFiSSHClient::intern_buffer_fill() +{ + if((session != null) && (channel != null)) + { + const size_t bufRemain = INTERNAL_BUF_SIZE - ibufSz; + if(bufRemain > 0) + { + libssh2_session_set_blocking(session, 0); + ssize_t err = libssh2_channel_read(channel, ibuf + ibufSz, bufRemain); + libssh2_session_set_blocking(session, 1); + if((err > 0) && (err != LIBSSH2_ERROR_EAGAIN)) { + ibufSz += err; + } + } + } +} + +int WiFiSSHClient::peek() +{ + intern_buffer_fill(); + if(ibufSz > 0) + return ibuf[0]; + return -1; +} + +size_t WiFiSSHClient::write(uint8_t data) +{ + return write(&data, 1); +} + +int WiFiSSHClient::read() +{ + uint8_t b[1]; + size_t num = read(b, 1); + if(num > 0) + return b[0]; + return -1; +} + +size_t WiFiSSHClient::write(const uint8_t *buf, size_t size) +{ + if(channel != null) + return libssh2_channel_write(channel, (const char *)buf, size); + return 0; +} + +int WiFiSSHClient::read(uint8_t *buf, size_t size) +{ + size_t bytesRead = 0; + while(bytesRead < size) + { + intern_buffer_fill(); + if(ibufSz == 0) + break; + size_t bytesToTake = size - bytesRead; + if(bytesToTake > ibufSz) + bytesToTake = ibufSz; + memcpy(buf, ibuf, bytesToTake); + if(ibufSz > bytesToTake) // if there are still buffer bytes remaining + memcpy(ibuf, ibuf + bytesToTake, ibufSz - bytesToTake); + ibufSz -= bytesToTake; + bytesRead += bytesToTake; + } + return (int)bytesRead; +} + +int WiFiSSHClient::available() +{ + intern_buffer_fill(); + return ibufSz; +} + +uint8_t WiFiSSHClient::connected() +{ + if(ibufSz > 0) + return true; + intern_buffer_fill(); + if(ibufSz > 0) + return true; + if(channel == null) + return false; + return !libssh2_channel_eof(channel); +} + +int WiFiSSHClient::fd() const +{ + return sock; +} + +#endif diff --git a/src/zbrowser.ino b/src/zbrowser.ino new file mode 100644 index 0000000..fe543a0 --- /dev/null +++ b/src/zbrowser.ino @@ -0,0 +1,1178 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef INCLUDE_SD_SHELL + +static void initSDShell() +{ + if(SD.begin()) + { + } +} + +ZBrowser::~ZBrowser() +{ + if(ftpHost != 0) + { + delete ftpHost; + ftpHost = 0; + } +} + +void ZBrowser::switchTo() +{ + currMode=&browseMode; + init(); +} + +void ZBrowser::init() +{ + serial.setFlowControlType(commandMode.serial.getFlowControlType()); + serial.setPetsciiMode(commandMode.serial.isPetsciiMode()); + savedEcho=commandMode.doEcho; + commandMode.doEcho=true; + serial.setXON(true); + showMenu=true; + EOLN=commandMode.EOLN; + strcpy(EOLNC,commandMode.EOLN.c_str()); + currState = ZBROW_MAIN; + lastNumber=0; + lastString=""; +} + +void ZBrowser::serialIncoming() +{ + bool crReceived=commandMode.readSerialStream(); + commandMode.clearPlusProgress(); // re-check the plus-escape mode + if(crReceived) + { + if(commandMode.doEcho) + serial.prints(EOLN); + String line =commandMode.getNextSerialCommand(); + doModeCommand(line); + } +} + +void ZBrowser::switchBackToCommandMode() +{ + commandMode.doEcho=savedEcho; + currMode = &commandMode; +} + +String ZBrowser::fixPathNoSlash(String p) +{ + String finalPath=""; + int lastX=0; + uint16_t backStack[256] = {0}; + backStack[0]=1; + int backX=1; + for(int i=0;ilastX) + { + String sub=p.substring(lastX,i); + if(sub.equals(".")) + { + // do nothing + } + else + if(sub.equals("..")) + { + if(backX > 1) + finalPath = finalPath.substring(0,backStack[--backX]); + } + else + if(sub.length()>0) + { + finalPath += sub; + finalPath += "/"; + backStack[++backX]=finalPath.length(); + } + } + else + if((i==0) && (i 1) + finalPath = finalPath.substring(0,backStack[--backX]); + } + else + { + finalPath += sub; + finalPath += "/"; // why this?! -- oh, so it can be removed below? + } + } + if(finalPath.length()==0) + return "/"; + if(finalPath.length()>1) + finalPath.remove(finalPath.length()-1); + return finalPath; +} + +String ZBrowser::stripDir(String p) +{ + int x=p.lastIndexOf("/"); + if(x<=0) + return "/"; + return p.substring(0,x); +} + +String ZBrowser::stripFilename(String p) +{ + int x=p.lastIndexOf("/"); + if((x<0)||(x==p.length()-1)) + return ""; + return p.substring(x+1); +} + +String ZBrowser::cleanFirstArg(String line) +{ + int state=0; + String arg=""; + for(int i=0;i0) + { + if(addendum.startsWith("/")) + return fixPathNoSlash(addendum); + else + return fixPathNoSlash(path + addendum); + } + return fixPathNoSlash(path); +} + +bool ZBrowser::isMask(String mask) +{ + return (mask.indexOf("*")>=0) || (mask.indexOf("?")>=0); +} + +String ZBrowser::stripArgs(String line, String &argLetters) +{ + while(line.startsWith("-")) + { + int x=line.indexOf(' '); + if(x<0) + { + argLetters = line.substring(1); + return ""; + } + argLetters += line.substring(1,x); + line = line.substring(x+1); + line.trim(); + } + return line; +} + +void ZBrowser::copyFiles(String source, String mask, String target, bool recurse, bool overwrite) +{ + int maskFilterLen = source.length(); + if(!source.endsWith("/")) + maskFilterLen++; + + File root = SD.open(source); + if(!root) + { + serial.printf("Unknown path: %s%s",source.c_str(),EOLNC); + return; + } + + if(root.isDirectory()) + { + if(!SD.exists(target)) // cp d a + { + SD.mkdir(target); + } + else + { + File DD=SD.open(target); //cp d d2, cp d f + if(!DD.isDirectory()) + { + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return; + } + } + for(File file = root.openNextFile(); file != null; file = root.openNextFile()) + { + if(matches(file.name()+maskFilterLen, mask)) + { + debugPrintf("file matched:%s\n",file.name()); + String tpath = target; + if(file.isDirectory()) + { + if(!recurse) + serial.printf("Skipping: %s%s",file.name(),EOLNC); + else + { + if(!tpath.endsWith("/")) + tpath += "/"; + tpath += stripFilename(file.name()); + } + } + copyFiles(file.name(),"",tpath,false,overwrite); + } + } + } + else + { + String tpath = target; + if(SD.exists(tpath)) + { + File DD=SD.open(tpath); + if(DD.isDirectory()) // cp f d, cp f . + { + if(!tpath.endsWith("/")) + tpath += "/"; + tpath += stripFilename(root.name()); + debugPrintf("file xform to file in dir:%s\n",tpath.c_str()); + } + DD.close(); + } + if(SD.exists(tpath)) + { + File DD=SD.open(tpath); + if(strcmp(DD.name(),root.name())==0) + { + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return; + } + else + if(!overwrite) // cp f a, cp f e + { + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return; + } + else + { + DD.close(); + SD.remove(tpath); + } + } + size_t len=root.size(); + File tfile = SD.open(tpath,FILE_WRITE); + for(int i=0;i=fname.length()) + return false; + if(mask[i]=='?') + f++; + else + if(mask[i]=='*') + { + if(i==mask.length()-1) + return true; + int remain=mask.length()-i-1; + f=fname.length()-remain; + } + else + if(mask[i]!=fname[f++]) + return false; + } + return true; +} + +void ZBrowser::showDirectory(String p, String mask, String prefix, bool recurse) +{ + int maskFilterLen = p.length(); + if(!p.endsWith("/")) + maskFilterLen++; + + File root = SD.open(p); + if(!root) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + else + if(root.isDirectory()) + { + File file = root.openNextFile(); + while(file) + { + if(matches(file.name()+maskFilterLen, mask)) + { + debugPrintf("file matched:%s\n",file.name()); + if(file.isDirectory()) + { + serial.printf("%sd %s%s",prefix.c_str(),file.name()+maskFilterLen,EOLNC); + if(recurse) + { + String newPrefix = prefix + " "; + showDirectory(file.name(), mask, newPrefix, recurse); + } + } + else + serial.printf("%s %s %lu%s",prefix.c_str(),file.name()+maskFilterLen,file.size(),EOLNC); + } + else + debugPrintf("file unmatched:%s (%s)\n",file.name(),mask.c_str()); + file = root.openNextFile(); + } + } + else + serial.printf(" %s %lu%s",root.name(),root.size(),EOLNC); +} + +void ZBrowser::doModeCommand(String &line) +{ + char c='?'; + for(int i=0;i 0) + { + cmd=line.substring(0,sp); + cmd.trim(); + line = line.substring(sp+1); + line.trim(); + } + else + line = ""; + switch(currState) + { + case ZBROW_MAIN: + { + if(cmd.length()==0) + showMenu=true; + else + if(cmd.equalsIgnoreCase("exit")||cmd.equalsIgnoreCase("quit")||cmd.equalsIgnoreCase("x")||cmd.equalsIgnoreCase("endshell")) + { + serial.prints("OK"); + serial.prints(EOLN); + //commandMode.showInitMessage(); + switchBackToCommandMode(); + return; + } + else + if(cmd.equalsIgnoreCase("ls")||cmd.equalsIgnoreCase("dir")||cmd.equalsIgnoreCase("$")||cmd.equalsIgnoreCase("list")) + { + String argLetters = ""; + line = stripArgs(line,argLetters); + argLetters.toLowerCase(); + bool recurse=argLetters.indexOf('r')>=0; + String rawPath = makePath(cleanOneArg(line)); + String p; + String mask; + if((line.length()==0)||(line.endsWith("/"))) + { + p=rawPath; + mask=""; + } + else + { + mask=stripFilename(rawPath); + if((mask.length()>0)&&(isMask(mask))) + p=stripDir(rawPath); + else + { + mask=""; + p=rawPath; + } + } + showDirectory(p,mask,"",recurse); + } + else + if(cmd.equalsIgnoreCase("md")||cmd.equalsIgnoreCase("mkdir")||cmd.equalsIgnoreCase("makedir")) + { + String p = makePath(cleanOneArg(line)); + debugPrintf("md:%s\n",p.c_str()); + if((p.length() < 2) || isMask(p) || !SD.mkdir(p)) + serial.printf("Illegal path: %s%s",p.c_str(),EOLNC); + } + else + if(cmd.equalsIgnoreCase("cd")) + { + String p = makePath(cleanOneArg(line)); + debugPrintf("cd:%s\n",p.c_str()); + if(p.length()==0) + serial.printf("Current path: %s%s",p.c_str(),EOLNC); + else + if(p == "/") + path = "/"; + else + if(p.length()>1) + { + File root = SD.open(p); + if(!root) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + else + if(!root.isDirectory()) + serial.printf("Illegal path: %s%s",p.c_str(),EOLNC); + else + path = p + "/"; + } + } + else + if(cmd.equalsIgnoreCase("rd")||cmd.equalsIgnoreCase("rmdir")||cmd.equalsIgnoreCase("deletedir")) + { + String p = makePath(cleanOneArg(line)); + debugPrintf("rd:%s\n",p.c_str()); + File root = SD.open(p); + if(!root) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + else + if(!root.isDirectory()) + serial.printf("Not a directory: %s%s",p.c_str(),EOLNC); + else + if(!SD.rmdir(p)) + serial.printf("Failed to remove directory: %s%s",p.c_str(),EOLNC); + } + else + if(cmd.equalsIgnoreCase("cat")||cmd.equalsIgnoreCase("type")) + { + String p = makePath(cleanOneArg(line)); + debugPrintf("cat:%s\n",p.c_str()); + File root = SD.open(p); + if(!root) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + else + if(root.isDirectory()) + serial.printf("Is a directory: %s%s",p.c_str(),EOLNC); + else + { + root.close(); + File f=SD.open(p, FILE_READ); + for(int i=0;i=0; + String rawPath = makePath(cleanOneArg(line)); + String p=stripDir(rawPath); + String mask=stripFilename(rawPath); + debugPrintf("rm:%s (%s)\n",p.c_str(),mask.c_str()); + deleteFile(p,mask,recurse); + } + else + if(cmd.equalsIgnoreCase("cp")||cmd.equalsIgnoreCase("copy")) + { + String argLetters = ""; + line = stripArgs(line,argLetters); + argLetters.toLowerCase(); + bool recurse=argLetters.indexOf('r')>=0; + bool overwrite=argLetters.indexOf('f')>=0; + String p1=makePath(cleanFirstArg(line)); + String p2=makePath(cleanRemainArg(line)); + String mask; + if((line.length()==0)||(line.endsWith("/"))) + mask=""; + else + { + mask=stripFilename(p1); + if(!isMask(mask)) + mask=""; + else + p1=stripDir(p1); + } + debugPrintf("cp:%s (%s) -> %s\n",p1.c_str(),mask.c_str(), p2.c_str()); + copyFiles(p1,mask,p2,recurse,overwrite); + } + else + if(cmd.equalsIgnoreCase("df")||cmd.equalsIgnoreCase("free")||cmd.equalsIgnoreCase("info")) + { + serial.printf("%llu free of %llu total%s",(SD.totalBytes()-SD.usedBytes()),SD.totalBytes(),EOLNC); + } + else + if(cmd.equalsIgnoreCase("ren")||cmd.equalsIgnoreCase("rename")) + { + + String p1=makePath(cleanFirstArg(line)); + String p2=makePath(cleanRemainArg(line)); + debugPrintf("ren:%s -> %s\n",p1.c_str(), p2.c_str()); + if(p1 == p2) + serial.printf("File exists: %s%s",p1.c_str(),EOLNC); + else + if(SD.exists(p2)) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + else + { + if(!SD.rename(p1,p2)) + serial.printf("Failed to rename: %s%s",p1.c_str(),EOLNC); + } + } + else + if(cmd.equalsIgnoreCase("wget")) + { + String p1=cleanFirstArg(line); + String p2=makePath(cleanRemainArg(line)); + debugPrintf("wget:%s -> %s\n",p1.c_str(), p2.c_str()); + if((p1.length()<8) + || ((strcmp(p1.substring(0,7).c_str(),"http://") != 0) + && (strcmp(p1.substring(0,9).c_str(),"https://") != 0))) + serial.printf("Not a url: %s%s",p1.c_str(),EOLNC); + else + if(SD.exists(p2)) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + else + { + char buf[p1.length()+1]; + strcpy(buf,p1.c_str()); + char *hostIp; + char *req; + int port; + bool doSSL; + if(!parseWebUrl((uint8_t *)buf,&hostIp,&req,&port,&doSSL)) + serial.printf("Invalid url: %s",p1.c_str()); + else + if(!doWebGet(hostIp, port, &SD, p2.c_str(), req, doSSL)) + serial.printf("Wget failed: %s to file %s",p1.c_str(),p2.c_str()); + } + } + else + if(cmd.equalsIgnoreCase("fget")) + { + String p1=cleanFirstArg(line); + String p2=cleanRemainArg(line); + if(p2.length() == 0) + { + int slash = p1.lastIndexOf('/'); + if(slash < p1.length()-1) + p2 = makePath(p1.substring(slash+1)); + else + p2 = makePath(p1); + } + else + p2=makePath(p2); + debugPrintf("fget:%s -> %s\n",p1.c_str(), p2.c_str()); + char *tmp=0; + bool isUrl = ((p1.length()>=11) + && ((strcmp(p1.substring(0,6).c_str(),"ftp://") == 0) + || (strcmp(p1.substring(0,7).c_str(),"ftps://") == 0))); + if((ftpHost==0)&&(!isUrl)) + serial.printf("Url required: %s%s",p1.c_str(),EOLNC); + else + if(SD.exists(p2)) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + else + { + uint8_t buf[p1.length()+1]; + strcpy((char *)buf,p1.c_str()); + char *req; + ftpHost = makeFTPHost(isUrl,ftpHost,buf,&req); + if(req == 0) + serial.printf("Invalid url: %s",p1.c_str()); + else + if(!ftpHost->doGet(&SD, p2.c_str(), req)) + serial.printf("Fget failed: %s to file %s",p1.c_str(),p2.c_str()); + } + } + else + if(cmd.equalsIgnoreCase("fput")) + { + String p1=makePath(cleanFirstArg(line)); + String p2=cleanRemainArg(line); + debugPrintf("fput:%s -> %s\n",p1.c_str(), p2.c_str()); + char *tmp=0; + bool isUrl = ((p2.length()>=11) + && ((strcmp(p2.substring(0,6).c_str(),"ftp://") == 0) + || (strcmp(p2.substring(0,7).c_str(),"ftps://") == 0))); + if((ftpHost==0)&&(!isUrl)) + serial.printf("Url required: %s%s",p2.c_str(),EOLNC); + else + if(!SD.exists(p1)) + serial.printf("File not found: %s%s",p1.c_str(),EOLNC); + else + { + uint8_t buf[p2.length()+1]; + strcpy((char *)buf,p2.c_str()); + File file = SD.open(p1); + char *req; + ftpHost = makeFTPHost(isUrl,ftpHost,buf,&req); + if(req == 0) + serial.printf("Invalid url: %s",p2.c_str()); + else + if(!file) + serial.printf("File not found: %s%s",p1.c_str(),EOLNC); + else + { + if(!ftpHost->doPut(file, req)) + serial.printf("Fput failed: %s from file %s",p2.c_str(),p1.c_str()); + file.close(); + } + } + } + else + if(cmd.equalsIgnoreCase("fls") || cmd.equalsIgnoreCase("fdir")) + { + String p1=cleanOneArg(line); + debugPrintf("fls:%s\n",p1.c_str()); + char *tmp=0; + bool isUrl = ((p1.length()>=11) + && ((strcmp(p1.substring(0,6).c_str(),"ftp://") == 0) + || (strcmp(p1.substring(0,7).c_str(),"ftps://") == 0))); + if((ftpHost==0)&&(!isUrl)) + serial.printf("Url required: %s%s",p1.c_str(),EOLNC); + else + { + uint8_t buf[p1.length()+1]; + strcpy((char *)buf,p1.c_str()); + char *req; + ftpHost = makeFTPHost(isUrl,ftpHost,buf,&req); + if(req == 0) + serial.printf("Invalid url: %s",p1.c_str()); + else + if(!ftpHost->doLS(&serial, req)) + serial.printf("Fls failed: %s",p1.c_str()); + } + } + else + if(cmd.equalsIgnoreCase("mv")||cmd.equalsIgnoreCase("move")) + { + String argLetters = ""; + line = stripArgs(line,argLetters); + argLetters.toLowerCase(); + bool overwrite=argLetters.indexOf('f')>=0; + String p1=makePath(cleanFirstArg(line)); + String p2=makePath(cleanRemainArg(line)); + String mask; + if((line.length()==0)||(line.endsWith("/"))) + mask=""; + else + { + mask=stripFilename(p1); + if((mask.length()>0)&&(isMask(mask))) + p1=stripDir(p1); + else + mask = ""; + } + debugPrintf("mv:%s(%s) -> %s\n",p1.c_str(),mask.c_str(),p2.c_str()); + if((mask.length()==0)||(!isMask(mask))) + { + File root = SD.open(p2); + if(root && root.isDirectory()) + { + if (!p2.endsWith("/")) + p2 += "/"; + p2 += stripFilename(p1); + debugPrintf("mv:%s -> %s\n",p1.c_str(),p2.c_str()); + } + root.close(); + if(p1 == p2) + serial.printf("File exists: %s%s",p1.c_str(),EOLNC); + else + if(SD.exists(p2) && (!overwrite)) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + else + { + if(SD.exists(p2)) + SD.remove(p2); + if(!SD.rename(p1,p2)) + serial.printf("Failed to move: %s%s",p1.c_str(),EOLNC); + } + } + else + { + copyFiles(p1,mask,p2,false,overwrite); + deleteFile(p1,mask,false); + } + } + else + if(cmd.equals("?")||cmd.equals("help")) + { + serial.printf("Commands:%s",EOLNC); + serial.printf("ls/dir/list/$ [-r] [/][path] - List files%s",EOLNC); + serial.printf("cd [/][path][..] - Change to new directory%s",EOLNC); + serial.printf("md/mkdir/makedir [/][path] - Create a new directory%s",EOLNC); + serial.printf("rd/rmdir/deletedir [/][path] - Delete a directory%s",EOLNC); + serial.printf("rm/del/delete [-r] [/][path]filename - Delete a file%s",EOLNC); + serial.printf("cp/copy [-r] [-f] [/][path]file [/][path]file - Copy file(s)%s",EOLNC); + serial.printf("ren/rename [/][path]file [/][path]file - Rename a file%s",EOLNC); + serial.printf("mv/move [-f] [/][path]file [/][path]file - Move file(s)%s",EOLNC); + serial.printf("cat/type [/][path]filename - View a file(s)%s",EOLNC); + serial.printf("df/free/info - Show space remaining%s",EOLNC); + serial.printf("xget/zget/kget [/][path]filename - Download a file%s",EOLNC); + serial.printf("xput/zput/kput [/][path]filename - Upload a file%s",EOLNC); + serial.printf("wget [http://url] [/][path]filename - Download url to file%s",EOLNC); + serial.printf("fget [ftp://user:pass@url/file] [/][path]file - FTP get file%s",EOLNC); + serial.printf("fput [/][path]file [ftp://user:pass@url/file] - FTP put file%s",EOLNC); + serial.printf("fdir [ftp://user:pass@url/path] - ftp url dir%s",EOLNC); + serial.printf("exit/quit/x/endshell - Quit to command mode%s",EOLNC); + serial.printf("%s",EOLNC); + } + else + serial.printf("Unknown command: '%s'. Try '?'.%s",cmd.c_str(),EOLNC); + showMenu=true; + break; + } + } +} + +void ZBrowser::loop() +{ + if(showMenu) + { + showMenu=false; + switch(currState) + { + case ZBROW_MAIN: + { + serial.printf("%s%s> ",EOLNC,path.c_str()); + break; + } + } + } + if(commandMode.checkPlusEscape()) + { + switchBackToCommandMode(); + } + else + if(serial.isSerialOut()) + { + serialOutDeque(); + } +} +#else +static void initSDShell() +{} +#endif diff --git a/src/zcommand.ino b/src/zcommand.ino new file mode 100644 index 0000000..30dfe9d --- /dev/null +++ b/src/zcommand.ino @@ -0,0 +1,3798 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +extern "C" void esp_schedule(); +extern "C" void esp_yield(); + +#ifdef INCLUDE_PING +# include "proto_ping.h" +#endif + +ZCommand::ZCommand() +{ + strcpy(CRLF,"\r\n"); + strcpy(LFCR,"\n\r"); + strcpy(LF,"\n"); + strcpy(CR,"\r"); + strcpy(ECS,"+++"); + freeCharArray(&tempMaskOuts); + freeCharArray(&tempDelimiters); + freeCharArray(&tempStateMachine); + setCharArray(&delimiters,""); + setCharArray(&maskOuts,""); + setCharArray(&stateMachine,""); + machineState = stateMachine; +} + +static void parseHostInfo(uint8_t *vbuf, char **hostIp, int *port, char **username, char **password) +{ + char *at=strstr((char *)vbuf,"@"); + *hostIp = (char *)vbuf; + if(at != null) + { + *at = 0; + *hostIp = at+1; + char *ucol = strstr((char *)vbuf,":"); + *username = (char *)vbuf; + if(ucol != null) + { + *ucol = 0; + *password = ucol + 1; + } + } + char *colon=strstr(*hostIp,":"); + if(colon != null) + { + (*colon)=0; + *port=atoi((char *)(++colon)); + } +} + +static bool validateHostInfo(uint8_t *vbuf) +{ + String cmd = (char *)vbuf; + int atDex=cmd.indexOf('@'); + int colonDex=cmd.indexOf(':'); + int lastColonDex=cmd.lastIndexOf(':'); + bool fail = cmd.indexOf(',') >= 0; + if(atDex >=0) + { + colonDex=cmd.indexOf(':',atDex+1); + fail = cmd.indexOf(',') >= atDex; + } + fail = fail || (colonDex <= 0) || (colonDex == cmd.length()-1); + fail = fail || (colonDex != cmd.lastIndexOf(':')); + if(!fail) + { + for(int i=colonDex+1;i20) + { + logFile.print("\r\ncrc8: "); + c=0; + } + else + logFile.print(" "); + } + */ + for (byte tempI = 8; tempI; tempI--) + { + byte sum = (crc ^ extract) & 0x01; + crc >>= 1; + if (sum) { + crc ^= 0x8C; + } + extract >>= 1; + } + } + logPrintf("\r\nFinal CRC8: %s\r\n",TOHEX(crc)); + return crc; +} + +void ZCommand::setConfigDefaults() +{ + doEcho=true; + autoStreamMode=false; + telnetSupport=true; + preserveListeners=false; + ringCounter=1; + serial.setFlowControlType(DEFAULT_FCT); + serial.setXON(true); + packetXOn = true; + serial.setPetsciiMode(false); + binType=BTYPE_NORMAL; + serialDelayMs=0; + dcdActive=DEFAULT_DCD_HIGH; + dcdInactive=DEFAULT_DCD_LOW; +# ifdef HARD_DCD_HIGH + dcdInactive=DEFAULT_DCD_HIGH; +# elif defined(HARD_DCD_LOW) + dcdActive=DEFAULT_DCD_LOW; +# endif + ctsActive=DEFAULT_CTS_HIGH; + ctsInactive=DEFAULT_CTS_LOW; + rtsActive=DEFAULT_RTS_HIGH; + rtsInactive=DEFAULT_RTS_LOW; + riActive = DEFAULT_RTS_HIGH; + riInactive = DEFAULT_RTS_LOW; + dtrActive = DEFAULT_RTS_HIGH; + dtrInactive = DEFAULT_RTS_LOW; + dsrActive = DEFAULT_RTS_HIGH; + dsrInactive = DEFAULT_RTS_LOW; + pinDCD = DEFAULT_PIN_DCD; + pinCTS = getDefaultCtsPin(); + pinRTS = DEFAULT_PIN_RTS; + pinDTR = DEFAULT_PIN_DTR; + pinDSR = DEFAULT_PIN_DSR; + pinRI = DEFAULT_PIN_RI; + dcdStatus = dcdInactive; + if(pinSupport[pinRTS]) + pinMode(pinRTS,OUTPUT); + if(pinSupport[pinCTS]) + pinMode(pinCTS,INPUT); + if(pinSupport[pinDCD]) + pinMode(pinDCD,OUTPUT); + if(pinSupport[pinDTR]) + pinMode(pinDTR,INPUT); + if(pinSupport[pinDSR]) + pinMode(pinDSR,OUTPUT); + if(pinSupport[pinRI]) + pinMode(pinRI,OUTPUT); + s_pinWrite(pinRTS,rtsActive); + s_pinWrite(pinDCD,dcdStatus); + s_pinWrite(pinDSR,dsrActive); + s_pinWrite(pinRI,riInactive); + suppressResponses=false; + numericResponses=false; + longResponses=true; + packetSize=127; + strcpy(CRLF,"\r\n"); + strcpy(LFCR,"\n\r"); + strcpy(LF,"\n"); + strcpy(CR,"\r"); + EC='+'; + strcpy(ECS,"+++"); + BS=8; + EOLN = CRLF; + tempBaud = -1; + freeCharArray(&tempMaskOuts); + freeCharArray(&tempDelimiters); + freeCharArray(&tempStateMachine); + setCharArray(&delimiters,""); + setCharArray(&stateMachine,""); + machineState = stateMachine; + termType = DEFAULT_TERMTYPE; + busyMsg = DEFAULT_BUSYMSG; +#ifdef SUPPORT_LED_PINS + if(pinSupport[DEFAULT_PIN_AA]) + pinMode(DEFAULT_PIN_AA,OUTPUT); + if(pinSupport[DEFAULT_PIN_WIFI]) + pinMode(DEFAULT_PIN_WIFI,OUTPUT); + if(pinSupport[DEFAULT_PIN_HS]) + pinMode(DEFAULT_PIN_HS,OUTPUT); +#endif +} + +char lc(char c) +{ + if((c>=65) && (c<=90)) + return c+32; + if((c>=193) && (c<=218)) + return c-96; + return c; +} + +void ZCommand::connectionArgs(WiFiClientNode *c) +{ + setCharArray(&(c->delimiters),tempDelimiters); + setCharArray(&(c->maskOuts),tempMaskOuts); + setCharArray(&(c->stateMachine),tempStateMachine); + freeCharArray(&tempDelimiters); + freeCharArray(&tempMaskOuts); + freeCharArray(&tempStateMachine); + c->machineState = c->stateMachine; +} + +ZResult ZCommand::doResetCommand() +{ + while(conns != null) + { + WiFiClientNode *c=conns; + delete c; + } + current = null; + nextConn = null; + WiFiServerNode::DestroyAllServers(); + setConfigDefaults(); + String argv[CFG_LAST+1]; + parseConfigOptions(argv); + eon=0; + serial.setXON(true); + packetXOn = true; + serial.setPetsciiMode(false); + serialDelayMs=0; + binType=BTYPE_NORMAL; + serial.setFlowControlType(DEFAULT_FCT); + setOptionsFromSavedConfig(argv); + memset(nbuf,0,MAX_COMMAND_SIZE); + return ZOK; +} + +ZResult ZCommand::doNoListenCommand() +{ + /* + WiFiClientNode *c=conns; + while(c != null) + { + WiFiClientNode *c2=c->next; + if(c->serverClient) + delete c; + c=c2; + } + */ + WiFiServerNode::DestroyAllServers(); + return ZOK; +} + +FlowControlType ZCommand::getFlowControlType() +{ + return serial.getFlowControlType(); +} + +int pinModeCoder(int activeChk, int inactiveChk, int activeDefault) +{ + if(activeChk == activeDefault) + { + if(inactiveChk == activeDefault) + return 2; + return 0; + } + else + { + if(inactiveChk != activeDefault) + return 3; + return 1; + } +} + +void pinModeDecoder(int mode, int *active, int *inactive, int activeDef, int inactiveDef) +{ + switch(mode) + { + case 0: + *active = activeDef; + *inactive = inactiveDef; + break; + case 1: + *inactive = activeDef; + *active = inactiveDef; + break; + case 2: + *active = activeDef; + *inactive = activeDef; + break; + case 3: + *active = inactiveDef; + *inactive = inactiveDef; + break; + default: + *active = activeDef; + *inactive = inactiveDef; + break; + } +} + +void pinModeDecoder(String newMode, int *active, int *inactive, int activeDef, int inactiveDef) +{ + if(newMode.length()>0) + { + int mode = atoi(newMode.c_str()); + pinModeDecoder(mode,active,inactive,activeDef,inactiveDef); + } +} + +void ZCommand::reSaveConfig() +{ + char hex[256]; + SPIFFS.remove(CONFIG_FILE_OLD); + SPIFFS.remove(CONFIG_FILE); + delay(500); + const char *eoln = EOLN.c_str(); + int dcdMode = pinModeCoder(dcdActive, dcdInactive, DEFAULT_DCD_HIGH); + int ctsMode = pinModeCoder(ctsActive, ctsInactive, DEFAULT_CTS_HIGH); + int rtsMode = pinModeCoder(rtsActive, rtsInactive, DEFAULT_RTS_HIGH); + int riMode = pinModeCoder(riActive, riInactive, DEFAULT_RTS_HIGH); + int dtrMode = pinModeCoder(dtrActive, dtrInactive, DEFAULT_DTR_HIGH); + int dsrMode = pinModeCoder(dsrActive, dsrInactive, DEFAULT_DSR_HIGH); + String wifiSSIhex = TOHEX(wifiSSI.c_str(),hex,256); + String wifiPWhex = TOHEX(wifiPW.c_str(),hex,256); + String zclockFormathex = TOHEX(zclock.getFormat().c_str(),hex,256); + String zclockHosthex = TOHEX(zclock.getNtpServerHost().c_str(),hex,256); + String hostnamehex = TOHEX(hostname.c_str(),hex,256); + String printSpechex = TOHEX(printMode.getLastPrinterSpec(),hex,256); + String termTypehex = TOHEX(termType.c_str(),hex,256); + String busyMsghex = TOHEX(busyMsg.c_str(),hex,256); + String staticIPstr; + ConnSettings::IPtoStr(staticIP,staticIPstr); + String staticDNSstr; + ConnSettings::IPtoStr(staticDNS,staticDNSstr); + String staticGWstr; + ConnSettings::IPtoStr(staticGW,staticGWstr); + String staticSNstr; + ConnSettings::IPtoStr(staticSN,staticSNstr); + + File f = SPIFFS.open(CONFIG_FILE, "w"); + f.printf("%s,%s,%d,%s," + "%d,%d,%d,%d," + "%d,%d,%d,%d,%d," + "%d,%d,%d,%d,%d,%d,%d," + "%d,%d,%d,%d,%d,%d," + "%d," + "%s,%s,%s," + "%d,%s,%s," + "%s,%s,%s,%s," + "%s,%d", + wifiSSIhex.c_str(), wifiPWhex.c_str(), baudRate, eoln, + serial.getFlowControlType(), doEcho, suppressResponses, numericResponses, + longResponses, serial.isPetsciiMode(), dcdMode, serialConfig, ctsMode, + rtsMode,pinDCD,pinCTS,pinRTS,ringCounter,autoStreamMode,preserveListeners, + riMode,dtrMode,dsrMode,pinRI,pinDTR,pinDSR, + zclock.isDisabled()?999:zclock.getTimeZoneCode(), + zclockFormathex.c_str(),zclockHosthex.c_str(),hostnamehex.c_str(), + printMode.getTimeoutDelayMs(),printSpechex.c_str(),termTypehex.c_str(), + staticIPstr.c_str(),staticDNSstr.c_str(),staticGWstr.c_str(),staticSNstr.c_str(), + busyMsghex.c_str(),telnetSupport + ); + f.close(); + delay(500); + if(SPIFFS.exists(CONFIG_FILE)) + { + File f=SPIFFS.open(CONFIG_FILE, "r"); + String str=f.readString(); + f.close(); + int argn=0; + if((str!=null)&&(str.length()>0)) + { + debugPrintf("Saved Config: %s\n",str.c_str()); + for(int i=0;i0) + { + EOLN = configArguments[CFG_EOLN]; + } + if(configArguments[CFG_FLOWCONTROL].length()>0) + { + int x = atoi(configArguments[CFG_FLOWCONTROL].c_str()); + if((x>=0)&&(x0) + doEcho = atoi(configArguments[CFG_ECHO].c_str()); + if(configArguments[CFG_RESP_SUPP].length()>0) + suppressResponses = atoi(configArguments[CFG_RESP_SUPP].c_str()); + if(configArguments[CFG_RESP_NUM].length()>0) + numericResponses = atoi(configArguments[CFG_RESP_NUM].c_str()); + if(configArguments[CFG_RESP_LONG].length()>0) + longResponses = atoi(configArguments[CFG_RESP_LONG].c_str()); + if(configArguments[CFG_PETSCIIMODE].length()>0) + serial.setPetsciiMode(atoi(configArguments[CFG_PETSCIIMODE].c_str())); + pinModeDecoder(configArguments[CFG_DCDMODE],&dcdActive,&dcdInactive,DEFAULT_DCD_HIGH,DEFAULT_DCD_LOW); + pinModeDecoder(configArguments[CFG_CTSMODE],&ctsActive,&ctsInactive,DEFAULT_CTS_HIGH,DEFAULT_CTS_LOW); + pinModeDecoder(configArguments[CFG_RTSMODE],&rtsActive,&rtsInactive,DEFAULT_RTS_HIGH,DEFAULT_RTS_LOW); + pinModeDecoder(configArguments[CFG_RIMODE],&riActive,&riInactive,DEFAULT_RI_HIGH,DEFAULT_RI_LOW); + pinModeDecoder(configArguments[CFG_DTRMODE],&dtrActive,&dtrInactive,DEFAULT_DTR_HIGH,DEFAULT_DTR_LOW); + pinModeDecoder(configArguments[CFG_DSRMODE],&dsrActive,&dsrInactive,DEFAULT_DSR_HIGH,DEFAULT_DSR_LOW); + if(configArguments[CFG_DCDPIN].length()>0) + { + pinDCD = atoi(configArguments[CFG_DCDPIN].c_str()); + if(pinSupport[pinDCD]) + pinMode(pinDCD,OUTPUT); + dcdStatus=dcdInactive; + } + s_pinWrite(pinDCD,dcdStatus); + if(configArguments[CFG_CTSPIN].length()>0) + { + pinCTS = atoi(configArguments[CFG_CTSPIN].c_str()); + if(pinSupport[pinCTS]) + pinMode(pinCTS,INPUT); + } + if(configArguments[CFG_RTSPIN].length()>0) + { + pinRTS = atoi(configArguments[CFG_RTSPIN].c_str()); + if(pinSupport[pinRTS]) + pinMode(pinRTS,OUTPUT); + } + s_pinWrite(pinRTS,rtsActive); +#ifdef ZIMODEM_ESP32 + serial.setFlowControlType(serial.getFlowControlType()); +#endif + if(configArguments[CFG_RIPIN].length()>0) + { + pinRI = atoi(configArguments[CFG_RIPIN].c_str()); + if(pinSupport[pinRI]) + pinMode(pinRI,OUTPUT); + } + s_pinWrite(pinRI,riInactive); + if(configArguments[CFG_DTRPIN].length()>0) + { + pinDTR = atoi(configArguments[CFG_DTRPIN].c_str()); + if(pinSupport[pinDTR]) + pinMode(pinDTR,INPUT); + } + if(configArguments[CFG_DSRPIN].length()>0) + { + pinDSR = atoi(configArguments[CFG_DSRPIN].c_str()); + if(pinSupport[pinDSR]) + pinMode(pinDSR,OUTPUT); + } + s_pinWrite(pinDSR,dsrActive); + if(configArguments[CFG_S0_RINGS].length()>0) + ringCounter = atoi(configArguments[CFG_S0_RINGS].c_str()); + if(configArguments[CFG_S41_STREAM].length()>0) + autoStreamMode = atoi(configArguments[CFG_S41_STREAM].c_str()); + if(configArguments[CFG_S62_TELNET].length()>0) + telnetSupport = atoi(configArguments[CFG_S62_TELNET].c_str()); + if(configArguments[CFG_S60_LISTEN].length()>0) + { + preserveListeners = atoi(configArguments[CFG_S60_LISTEN].c_str()); + if(preserveListeners) + WiFiServerNode::RestoreWiFiServers(); + } + if(configArguments[CFG_TIMEZONE].length()>0) + { + int tzCode = atoi(configArguments[CFG_TIMEZONE].c_str()); + if(tzCode > 500) + zclock.setDisabled(true); + else + { + zclock.setDisabled(false); + zclock.setTimeZoneCode(tzCode); + } + } + if((!zclock.isDisabled())&&(configArguments[CFG_TIMEFMT].length()>0)) + zclock.setFormat(configArguments[CFG_TIMEFMT]); + if((!zclock.isDisabled())&&(configArguments[CFG_TIMEURL].length()>0)) + zclock.setNtpServerHost(configArguments[CFG_TIMEURL]); + if((!zclock.isDisabled()) && (WiFi.status() == WL_CONNECTED)) + zclock.forceUpdate(); + //if(configArguments[CFG_PRINTDELAYMS].length()>0) // since you can't change it, what's the point? + // printMode.setTimeoutDelayMs(atoi(configArguments[CFG_PRINTDELAYMS].c_str())); + printMode.setLastPrinterSpec(configArguments[CFG_PRINTSPEC].c_str()); + if(configArguments[CFG_TERMTYPE].length()>0) + termType = configArguments[CFG_TERMTYPE]; + if(configArguments[CFG_BUSYMSG].length()>0) + busyMsg = configArguments[CFG_BUSYMSG]; + updateAutoAnswer(); +} + +void ZCommand::parseConfigOptions(String configArguments[]) +{ + delay(500); + bool v2=SPIFFS.exists(CONFIG_FILE); + File f; + if(v2) + f = SPIFFS.open(CONFIG_FILE, "r"); + else + f = SPIFFS.open(CONFIG_FILE_OLD, "r"); + String str=f.readString(); + f.close(); + if((str!=null)&&(str.length()>0)) + { + debugPrintf("Read Config: %s\n",str.c_str()); + int argn=0; + for(int i=0;i0) + baudRate=atoi(argv[CFG_BAUDRATE].c_str()); + if(baudRate <= 0) + baudRate=DEFAULT_BAUD_RATE; + if(argv[CFG_UART].length()>0) + serialConfig = (SerialConfig)atoi(argv[CFG_UART].c_str()); + if(serialConfig <= 0) + serialConfig = DEFAULT_SERIAL_CONFIG; + changeBaudRate(baudRate); + changeSerialConfig(serialConfig); + wifiSSI=argv[CFG_WIFISSI]; + wifiPW=argv[CFG_WIFIPW]; + hostname = argv[CFG_HOSTNAME]; + setNewStaticIPs( + ConnSettings::parseIP(argv[CFG_STATIC_IP].c_str()), + ConnSettings::parseIP(argv[CFG_STATIC_DNS].c_str()), + ConnSettings::parseIP(argv[CFG_STATIC_GW].c_str()), + ConnSettings::parseIP(argv[CFG_STATIC_SN].c_str())); + if(wifiSSI.length()>0) + { + debugPrintf("Connecting to %s\n",wifiSSI.c_str()); + connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN); + nextReconnectDelay = DEFAULT_RECONNECT_DELAY; + debugPrintf("Done attempting to connect to %s\n",wifiSSI.c_str()); + } + debugPrintf("Reset start.\n"); + doResetCommand(); + debugPrintf("Reset complete. Init start\n"); + showInitMessage(); + debugPrintf("Init complete.\n"); +} + +ZResult ZCommand::doInfoCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + if(vval == 0) + { + showInitMessage(); + } + else + switch(vval) + { + case 1: + case 5: + { + bool showAll = (vval==5); + serial.prints("AT"); + serial.prints("B"); + serial.printi(baudRate); + serial.prints(doEcho?"E1":"E0"); + if(suppressResponses) + { + serial.prints("Q1"); + if(showAll) + { + serial.prints(numericResponses?"V0":"V1"); + serial.prints(longResponses?"X1":"X0"); + } + } + else + { + serial.prints("Q0"); + serial.prints(numericResponses?"V0":"V1"); + serial.prints(longResponses?"X1":"X0"); + } + switch(serial.getFlowControlType()) + { + case FCT_RTSCTS: + serial.prints("F0"); + break; + case FCT_NORMAL: + serial.prints("F1"); + break; + case FCT_AUTOOFF: + serial.prints("F2"); + break; + case FCT_MANUAL: + serial.prints("F3"); + break; + case FCT_DISABLED: + serial.prints("F4"); + break; + } + if(EOLN==CR) + serial.prints("R0"); + else + if(EOLN==CRLF) + serial.prints("R1"); + else + if(EOLN==LFCR) + serial.prints("R2"); + else + if(EOLN==LF) + serial.prints("R3"); + + if((delimiters != NULL)&&(delimiters[0]!=0)) + { + for(int i=0;iport); + serv=serv->next; + } + if(tempBaud > 0) + { + serial.prints("S43="); + serial.printi(tempBaud); + } + else + if(showAll) + serial.prints("S43=0"); + if((serialDelayMs > 0)||(showAll)) + { + serial.prints("S44="); + serial.printi(serialDelayMs); + } + if((binType > 0)||(showAll)) + { + serial.prints("S45="); + serial.printi(binType); + } + if((dcdActive != DEFAULT_DCD_HIGH)||(dcdInactive == DEFAULT_DCD_HIGH)||(showAll)) + serial.printf("S46=%d",pinModeCoder(dcdActive,dcdInactive,DEFAULT_DCD_HIGH)); + if((pinDCD != DEFAULT_PIN_DCD)||(showAll)) + serial.printf("S47=%d",pinDCD); + if((ctsActive != DEFAULT_CTS_HIGH)||(ctsInactive == DEFAULT_CTS_HIGH)||(showAll)) + serial.printf("S48=%d",pinModeCoder(ctsActive,ctsInactive,DEFAULT_CTS_HIGH)); + if((pinCTS != getDefaultCtsPin())||(showAll)) + serial.printf("S49=%d",pinCTS); + if((rtsActive != DEFAULT_RTS_HIGH)||(rtsInactive == DEFAULT_RTS_HIGH)||(showAll)) + serial.printf("S50=%d",pinModeCoder(rtsActive,rtsInactive,DEFAULT_RTS_HIGH)); + if((pinRTS != DEFAULT_PIN_RTS)||(showAll)) + serial.printf("S51=%d",pinRTS); + if((riActive != DEFAULT_RI_HIGH)||(riInactive == DEFAULT_RI_HIGH)||(showAll)) + serial.printf("S52=%d",pinModeCoder(riActive,riInactive,DEFAULT_RI_HIGH)); + if((pinRI != DEFAULT_PIN_RI)||(showAll)) + serial.printf("S53=%d",pinRI); + if((dtrActive != DEFAULT_DTR_HIGH)||(dtrInactive == DEFAULT_DTR_HIGH)||(showAll)) + serial.printf("S54=%d",pinModeCoder(dtrActive,dtrInactive,DEFAULT_DTR_HIGH)); + if((pinDTR != DEFAULT_PIN_DTR)||(showAll)) + serial.printf("S55=%d",pinDTR); + if((dsrActive != DEFAULT_DSR_HIGH)||(dsrInactive == DEFAULT_DSR_HIGH)||(showAll)) + serial.printf("S56=%d",pinModeCoder(dsrActive,dsrInactive,DEFAULT_DSR_HIGH)); + if((pinDSR != DEFAULT_PIN_DSR)||(showAll)) + serial.printf("S57=%d",pinDSR); + if(preserveListeners ||(showAll)) + serial.prints(preserveListeners ? "S60=1" : "S60=0"); + if(!telnetSupport ||(showAll)) + serial.prints(telnetSupport ? "S62=1" : "S62=0"); + if((serial.isPetsciiMode())||(showAll)) + serial.prints(serial.isPetsciiMode() ? "&P1" : "&P0"); + if((logFileOpen) || showAll) + serial.prints((logFileOpen && !logFileDebug) ? "&O1" : ((logFileOpen && logFileDebug) ? "&O88" : "&O0")); + serial.prints(EOLN); + break; + } + case 2: + { + serial.prints(WiFi.localIP().toString().c_str()); + serial.prints(EOLN); + break; + } + case 3: + { + serial.prints(wifiSSI.c_str()); + serial.prints(EOLN); + break; + } + case 4: + { + serial.prints(ZIMODEM_VERSION); + serial.prints(EOLN); + break; + } + case 6: + { + serial.prints(WiFi.macAddress()); + serial.prints(EOLN); + break; + } + case 7: + { + serial.prints(zclock.getCurrentTimeFormatted()); + serial.prints(EOLN); + break; + } + case 8: + { + serial.prints(compile_date); + serial.prints(EOLN); + break; + } + case 9: + { + serial.prints(wifiSSI.c_str()); + serial.prints(EOLN); + if(staticIP != null) + { + String str; + ConnSettings::IPtoStr(staticIP,str); + serial.prints(str.c_str()); + serial.prints(EOLN); + ConnSettings::IPtoStr(staticDNS,str); + serial.prints(str.c_str()); + serial.prints(EOLN); + ConnSettings::IPtoStr(staticGW,str); + serial.prints(str.c_str()); + serial.prints(EOLN); + ConnSettings::IPtoStr(staticSN,str); + serial.prints(str.c_str()); + serial.prints(EOLN); + } + break; + } + case 10: + serial.printf("%s%s",printMode.getLastPrinterSpec(),EOLN.c_str()); + break; + case 11: + { + serial.printf("%d%s",ESP.getFreeHeap(),EOLN.c_str()); + break; + } + default: + return ZERROR; + } + return ZOK; +} + +ZResult ZCommand::doBaudCommand(int vval, uint8_t *vbuf, int vlen) +{ + int baudChk = baudRate; + uint32_t configChk = serialConfig; + if(vval<=0) + { + char *commaLoc=strchr((char *)vbuf,','); + if(commaLoc == NULL) + return ZERROR; + char *conStr=commaLoc+1; + if(strlen(conStr)!=3) + return ZERROR; + *commaLoc=0; + configChk = 0; + baudChk=atoi((char *)vbuf); + switch(conStr[0]) + { + case '5': + configChk = UART_NB_BIT_5; + break; + case '6': + configChk = UART_NB_BIT_6; + break; + case '7': + configChk = UART_NB_BIT_7; + break; + case '8': + configChk = UART_NB_BIT_8; + break; + default: + return ZERROR; + } + switch(conStr[1]) + { + case 'o': case 'O': + configChk = configChk | UART_PARITY_ODD; + break; + case 'e': case 'E': + configChk = configChk | UART_PARITY_EVEN; + break; + case 'm': case 'M': + configChk = configChk | UART_PARITY_MASK; + break; + case 'n': case 'N': + configChk = configChk | UART_PARITY_NONE; + break; + default: + return ZERROR; + } + switch(conStr[2]) + { + case '1': + configChk = configChk | UART_NB_STOP_BIT_1; + break; + case '2': + configChk = configChk | UART_NB_STOP_BIT_2; + break; + default: + return ZERROR; + } + } + else + baudChk=vval; + hwSerialFlush(); + if(baudChk != baudRate) + { + if((baudChk<128)||(baudChk>115200)) + return ZERROR; + baudRate = baudChk; + changeBaudRate(baudRate); + } + if(serialConfig != configChk) + { + serialConfig = (SerialConfig)configChk; + changeSerialConfig(serialConfig); + } + return ZOK; +} + +ZResult ZCommand::doConnectCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers) +{ + if((WiFi.status() != WL_CONNECTED) + &&((vlen==0)||(isNumber && (vval==0))) + &&(conns==null)) + { + if(wifiSSI.length()==0) + return ZERROR; + debugPrintf("Connecting to %s\n",wifiSSI.c_str()); + bool doconn = connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN); + debugPrintf("Done attempting connect to %s\n",wifiSSI.c_str()); + return doconn ? ZOK : ZERROR; + } + if(vlen == 0) + { + logPrintln("ConnCheck: CURRENT"); + if(strlen(dmodifiers)>0) + return ZERROR; + if(current == null) + return ZERROR; + else + { + if(current->isConnected()) + { + current->answer(); + serial.prints("CONNECTED "); + serial.printf("%d %s:%d",current->id,current->host,current->port); + serial.prints(EOLN); + } + else + if(current->isAnswered()) + { + serial.prints("NO CARRIER "); + serial.printf("%d %s:%d",current->id,current->host,current->port); + serial.prints(EOLN); + serial.flush(); + } + return ZIGNORE; + } + } + else + if((vval >= 0)&&(isNumber)) + { + if(vval == 0) + logPrintln("ConnList0:\r\n"); + else + logPrintfln("ConnSwitchTo: %d",vval); + if(strlen(dmodifiers)>0) // would be nice to allow petscii/telnet changes here, but need more flags + return ZERROR; + WiFiClientNode *c=conns; + if(vval > 0) + { + while((c!=null)&&(c->id != vval)) + c=c->next; + if((c!=null)&&(c->id == vval)) + { + current = c; + connectionArgs(c); + } + else + return ZERROR; + } + else + { + c=conns; + while(c!=null) + { + if(c->isConnected()) + { + c->answer(); + serial.prints("CONNECTED "); + serial.printf("%d %s:%d",c->id,c->host,c->port); + serial.prints(EOLN); + } + else + if(c->isAnswered()) + { + serial.prints("NO CARRIER "); + serial.printf("%d %s:%d",c->id,c->host,c->port); + serial.prints(EOLN); + serial.flush(); + } + c=c->next; + } + WiFiServerNode *s=servs; + while(s!=null) + { + serial.prints("LISTENING "); + serial.printf("%d *:%d",s->id,s->port); + serial.prints(EOLN); + s=s->next; + } + } + } + else + { + logPrintln("Connnect-Start:"); + char *host = 0; + int port = -1; + char *username = 0; + char *password = 0; + parseHostInfo(vbuf, &host, &port, &username, &password); + int flagsBitmap=0; + { + ConnSettings flags(dmodifiers); + flagsBitmap = flags.getBitmap(serial.getFlowControlType()); + } + if(port < 0) + port = ((username != 0) && ((flagsBitmap&FLAG_SECURE)==FLAG_SECURE))?22:23; + if(username != null) + logPrintfln("Connnecting: %s@%s:%d %d",username,host,port,flagsBitmap); + else + logPrintfln("Connnecting: %s %d %d",host,port,flagsBitmap); + WiFiClientNode *c = new WiFiClientNode(host,port,username,password,flagsBitmap); + if(!c->isConnected()) + { + logPrintln("Connnect: FAIL"); + delete c; + return ZNOANSWER; + } + else + { + logPrintfln("Connnect: SUCCESS: %d",c->id); + current=c; + connectionArgs(c); + return ZCONNECT; + } + } + return ZOK; +} + +void ZCommand::headerOut(const int channel, const int num, const int sz, const int crc8) +{ + switch(binType) + { + case BTYPE_NORMAL: + sprintf(hbuf,"[ %d %d %d ]%s",channel,sz,crc8,EOLN.c_str()); + break; + case BTYPE_NORMAL_PLUS: + sprintf(hbuf,"[ %d %d %d %d ]%s",channel,num,sz,crc8,EOLN.c_str()); + break; + case BTYPE_HEX: + sprintf(hbuf,"[ %s %s %s ]%s", + String(TOHEX(channel)).c_str(), + String(TOHEX(sz)).c_str(), + String(TOHEX(crc8)).c_str(),EOLN.c_str()); + break; + case BTYPE_HEX_PLUS: + sprintf(hbuf,"[ %s %s %s %s ]%s", + String(TOHEX(channel)).c_str(), + String(TOHEX(num)).c_str(), + String(TOHEX(sz)).c_str(), + String(TOHEX(crc8)).c_str(),EOLN.c_str()); + break; + case BTYPE_DEC: + sprintf(hbuf,"[%s%d%s%d%s%d%s]%s",EOLN.c_str(),channel,EOLN.c_str(),sz,EOLN.c_str(),crc8,EOLN.c_str(),EOLN.c_str()); + break; + case BTYPE_DEC_PLUS: + sprintf(hbuf,"[%s%d%s%d%s%d%s%d%s]%s",EOLN.c_str(), + channel,EOLN.c_str(), + num,EOLN.c_str(), + sz,EOLN.c_str(), + crc8,EOLN.c_str(), + EOLN.c_str()); + break; + case BTYPE_NORMAL_NOCHK: + sprintf(hbuf,"[ %d %d ]%s",channel,sz,EOLN.c_str()); + break; + } + serial.prints(hbuf); +} + +ZResult ZCommand::doWebStream(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *filename, bool cache) +{ + char *hostIp; + char *req; + int port; + bool doSSL; + if(!parseWebUrl(vbuf,&hostIp,&req,&port,&doSSL)) + return ZERROR; + + if(cache) + { + if(!SPIFFS.exists(filename)) + { + if(!doWebGet(hostIp, port, &SPIFFS, filename, req, doSSL)) + return ZERROR; + } + } + else + if((binType == BTYPE_NORMAL_NOCHK) + &&(machineQue.length()==0)) + { + uint32_t respLength=0; + WiFiClient *c = doWebGetStream(hostIp, port, req, doSSL, &respLength); + if(c==null) + { + serial.prints(EOLN); + return ZERROR; + } + headerOut(0,1,respLength,0); + serial.flush(); // stupid important because otherwise apps that go xoff miss the header info + ZResult res = doWebDump(c,respLength,false); + c->stop(); + delete c; + serial.prints(EOLN); + return res; + } + else + if(!doWebGet(hostIp, port, &SPIFFS, filename, req, doSSL)) + return ZERROR; + return doWebDump(filename, cache); +} + +ZResult ZCommand::doWebDump(Stream *in, int len, const bool cacheFlag) +{ + bool flowControl=!cacheFlag; + BinType streamType = cacheFlag?BTYPE_NORMAL:binType; + uint8_t *buf = (uint8_t *)malloc(1); + uint16_t bufLen = 1; + int bct=0; + unsigned long now = millis(); + while((len>0) + && ((millis()-now)<10000)) + { + if(((!flowControl) || serial.isSerialOut()) + &&(in->available()>0)) + { + now=millis(); + len--; + int c=in->read(); + if(c<0) + break; + buf[0] = (uint8_t)c; + bufLen = 1; + buf = doMaskOuts(buf,&bufLen,maskOuts); + buf = doStateMachine(buf,&bufLen,&machineState,&machineQue,stateMachine); + for(int i=0;i=39) + { + serial.prints(EOLN); + bct=0; + } + break; + } + case BTYPE_DEC: + case BTYPE_DEC_PLUS: + serial.printf("%d%s",c,EOLN.c_str()); + break; + } + } + } + if(serial.isSerialOut()) + { + serialOutDeque(); + yield(); + } + if(serial.drainForXonXoff()==3) + { + serial.setXON(true); + free(buf); + machineState = stateMachine; + return ZOK; + } + while(serial.availableForWrite()<5) + { + if(serial.isSerialOut()) + { + serialOutDeque(); + yield(); + } + if(serial.drainForXonXoff()==3) + { + serial.setXON(true); + free(buf); + machineState = stateMachine; + return ZOK; + } + delay(1); + } + yield(); + } + free(buf); + machineState = stateMachine; + if(bct > 0) + serial.prints(EOLN); + return ZOK; +} + +ZResult ZCommand::doWebDump(const char *filename, const bool cache) +{ + machineState = stateMachine; + int chk8=0; + uint16_t bufLen = 1; + int len = 0; + + { + File f = SPIFFS.open(filename, "r"); + int flen = f.size(); + if((binType != BTYPE_NORMAL_NOCHK) + &&(machineQue.length()==0)) + { + uint8_t *buf = (uint8_t *)malloc(1); + delay(100); + char *oldMachineState = machineState; + String oldMachineQue = machineQue; + for(int i=0;i255) + chk8-=256; + } + } + } + machineState = oldMachineState; + machineQue = oldMachineQue; + free(buf); + } + else + len=flen; + f.close(); + } + File f = SPIFFS.open(filename, "r"); + if(!cache) + { + headerOut(0,1,len,chk8); + serial.flush(); // stupid important because otherwise apps that go xoff miss the header info + } + len = f.size(); + ZResult res = doWebDump(&f, len, cache); + f.close(); + return res; +} + +ZResult ZCommand::doUpdateFirmware(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + serial.prints("Local firmware version "); + serial.prints(ZIMODEM_VERSION); + serial.prints("."); + serial.prints(EOLN); + + uint8_t buf[255]; + int bufSize = 254; + char firmwareName[100]; +#ifdef USE_DEVUPDATER + char *updaterHost = "192.168.1.10"; + int updaterPort = 8080; +#else + char *updaterHost = "www.zimmers.net"; + int updaterPort = 80; +#endif +#ifdef ZIMODEM_ESP32 + char *updaterPrefix = "/otherprojs/guru2"; +#else + char *updaterPrefix = "/otherprojs/c64net"; +#endif + sprintf(firmwareName,"%s-latest-version.txt",updaterPrefix); + if((!doWebGetBytes(updaterHost, updaterPort, firmwareName, false, buf, &bufSize))||(bufSize<=0)) + return ZERROR; + + if((!isNumber)&&(vlen>2)) + { + if(vbuf[0]=='=') + { + for(int i=1;i0) + &&((buf[bufSize-1]==10)||(buf[bufSize-1]==13))) + { + bufSize--; + buf[bufSize] = 0; + } + + if((strlen(ZIMODEM_VERSION)==bufSize) && memcmp(buf,ZIMODEM_VERSION,strlen(ZIMODEM_VERSION))==0) + { + serial.prints("Your modem is up-to-date."); + serial.prints(EOLN); + if(vval == 6502) + return ZOK; + } + else + { + serial.prints("Latest available version is "); + buf[bufSize]=0; + serial.prints((char *)buf); + serial.prints("."); + serial.prints(EOLN); + } + if(vval != 6502) + return ZOK; + + serial.printf("Updating to %s, wait for modem restart...",buf); + serial.flush(); + sprintf(firmwareName,"%s-firmware-%s.bin", updaterPrefix, buf); + uint32_t respLength=0; + WiFiClient *c = doWebGetStream(updaterHost, updaterPort, firmwareName, false, &respLength); + if(c==null) + { + serial.prints(EOLN); + return ZERROR; + } + + if(!Update.begin((respLength == 0) ? 4096 : respLength)) + { + c->stop(); + delete c; + return ZERROR; + } + + serial.prints("."); + serial.flush(); + int writeBytes = Update.writeStream(*c); + if(writeBytes != respLength) + { + c->stop(); + delete c; + serial.prints(EOLN); + return ZERROR; + } + serial.prints("."); + serial.flush(); + if(!Update.end()) + { + c->stop(); + delete c; + serial.prints(EOLN); + return ZERROR; + } + c->stop(); + delete c; + serial.prints("Done"); + serial.prints(EOLN); + serial.prints("Modem will now restart, but you should power-cycle or reset your modem."); + ESP.restart(); + return ZOK; +} + +ZResult ZCommand::doWiFiCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers) +{ + bool doPETSCII = (strchr(dmodifiers,'p')!=null) || (strchr(dmodifiers,'P')!=null); + if((vlen==0)||(vval>0)) + { + int n = WiFi.scanNetworks(); + if((vval > 0)&&(vval < n)) + n=vval; + for (int i = 0; i < n; ++i) + { + if((doPETSCII)&&(!serial.isPetsciiMode())) + { + String ssidstr=WiFi.SSID(i); + char *c = (char *)ssidstr.c_str(); + for(;*c!=0;c++) + serial.printc(ascToPetcii(*c)); + } + else + serial.prints(WiFi.SSID(i).c_str()); + serial.prints(" ("); + serial.printi(WiFi.RSSI(i)); + serial.prints(")"); + serial.prints((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*"); + serial.prints(EOLN.c_str()); + serial.flush(); + delay(10); + } + } + else + { + char *x=strstr((char *)vbuf,","); + char *ssi=(char *)vbuf; + char *pw=ssi + strlen(ssi); + IPAddress *ip[4]; + for(int i=0;i<4;i++) + ip[i]=null; + if(x > 0) + { + *x=0; + pw=x+1; + x=strstr(pw,","); + if(x > 0) + { + int numCommasFound=0; + int numDotsFound=0; + char *comPos[4]; + for(char *e=pw+strlen(pw)-1;e>pw;e--) + { + if(*e==',') + { + if(numDotsFound!=3) + break; + numDotsFound=0; + if(numCommasFound<4) + { + numCommasFound++; + comPos[4-numCommasFound]=e; + } + if(numCommasFound==4) + break; + } + else + if(*e=='.') + numDotsFound++; + else + if(strchr("0123456789 ",*e)==null) + break; + } + if(numCommasFound==4) + { + for(int i=0;i<4;i++) + *(comPos[i])=0; + for(int i=0;i<4;i++) + { + ip[i]=ConnSettings::parseIP(comPos[i]+1); + if(ip[i]==null) + { + while(--i>=0) + { + free(ip[i]); + ip[i]=null; + } + break; + } + } + } + } + } + bool connSuccess=false; + if((doPETSCII)&&(!serial.isPetsciiMode())) + { + char *ssiP =(char *)malloc(strlen(ssi)+1); + char *pwP = (char *)malloc(strlen(pw)+1); + strcpy(ssiP,ssi); + strcpy(pwP,pw); + for(char *c=ssiP;*c!=0;c++) + *c = ascToPetcii(*c); + for(char *c=pwP;*c!=0;c++) + *c = ascToPetcii(*c); + connSuccess = connectWifi(ssiP,pwP,ip[0],ip[1],ip[2],ip[3]); + free(ssiP); + free(pwP); + } + else + connSuccess = connectWifi(ssi,pw,ip[0],ip[1],ip[2],ip[3]); + + if(!connSuccess) + { + for(int ii=0;ii<4;ii++) + { + if(ip[ii]!=null) + free(ip[ii]); + } + return ZERROR; + } + else + { + wifiSSI=ssi; + wifiPW=pw; + setNewStaticIPs(ip[0],ip[1],ip[2],ip[3]); + } + } + return ZOK; +} + +ZResult ZCommand::doTransmitCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers, int *crc8) +{ + bool doPETSCII = (strchr(dmodifiers,'p')!=null) || (strchr(dmodifiers,'P')!=null); + int crcChk = *crc8; + *crc8=-1; + int rcvdCrc8=-1; + if((current==null)||(!current->isConnected())) + return ZERROR; + else + if(isNumber && (vval>0)) + { + uint8_t buf[vval]; + int recvd = HWSerial.readBytes(buf,vval); + if(logFileOpen) + { + for(int i=0;iisPETSCII() || doPETSCII) + { + for(int i=0;iwrite(buf,recvd); + if(logFileOpen) + { + for(int i=0;iisPETSCII() || doPETSCII) + { + for(int i=0;iwrite(buf,vlen+2); + if(logFileOpen) + { + for(int i=0;iisConnected())) + return ZERROR; + else + { + streamMode.switchTo(current); + } + } + else + if((vval >= 0)&&(isNumber)) + { + PhoneBookEntry *phb = phonebook; + while(phb != null) + { + if(phb->number == vval) + { + int addrLen=strlen(phb->address); + uint8_t *vbuf = new uint8_t[addrLen+1]; + strcpy((char *)vbuf,phb->address); + ZResult res = doDialStreamCommand(0,vbuf,addrLen,false,phb->modifiers); + free(vbuf); + return res; + } + phb = phb->next; + } + /* + if(vval == 5517545) // slip no login + { + slipMode.switchTo(); + } + */ + + WiFiClientNode *c=conns; + while((c!=null)&&(c->id != vval)) + c=c->next; + if((c!=null)&&(c->id == vval)&&(c->isConnected())) + { + current=c; + connectionArgs(c); + streamMode.switchTo(c); + return ZCONNECT; + } + else + return ZERROR; + } + else + { + ConnSettings flags(dmodifiers); + if(!telnetSupport) + flags.setFlag(FLAG_TELNET, false); + int flagsBitmap = flags.getBitmap(serial.getFlowControlType()); + char *host = 0; + int port = -1; + char *username = 0; + char *password = 0; + parseHostInfo(vbuf, &host, &port, &username, &password); + if(port < 0) + port = ((username != 0) && ((flagsBitmap&FLAG_SECURE)==FLAG_SECURE))?22:23; + WiFiClientNode *c = new WiFiClientNode(host,port,username,password,flagsBitmap | FLAG_DISCONNECT_ON_EXIT); + if(!c->isConnected()) + { + delete c; + return ZNOANSWER; + } + else + { + current=c; + connectionArgs(c); + streamMode.switchTo(c); + return ZCONNECT; + } + } + return ZOK; +} + +ZResult ZCommand::doPhonebookCommand(unsigned long vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers) +{ + if((vlen==0)||(isNumber)||((vlen==1)&&(*vbuf='?'))) + { + PhoneBookEntry *phb=phonebook; + char nbuf[30]; + while(phb != null) + { + if((!isNumber) + ||(vval==0) + ||(vval == phb->number)) + { + if((strlen(dmodifiers)==0) + || (modifierCompare(dmodifiers,phb->modifiers)==0)) + { + sprintf(nbuf,"%lu",phb->number); + serial.prints(nbuf); + for(int i=0;i<10-strlen(nbuf);i++) + serial.prints(" "); + serial.prints(" "); + serial.prints(phb->modifiers); + for(int i=1;i<5-strlen(phb->modifiers);i++) + serial.prints(" "); + serial.prints(" "); + serial.prints(phb->address); + if(!isNumber) + { + serial.prints(" ("); + serial.prints(phb->notes); + serial.prints(")"); + } + serial.prints(EOLN.c_str()); + serial.flush(); + delay(10); + } + } + phb=phb->next; + } + return ZOK; + } + char *eq=strchr((char *)vbuf,'='); + if(eq == NULL) + return ZERROR; + for(char *cptr=(char *)vbuf;cptr!=eq;cptr++) + { + if(strchr("0123456789",*cptr) < 0) + return ZERROR; + } + char *rest=eq+1; + *eq=0; + if(strlen((char *)vbuf)>9) + return ZERROR; + + unsigned long number = atol((char *)vbuf); + PhoneBookEntry *found=PhoneBookEntry::findPhonebookEntry(number); + if((strcmp("DELETE",rest)==0) + ||(strcmp("delete",rest)==0)) + { + if(found==null) + return ZERROR; + delete found; + PhoneBookEntry::savePhonebook(); + return ZOK; + } + char *colon = strchr(rest,':'); + if(colon == NULL) + return ZERROR; + char *comma = strchr(colon,','); + char *notes = ""; + if(comma != NULL) + { + *comma=0; + notes = comma+1; + } + if(!PhoneBookEntry::checkPhonebookEntry(colon)) + return ZERROR; + if(found != null) + delete found; + PhoneBookEntry *newEntry = new PhoneBookEntry(number,rest,dmodifiers,notes); + PhoneBookEntry::savePhonebook(); + return ZOK; +} + +ZResult ZCommand::doAnswerCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers) +{ + if(vval <= 0) + { + WiFiClientNode *c=conns; + while(c!=null) + { + if((c->isConnected()) + &&(c->id == lastServerClientId)) + { + current=c; + checkOpenConnections(); + if((!c->isAnswered()) || (ringCounter==0)) + { + if(autoStreamMode) + sendConnectionNotice(baudRate); + else + sendConnectionNotice(c->id); + c->answer(); + ringCounter = 0; + streamMode.switchTo(c); + checkOpenConnections(); + return ZIGNORE; + } + else + { + streamMode.switchTo(c); + checkOpenConnections(); + return ZOK; + } + break; + } + c=c->next; + } + return ZOK; // not really doing anything important... + } + else + { + while(WiFi.status() != WL_CONNECTED) + return ZERROR; + ConnSettings flags(dmodifiers); + int flagsBitmap = flags.getBitmap(serial.getFlowControlType()); + WiFiServerNode *s=servs; + while(s != null) + { + if(s->port == vval) + return ZOK; + s=s->next; + } + WiFiServerNode *newServer = new WiFiServerNode(vval, flagsBitmap); + setCharArray(&(newServer->delimiters),tempDelimiters); + setCharArray(&(newServer->maskOuts),tempMaskOuts); + setCharArray(&(newServer->stateMachine),tempStateMachine); + freeCharArray(&tempDelimiters); + freeCharArray(&tempMaskOuts); + freeCharArray(&tempStateMachine); + updateAutoAnswer(); + return ZOK; + } +} + +ZResult ZCommand::doHangupCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + if(vlen == 0) + { + while(conns != null) + { + WiFiClientNode *c=conns; + delete c; + } + lastServerClientId=0; + current = null; + nextConn = null; + return ZOK; + } + else + if(isNumber && (vval == 0)) + { + if(current != 0) + { + if(lastServerClientId==current->id) + lastServerClientId=0; + delete current; + current = conns; + nextConn = conns; + return ZOK; + } + return ZERROR; + } + else + if(vval > 0) + { + WiFiClientNode *c=conns; + while(c != 0) + { + if(vval == c->id) + { + if(current == c) + current = conns; + if(nextConn == c) + nextConn = conns; + if(lastServerClientId==c->id) + lastServerClientId=0; + delete c; + return ZOK; + } + c=c->next; + } + WiFiServerNode *s=servs; + while(s!=null) + { + if(vval == s->id) + { + delete s; + updateAutoAnswer(); + return ZOK; + } + s=s->next; + } + return ZERROR; + } + return ZERROR; +} + +void ZCommand::updateAutoAnswer() +{ +#ifdef SUPPORT_LED_PINS + bool setPin = (ringCounter>0) && (autoStreamMode) && (servs != NULL); + s_pinWrite(DEFAULT_PIN_AA,setPin?DEFAULT_AA_ACTIVE:DEFAULT_AA_INACTIVE); +#endif + +} + +ZResult ZCommand::doLastPacket(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + if(!isNumber) + return ZERROR; + WiFiClientNode *cnode=null; + uint8_t which = 1; + if(vval == 0) + vval = lastPacketId; + else + { + uint8_t *c = vbuf; + while(*c++ == '0') + which++; + } + if(vval <= 0) + cnode = current; + else + { + WiFiClientNode *c=conns; + while(c != null) + { + if(vval == c->id) + { + cnode=c; + break; + } + c=c->next; + } + } + if(cnode == null) + return ZERROR; + reSendLastPacket(cnode,which); + return ZIGNORE; +} + +ZResult ZCommand::doEOLNCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + if(isNumber) + { + if((vval>=0)&&(vval < 4)) + { + switch(vval) + { + case 0: + EOLN = CR; + break; + case 1: + EOLN = CRLF; + break; + case 2: + EOLN = LFCR; + break; + case 3: + EOLN = LF; + break; + } + return ZOK; + } + } + return ZERROR; +} + +bool ZCommand::readSerialStream() +{ + bool crReceived=false; + while((HWSerial.available()>0) + &&(!crReceived)) + { + uint8_t c=HWSerial.read(); + logSerialIn(c); + if((c==CR[0])||(c==LF[0])) + { + if(doEcho) + { + echoEOLN(c); + if(serial.isSerialOut()) + serialOutDeque(); + } + crReceived=true; + break; + } + + if(c>0) + { + if(c!=EC) + lastNonPlusTimeMs=millis(); + + if((c==19)&&(serial.getFlowControlType() == FCT_NORMAL)) + { + serial.setXON(false); + } + else + if((c==19) + &&((serial.getFlowControlType() == FCT_AUTOOFF) + ||(serial.getFlowControlType() == FCT_MANUAL))) + { + packetXOn = false; + } + else + if((c==17) + &&(serial.getFlowControlType() == FCT_NORMAL)) + { + serial.setXON(true); + } + else + if((c==17) + &&((serial.getFlowControlType() == FCT_AUTOOFF) + ||(serial.getFlowControlType() == FCT_MANUAL))) + { + packetXOn = true; + if(serial.getFlowControlType() == FCT_MANUAL) + { + sendNextPacket(); + } + } + else + { + if(doEcho) + { + serial.write(c); + if(serial.isSerialOut()) + serialOutDeque(); + } + if((c==BS)||((BS==8)&&((c==20)||(c==127)))) + { + if(eon>0) + nbuf[--eon]=0; + continue; + } + nbuf[eon++]=c; + if((eon>=MAX_COMMAND_SIZE) + ||((eon==2)&&(nbuf[1]=='/')&&lc(nbuf[0])=='a')) + { + eon--; + crReceived=true; + } + } + } + } + return crReceived; +} + +String ZCommand::getNextSerialCommand() +{ + int len=eon; + String currentCommand = (char *)nbuf; + currentCommand.trim(); + memset(nbuf,0,MAX_COMMAND_SIZE); + if(serial.isPetsciiMode()) + { + for(int i=0;i0)&&(!zclock.setTimeZone((char *)vbuf))) + return ZERROR; + if(strlen(c1)==0) + return ZOK; + char *c2=strchr(c1,','); + if(c2 == 0) + { + zclock.setFormat(c1); + return ZOK; + } + else + { + *c2=0; + c2++; + if(strlen(c1)>0) + zclock.setFormat(c1); + if(strlen(c2)>0) + zclock.setNtpServerHost(c2); + } + } + return ZOK; +} + +ZResult ZCommand::doSerialCommand() +{ + int len=eon; + String sbuf = getNextSerialCommand(); + + if((sbuf.length()==2) + &&(lc(sbuf[0])=='a') + &&(sbuf[1]=='/')) + { + sbuf = previousCommand; + len=previousCommand.length(); + } + if(logFileOpen) + logPrintfln("Command: %s",sbuf.c_str()); + + int crc8=-1; + ZResult result=ZOK; + + if((sbuf.length()==4) + &&(strcmp(sbuf.c_str(),"%!PS")==0)) + { + result = printMode.switchToPostScript("%!PS\n"); + sendOfficialResponse(result); + return result; + } + else + if((sbuf.length()==12) + &&(strcmp(sbuf.c_str(),"\x04grestoreall")==0)) + { + result = printMode.switchToPostScript("%!PS\ngrestoreall\n"); + sendOfficialResponse(result); + return result; + } + + int index=0; + while((index='0')&&(c<='9'))) && isNumber; + } + } + else + while((index='a')&&(lc(sbuf[index])<='z'))) + &&(sbuf[index]!='&') + &&(sbuf[index]!='%') + &&(sbuf[index]!=' ')) + { + char c=sbuf[index]; + isNumber = ((c=='-')||((c>='0') && (c<='9'))) && isNumber; + vlen++; + index++; + } + } + long vval=0; + uint8_t vbuf[vlen+1]; + memset(vbuf,0,vlen+1); + if(vlen>0) + { + memcpy(vbuf,sbuf.c_str()+vstart,vlen); + if((vlen > 0)&&(isNumber)) + { + String finalNum=""; + for(uint8_t *v=vbuf;v<(vbuf+vlen);v++) + if((*v>='0')&&(*v<='9')) + finalNum += (char)*v; + vval=atol(finalNum.c_str()); + } + } + + if(vlen > 0) + { + logPrintfln("Proc: %c %lu '%s'",lastCmd,vval,vbuf); + debugPrintf("Proc: %c %lu '%s'\n",lastCmd,vval,vbuf); + } + else + { + logPrintfln("Proc: %c %lu ''",lastCmd,vval); + debugPrintf("Proc: %c %lu ''\n",lastCmd,vval); + } + /* + * We have cmd and args, time to DO! + */ + switch(lastCmd) + { + case 'z': + result = doResetCommand(); + break; + case 'n': + if(isNumber && (vval == 0)) + { + doNoListenCommand(); + break; + } + case 'a': + result = doAnswerCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + break; + case 'e': + if(!isNumber) + result=ZERROR; + else + doEcho=(vval > 0); + break; + case 'f': + if((!isNumber)||(vval>=FCT_INVALID)) + result=ZERROR; + else + { + packetXOn = true; + serial.setXON(true); + serial.setFlowControlType((FlowControlType)vval); + if(serial.getFlowControlType() == FCT_MANUAL) + packetXOn = false; + } + break; + case 'x': + if(!isNumber) + result=ZERROR; + else + longResponses = (vval > 0); + break; + case 'r': + result = doEOLNCommand(vval,vbuf,vlen,isNumber); + break; + case 'b': + result = doBaudCommand(vval,vbuf,vlen); + break; + case 't': + result = doTransmitCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str(),&crc8); + break; + case 'h': + result = doHangupCommand(vval,vbuf,vlen,isNumber); + break; + case 'd': + result = doDialStreamCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + break; + case 'p': + result = doPhonebookCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + break; + case 'o': + if((vlen == 0)||(vval==0)) + { + if((current == null)||(!current->isConnected())) + result = ZERROR; + else + { + lastServerClientId = current->id; + result = doAnswerCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + } + } + else + result = isNumber ? doDialStreamCommand(vval,vbuf,vlen,isNumber,"") : ZERROR; + break; + case 'c': + result = doConnectCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + break; + case 'i': + result = doInfoCommand(vval,vbuf,vlen,isNumber); + break; + case 'l': + result = doLastPacket(vval,vbuf,vlen,isNumber); + break; + case 'm': + case 'y': + result = isNumber ? ZOK : ZERROR; + break; + case 'w': + result = doWiFiCommand(vval,vbuf,vlen,isNumber,dmodifiers.c_str()); + break; + case 'v': + if(!isNumber) + result=ZERROR; + else + numericResponses = (vval == 0); + break; + case 'q': + if(!isNumber) + result=ZERROR; + else + suppressResponses = (vval > 0); + break; + case 's': + { + if(vlen<3) + result=ZERROR; + else + { + char *eq=strchr((char *)vbuf,'='); + if((eq == null)||(eq == (char *)vbuf)||(eq>=(char *)&(vbuf[vlen-1]))) + result=ZERROR; + else + { + *eq=0; + int snum = atoi((char *)vbuf); + int sval = atoi((char *)(eq + 1)); + if((snum == 0)&&((vbuf[0]!='0')||(eq != (char *)(vbuf+1)))) + result=ZERROR; + else + if((sval == 0)&&((*(eq+1)!='0')||(*(eq+2) != 0))) + result=ZERROR; + else + switch(snum) + { + case 0: + if((sval < 0)||(sval>255)) + result=ZERROR; + else + { + ringCounter = sval; + updateAutoAnswer(); + } + break; + case 2: + if((sval < 0)||(sval>255)) + result=ZERROR; + else + { + EC=(char)sval; + ECS[0]=EC; + ECS[1]=EC; + ECS[2]=EC; + } + break; + case 3: + if((sval < 0)||(sval>127)) + result=ZERROR; + else + { + CR[0]=(char)sval; + CRLF[0]=(char)sval; + LFCR[1]=(char)sval; + } + break; + case 4: + if((sval < 0)||(sval>127)) + result=ZERROR; + else + { + LF[0]=(char)sval; + CRLF[1]=(char)sval; + LFCR[0]=(char)sval; + } + break; + case 5: + if((sval < 0)||(sval>32)) + result=ZERROR; + else + { + BS=(char)sval; + } + break; + case 40: + if(sval < 1) + result=ZERROR; + else + packetSize=sval; + break; + case 41: + { + autoStreamMode = (sval > 0); + updateAutoAnswer(); + break; + } + case 42: + crc8=sval; + break; + case 43: + if(sval > 0) + tempBaud = sval; + else + tempBaud = -1; + break; + case 44: + serialDelayMs=sval; + break; + case 45: + if((sval>=0)&&(sval= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinDCD=sval; + pinMode(pinDCD,OUTPUT); + s_pinWrite(pinDCD,dcdStatus); + result=ZOK; + } + else + result=ZERROR; + break; + case 48: + pinModeDecoder(sval,&ctsActive,&ctsInactive,DEFAULT_CTS_HIGH,DEFAULT_CTS_LOW); + result=ZOK; + break; + case 49: + if((sval >= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinCTS=sval; + pinMode(pinCTS,INPUT); + serial.setFlowControlType(serial.getFlowControlType()); + result=ZOK; + } + else + result=ZERROR; + break; + case 50: + pinModeDecoder(sval,&rtsActive,&rtsInactive,DEFAULT_RTS_HIGH,DEFAULT_RTS_LOW); + if(pinSupport[pinRTS]) + { + serial.setFlowControlType(serial.getFlowControlType()); + s_pinWrite(pinRTS,rtsActive); + } + result=ZOK; + break; + case 51: + if((sval >= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinRTS=sval; + pinMode(pinRTS,OUTPUT); + serial.setFlowControlType(serial.getFlowControlType()); + s_pinWrite(pinRTS,rtsActive); + result=ZOK; + } + else + result=ZERROR; + break; + case 52: + pinModeDecoder(sval,&riActive,&riInactive,DEFAULT_RI_HIGH,DEFAULT_RI_LOW); + if(pinSupport[pinRI]) + { + serial.setFlowControlType(serial.getFlowControlType()); + s_pinWrite(pinRI,riInactive); + } + result=ZOK; + break; + case 53: + if((sval >= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinRI=sval; + pinMode(pinRI,OUTPUT); + s_pinWrite(pinRTS,riInactive); + result=ZOK; + } + else + result=ZERROR; + break; + case 54: + pinModeDecoder(sval,&dtrActive,&dtrInactive,DEFAULT_DTR_HIGH,DEFAULT_DTR_LOW); + result=ZOK; + break; + case 55: + if((sval >= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinDTR=sval; + pinMode(pinDTR,INPUT); + result=ZOK; + } + else + result=ZERROR; + break; + case 56: + pinModeDecoder(sval,&dsrActive,&dsrInactive,DEFAULT_DSR_HIGH,DEFAULT_DSR_LOW); + s_pinWrite(pinDSR,dsrActive); + result=ZOK; + break; + case 57: + if((sval >= 0) && (sval <= MAX_PIN_NO) && pinSupport[sval]) + { + pinDSR=sval; + pinMode(pinDSR,OUTPUT); + s_pinWrite(pinDSR,dsrActive); + result=ZOK; + } + else + result=ZERROR; + break; + case 60: + if(sval >= 0) + { + preserveListeners=(sval != 0); + if(preserveListeners) + WiFiServerNode::SaveWiFiServers(); + else + SPIFFS.remove("/zlisteners.txt"); + } + else + result=ZERROR; + break; + case 61: + if(sval > 0) + printMode.setTimeoutDelayMs(sval * 1000); + else + result=ZERROR; + break; + case 62: + telnetSupport = (sval > 0); + default: + break; + } + } + } + } + break; + case '+': + for(int i=0;vbuf[i]!=0;i++) + vbuf[i]=lc(vbuf[i]); + if(strcmp((const char *)vbuf,"config")==0) + { + configMode.switchTo(); + result = ZOK; + } +#ifdef INCLUDE_SD_SHELL + else + if((strstr((const char *)vbuf,"shell")==(char *)vbuf) + &&(SD.cardType() != CARD_NONE)) + { + char *colon=strchr((const char*)vbuf,':'); + result = ZOK; + if(colon == 0) + browseMode.switchTo(); + else + { + String line = colon+1; + line.trim(); + browseMode.init(); + browseMode.doModeCommand(line); + } + } +# ifdef INCLUDE_HOSTCM + else + if((strstr((const char *)vbuf,"hostcm")==(char *)vbuf)) + { + result = ZOK; + hostcmMode.switchTo(); + } +# endif +#endif +# ifdef INCLUDE_IRCC + else + if((strstr((const char *)vbuf,"irc")==(char *)vbuf)) + { + result = ZOK; + ircMode.switchTo(); + } +# endif +#ifdef INCLUDE_SLIP + else + if((strstr((const char *)vbuf,"slip")==(char *)vbuf)) + { + result = ZOK; + slipMode.switchTo(); + } +# endif +# ifdef INCLUDE_PING + else + if((strstr((const char *)vbuf,"ping")==(char *)vbuf)) + { + char *host = (char *)vbuf + 4; + while((*host == '"' || *host==' ')&&(*host != 0)) + host++; + while(strlen(host)>0) + { + char c = *(host + strlen(host)-1); + if((c!='"') && (c!=' ')) + break; + *(host + strlen(host)-1) = 0; + } + if(strlen(host)==0) + result = ZERROR; + else + result = (ping(host) >= 0 )? ZOK : ZERROR; + } +# endif + else + if((strstr((const char *)vbuf,"print")==(char *)vbuf)||(strstr((const char *)vbuf,"PRINT")==(char *)vbuf)) + result = printMode.switchTo((char *)vbuf+5,vlen-5,serial.isPetsciiMode()); + else + result=ZERROR; //todo: branch based on vbuf contents + break; + case '$': + { + int eqMark=0; + for(int i=0;vbuf[i]!=0;i++) + if(vbuf[i]=='=') + { + eqMark=i; + break; + } + else + vbuf[i]=lc(vbuf[i]); + if(eqMark==0) + result=ZERROR; // no EQ means no good + else + { + vbuf[eqMark]=0; + String var=(char *)vbuf; + var.trim(); + String val=(char *)(vbuf+eqMark+1); + val.trim(); + result = ((val.length()==0)&&((strcmp(var.c_str(),"pass")!=0))) ? ZERROR : ZOK; + if(result == ZOK) + { + if(strcmp(var.c_str(),"ssid")==0) + wifiSSI = val; + else + if(strcmp(var.c_str(),"pass")==0) + wifiPW = val; + else + if(strcmp(var.c_str(),"mdns")==0) + hostname = val; + else + if(strcmp(var.c_str(),"sb")==0) + result = doBaudCommand(atoi(val.c_str()),(uint8_t *)val.c_str(),val.length()); + else + result = ZERROR; + } + } + break; + } + case '%': + result=ZERROR; + break; + case '&': + switch(lc(secCmd)) + { + case 'k': + if((!isNumber)||(vval>=FCT_INVALID)) + result=ZERROR; + else + { + packetXOn = true; + serial.setXON(true); + switch(vval) + { + case 0: case 1: case 2: + serial.setFlowControlType(FCT_DISABLED); + break; + case 3: case 6: + serial.setFlowControlType(FCT_RTSCTS); + break; + case 4: case 5: + serial.setFlowControlType(FCT_NORMAL); + break; + default: + result=ZERROR; + break; + } + } + break; + case 'l': + loadConfig(); + break; + case 'w': + reSaveConfig(); + break; + case 'f': + if(vval == 86) + { + loadConfig(); + zclock.reset(); + result = SPIFFS.format() ? ZOK : ZERROR; + reSaveConfig(); + } + else + { + SPIFFS.remove(CONFIG_FILE); + SPIFFS.remove(CONFIG_FILE_OLD); + SPIFFS.remove("/zphonebook.txt"); + SPIFFS.remove("/zlisteners.txt"); + PhoneBookEntry::clearPhonebook(); + if(WiFi.status() == WL_CONNECTED) + WiFi.disconnect(); + wifiSSI=""; + delay(500); + zclock.reset(); + result=doResetCommand(); + showInitMessage(); + } + break; + case 'm': + if(vval > 0) + { + int len = (tempMaskOuts != NULL) ? strlen(tempMaskOuts) : 0; + char newMaskOuts[len+2]; // 1 for the new char, and 1 for the 0 never counted + if(len > 0) + strcpy(newMaskOuts,tempMaskOuts); + newMaskOuts[len] = vval; + newMaskOuts[len+1] = 0; + setCharArray(&tempMaskOuts,newMaskOuts); + } + else + { + char newMaskOuts[vlen+1]; + newMaskOuts[vlen]=0; + if(vlen > 0) + memcpy(newMaskOuts,vbuf,vlen); + setCharArray(&tempMaskOuts,newMaskOuts); + } + result=ZOK; + break; + case 'y': + { + if(isNumber && ((vval > 0)||(vbuf[0]=='0'))) + { + machineState = stateMachine; + machineQue = ""; + if(current != null) + { + current->machineState = current->stateMachine; + current->machineQue = ""; + } + while(vval > 0) + { + vval--; + if((machineState != null)&&(machineState[0]!=0)) + machineState += ZI_STATE_MACHINE_LEN; + if(current != null) + { + if((current->machineState != null)&&(current->machineState[0]!=0)) + current->machineState += ZI_STATE_MACHINE_LEN; + } + } + } + else + if((vlen % ZI_STATE_MACHINE_LEN) != 0) + result=ZERROR; + else + { + bool ok = true; + const char *HEX_DIGITS = "0123456789abcdefABCDEF"; + for(int i=0;ok && (i 0) + memcpy(newStateMachine,vbuf,vlen); + setCharArray(&tempStateMachine,newStateMachine); + result=ZOK; + } + else + { + result=ZERROR; + } + } + } + break; + case 'd': + if(vval > 0) + { + int len = (tempDelimiters != NULL) ? strlen(tempDelimiters) : 0; + char newDelimiters [len+2]; // 1 for the new char, and 1 for the 0 never counted + if(len > 0) + strcpy(newDelimiters,tempDelimiters); + newDelimiters[len] = vval; + newDelimiters[len+1] = 0; + setCharArray(&tempDelimiters,newDelimiters); + } + else + { + char newDelimiters[vlen+1]; + newDelimiters[vlen]=0; + if(vlen > 0) + memcpy(newDelimiters,vbuf,vlen); + setCharArray(&tempDelimiters,newDelimiters); + } + result=ZOK; + break; + case 'o': + if(vval == 0) + { + if(logFileOpen) + { + logFile.flush(); + logFile.close(); + logFileOpen = false; + } + logFile = SPIFFS.open("/logfile.txt", "r"); + int numBytes = logFile.available(); + while (numBytes > 0) + { + if(numBytes > 128) + numBytes = 128; + byte buf[numBytes]; + int numRead = logFile.read(buf,numBytes); + int i=0; + while(i < numRead) + { + if(serial.availableForWrite() > 1) + { + serial.printc((char)buf[i++]); + } + else + { + if(serial.isSerialOut()) + { + serialOutDeque(); + hwSerialFlush(); + } + delay(1); + yield(); + } + if(serial.drainForXonXoff()==3) + { + serial.setXON(true); + while(logFile.available()>0) + logFile.read(); + break; + } + yield(); + } + numBytes = logFile.available(); + } + logFile.close(); + serial.prints(EOLN); + result=ZOK; + } + else + if(logFileOpen) + result=ZERROR; + else + if(vval==86) + { + result = SPIFFS.exists("/logfile.txt") ? ZOK : ZERROR; + if(result) + SPIFFS.remove("/logfile.txt"); + } + else + if(vval==87) + SPIFFS.remove("/logfile.txt"); + else + { + logFileOpen = true; + SPIFFS.remove("/logfile.txt"); + logFile = SPIFFS.open("/logfile.txt", "w"); + if(vval==88) + logFileDebug=true; + result=ZOK; + } + break; + case 'h': + { + char filename[50]; + sprintf(filename,"/c64net-help-%s.txt",ZIMODEM_VERSION); + if(vval == 6502) + { + SPIFFS.remove(filename); + result=ZOK; + } + else + { + int oldDelay = serialDelayMs; + serialDelayMs = vval; + uint8_t buf[100]; + sprintf((char *)buf,"www.zimmers.net:80/otherprojs%s",filename); + serial.prints("Control-C to Abort."); + serial.prints(EOLN); + result = doWebStream(0,buf,strlen((char *)buf),false,filename,true); + serialDelayMs = oldDelay; + if((result == ZERROR) + &&(WiFi.status() != WL_CONNECTED)) + { + serial.prints("Not Connected."); + serial.prints(EOLN); + serial.prints("Use ATW to list access points."); + serial.prints(EOLN); + serial.prints("ATW\"[SSI],[PASSWORD]\" to connect."); + serial.prints(EOLN); + } + } + break; + } + case 'g': + result = doWebStream(vval,vbuf,vlen,isNumber,"/temp.web",false); + break; + case 's': + if(vlen<3) + result=ZERROR; + else + { + char *eq=strchr((char *)vbuf,'='); + if((eq == null)||(eq == (char *)vbuf)||(eq>=(char *)&(vbuf[vlen-1]))) + result=ZERROR; + else + { + *eq=0; + int snum = atoi((char *)vbuf); + if((snum == 0)&&((vbuf[0]!='0')||(eq != (char *)(vbuf+1)))) + result=ZERROR; + else + { + eq++; + switch(snum) + { + case 40: + if(*eq == 0) + result=ZERROR; + else + { + hostname = eq; + if(WiFi.status()==WL_CONNECTED) + connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN); + result=ZOK; + } + break; + case 41: + if(*eq == 0) + result=ZERROR; + else + { + termType = eq; + result=ZOK; + } + break; + case 42: + if((*eq == 0)||(strlen(eq)>250)) + result=ZERROR; + else + { + busyMsg = eq; + busyMsg.replace("\\n","\n"); + busyMsg.replace("\\r","\r"); + result=ZOK; + } + break; + default: + result=ZERROR; + break; + } + } + } + } + break; + case 'p': + serial.setPetsciiMode(vval > 0); + break; + case 'n': + if(isNumber && (vval >=0) && (vval <= MAX_PIN_NO) && pinSupport[vval]) + { + int pinNum = vval; + int r = digitalRead(pinNum); + //if(pinNum == pinCTS) + // serial.printf("Pin %d READ=%s %s.%s",pinNum,r==HIGH?"HIGH":"LOW",enableRtsCts?"ACTIVE":"INACTIVE",EOLN.c_str()); + //else + serial.printf("Pin %d READ=%s.%s",pinNum,r==HIGH?"HIGH":"LOW",EOLN.c_str()); + } + else + if(!isNumber) + { + char *eq = strchr((char *)vbuf,'='); + if(eq == 0) + result = ZERROR; + else + { + *eq = 0; + int pinNum = atoi((char *)vbuf); + int sval = atoi(eq+1); + if((pinNum < 0) || (pinNum >= MAX_PIN_NO) || (!pinSupport[pinNum])) + result = ZERROR; + else + { + s_pinWrite(pinNum,sval); + serial.printf("Pin %d FORCED %s.%s",pinNum,(sval==LOW)?"LOW":(sval==HIGH)?"HIGH":"UNK",EOLN.c_str()); + } + } + } + else + result = ZERROR; + break; + case 't': + if(vlen == 0) + { + serial.prints(zclock.getCurrentTimeFormatted()); + serial.prints(EOLN); + result = ZIGNORE_SPECIAL; + } + else + result = doTimeZoneSetupCommand(vval, vbuf, vlen, isNumber); + break; + case 'u': + result=doUpdateFirmware(vval,vbuf,vlen,isNumber); + break; + default: + result=ZERROR; + break; + } + break; + default: + result=ZERROR; + break; + } + } + + if(tempDelimiters != NULL) + { + setCharArray(&delimiters,tempDelimiters); + freeCharArray(&tempDelimiters); + } + if(tempMaskOuts != NULL) + { + setCharArray(&maskOuts,tempMaskOuts); + freeCharArray(&tempMaskOuts); + } + if(tempStateMachine != NULL) + { + setCharArray(&stateMachine,tempStateMachine); + freeCharArray(&tempStateMachine); + machineState = stateMachine; + } + if(result != ZIGNORE_SPECIAL) + previousCommand = saveCommand; + if((suppressResponses)&&(result == ZERROR)) + return ZERROR; + if(crc8 >= 0) + result=ZERROR; // setting S42 without a T command is now Bad. + if((result != ZOK)||(index >= len)) + sendOfficialResponse(result); + if(result == ZERROR) // on error, cut and run + return ZERROR; + } + return result; +} + +void ZCommand::sendOfficialResponse(ZResult res) +{ + if(!suppressResponses) + { + switch(res) + { + case ZOK: + logPrintln("Response: OK"); + preEOLN(EOLN); + if(numericResponses) + serial.prints("0"); + else + serial.prints("OK"); + serial.prints(EOLN); + break; + case ZERROR: + logPrintln("Response: ERROR"); + preEOLN(EOLN); + if(numericResponses) + serial.prints("4"); + else + serial.prints("ERROR"); + serial.prints(EOLN); + break; + case ZNOANSWER: + logPrintln("Response: NOANSWER"); + preEOLN(EOLN); + if(numericResponses) + serial.prints("8"); + else + serial.prints("NO ANSWER"); + serial.prints(EOLN); + break; + case ZCONNECT: + logPrintln("Response: Connected!"); + sendConnectionNotice((current == null) ? baudRate : current->id); + break; + default: + break; + } + } +} + +void ZCommand::showInitMessage() +{ + serial.prints(commandMode.EOLN); +#ifdef ZIMODEM_ESP32 + int totalSPIFFSSize = SPIFFS.totalBytes(); +#else + FSInfo info; + SPIFFS.info(info); + int totalSPIFFSSize = info.totalBytes; +#endif + serial.prints("Zimodem "); +#ifdef ZIMODEM_ESP32 + serial.prints("ESP32 "); +#else + if (getDefaultCtsPin() == 0) + serial.prints("ESP01 "); + else + serial.prints("ESP8266 "); +#endif + serial.prints("Firmware v"); + HWSerial.setTimeout(60000); + serial.prints(ZIMODEM_VERSION); + //serial.prints(" ("); + //serial.prints(compile_date); + //serial.prints(")"); + serial.prints(commandMode.EOLN); + char s[100]; +#ifdef ZIMODEM_ESP32 + sprintf(s,"sdk=%s chipid=%d cpu@%d",ESP.getSdkVersion(),ESP.getChipRevision(),ESP.getCpuFreqMHz()); +#else + sprintf(s,"sdk=%s chipid=%d cpu@%d",ESP.getSdkVersion(),ESP.getFlashChipId(),ESP.getCpuFreqMHz()); +#endif + serial.prints(s); + serial.prints(commandMode.EOLN); +#ifdef ZIMODEM_ESP32 + sprintf(s,"totsize=%dk hsize=%dk fsize=%dk speed=%dm",(ESP.getFlashChipSize()/1024),(ESP.getFreeHeap()/1024),totalSPIFFSSize/1024,(ESP.getFlashChipSpeed()/1000000)); +#else + sprintf(s,"totsize=%dk ssize=%dk fsize=%dk speed=%dm",(ESP.getFlashChipRealSize()/1024),(ESP.getSketchSize()/1024),totalSPIFFSSize/1024,(ESP.getFlashChipSpeed()/1000000)); +#endif + + serial.prints(s); + serial.prints(commandMode.EOLN); + if(wifiSSI.length()>0) + { + if(WiFi.status() == WL_CONNECTED) + serial.prints(("CONNECTED TO " + wifiSSI + " (" + WiFi.localIP().toString().c_str() + ")").c_str()); + else + serial.prints(("ERROR ON " + wifiSSI).c_str()); + } + else + serial.prints("INITIALIZED"); + serial.prints(commandMode.EOLN); + serial.prints("READY."); + serial.prints(commandMode.EOLN); + serial.flush(); +} + +uint8_t *ZCommand::doStateMachine(uint8_t *buf, uint16_t *bufLen, char **machineState, String *machineQue, char *stateMachine) +{ + if((stateMachine != NULL) && ((stateMachine)[0] != 0) && (*machineState != NULL) && ((*machineState)[0] != 0)) + { + String newBuf = ""; + for(int i=0;i<*bufLen;) + { + char matchChar = FROMHEX((*machineState)[0],(*machineState)[1]); + if((matchChar == 0)||(matchChar == buf[i])) + { + char c= buf[i++]; + short cmddex=1; + do + { + cmddex++; + switch(lc((*machineState)[cmddex])) + { + case '-': // do nothing + case 'e': // do nothing + break; + case 'p': // push to the que + if(machineQue->length() < 256) + *machineQue += c; + break; + case 'd': // display this char + newBuf += c; + break; + case 'x': // flush queue + *machineQue = ""; + break; + case 'q': // eat this char, but flush the queue + if(machineQue->length()>0) + { + newBuf += *machineQue; + *machineQue = ""; + } + break; + case 'r': // replace this char + if(cmddex == 2) + { + char newChar = FROMHEX((*machineState)[cmddex+1],(*machineState)[cmddex+2]); + newBuf += newChar; + } + break; + default: + break; + } + } + while((cmddex<4) && (lc((*machineState)[cmddex])!='r')); + char *newstate = stateMachine + (ZI_STATE_MACHINE_LEN * FROMHEX((*machineState)[5],(*machineState)[6])); + char *test = stateMachine; + while(test[0] != 0) + { + if(test == newstate) + { + (*machineState) = test; + break; + } + test += ZI_STATE_MACHINE_LEN; + } + } + else + { + *machineState += ZI_STATE_MACHINE_LEN; + if((*machineState)[0] == 0) + { + *machineState = stateMachine; + i++; + } + } + } + if((*bufLen != newBuf.length()) || (memcmp(buf,newBuf.c_str(),*bufLen)!=0)) + { + if(newBuf.length() > 0) + { + if(newBuf.length() > *bufLen) + { + free(buf); + buf = (uint8_t *)malloc(newBuf.length()); + } + memcpy(buf,newBuf.c_str(),newBuf.length()); + } + *bufLen = newBuf.length(); + } + } + return buf; +} + +uint8_t *ZCommand::doMaskOuts(uint8_t *buf, uint16_t *bufLen, char *maskOuts) +{ + if(maskOuts[0] != 0) + { + uint16_t oldLen=*bufLen; + for(int i=0,o=0;i2)) + { + headerOut(conn->id,0,0,0); + } + else + if(conn->blankPackets>=which) + { + headerOut(conn->id,conn->nextPacketNum-which,0,0); + } + else + if(conn->lastPacket[which].len == 0) // never used, or empty + { + headerOut(conn->id,conn->lastPacket[which].num,0,0); + } + else + { + uint16_t bufLen = conn->lastPacket[which].len; + uint8_t *buf = (uint8_t *)malloc(bufLen); + uint8_t num = conn->lastPacket[which].num; + memcpy(buf,conn->lastPacket[which].buf,bufLen); + + buf = doMaskOuts(buf,&bufLen,maskOuts); + buf = doMaskOuts(buf,&bufLen,conn->maskOuts); + buf = doStateMachine(buf,&bufLen,&machineState,&machineQue,stateMachine); + buf = doStateMachine(buf,&bufLen,&(conn->machineState),&(conn->machineQue),conn->stateMachine); + if(nextConn->isPETSCII()) + { + int oldLen=bufLen; + for(int i=0, b=0;iid,num,bufLen,(int)crc); + int bct=0; + int i=0; + while(i < bufLen) + { + uint8_t c=buf[i++]; + switch(binType) + { + case BTYPE_NORMAL: + case BTYPE_NORMAL_NOCHK: + case BTYPE_NORMAL_PLUS: + serial.write(c); + break; + case BTYPE_HEX: + case BTYPE_HEX_PLUS: + { + const char *hbuf = TOHEX(c); + serial.printb(hbuf[0]); // prevents petscii + serial.printb(hbuf[1]); + if((++bct)>=39) + { + serial.prints(EOLN); + bct=0; + } + break; + } + case BTYPE_DEC: + case BTYPE_DEC_PLUS: + serial.printf("%d%s",c,EOLN.c_str()); + break; + } + while(serial.availableForWrite()<5) + { + if(serial.isSerialOut()) + { + serialOutDeque(); + hwSerialFlush(); + } + serial.drainForXonXoff(); + delay(1); + yield(); + } + yield(); + } + if(bct > 0) + serial.prints(EOLN); + free(buf); + } +} + +void ZCommand::clearPlusProgress() +{ + if(currentExpiresTimeMs > 0) + currentExpiresTimeMs = 0; + if((strcmp((char *)nbuf,ECS)==0)&&((millis()-lastNonPlusTimeMs)>1000)) + currentExpiresTimeMs = millis() + 1000; +} + +bool ZCommand::checkPlusEscape() +{ + if((currentExpiresTimeMs > 0) && (millis() > currentExpiresTimeMs)) + { + currentExpiresTimeMs = 0; + if(strcmp((char *)nbuf,ECS)==0) + { + if(current != null) + { + if(!suppressResponses) + { + preEOLN(EOLN); + if(numericResponses) + { + serial.prints("3"); + serial.prints(EOLN); + } + else + if(current->isAnswered()) + { + serial.prints("NO CARRIER "); + serial.printf("%d %s:%d",current->id,current->host,current->port); + serial.prints(EOLN); + serial.flush(); + } + } + delete current; + current = conns; + nextConn = conns; + } + memset(nbuf,0,MAX_COMMAND_SIZE); + eon=0; + return true; + } + } + return false; +} + +void ZCommand::sendNextPacket() +{ + if(serial.availableForWrite()next == null)) + { + firstConn = null; + nextConn = conns; + } + else + nextConn = nextConn->next; + while(serial.isSerialOut() && (nextConn != null) && (packetXOn)) + { + if(nextConn->available()>0) + //&& (nextConn->isConnected())) // being connected is not required to have buffered bytes waiting! + { + int availableBytes = nextConn->available(); + int maxBytes=packetSize; + if(availableBytes 0) + { + if((nextConn->delimiters[0] != 0) || (delimiters[0] != 0)) + { + uint16_t lastLen = nextConn->lastPacket[0].len; + uint8_t *lastBuf = nextConn->lastPacket[0].buf; + + if((lastLen >= packetSize) + ||((lastLen>0) + &&((strchr(nextConn->delimiters,lastBuf[lastLen-1]) != null) + ||(strchr(delimiters,lastBuf[lastLen-1]) != null)))) + lastLen = 0; + int bytesRemain = maxBytes; + while((bytesRemain > 0) + &&(lastLen < packetSize) + &&((lastLen==0) + ||((strchr(nextConn->delimiters,lastBuf[lastLen-1]) == null) + &&(strchr(delimiters,lastBuf[lastLen-1]) == null)))) + { + uint8_t c=nextConn->read(); + logSocketIn(c); + lastBuf[lastLen++] = c; + bytesRemain--; + } + nextConn->lastPacket[0].len = lastLen; + if((lastLen >= packetSize) + ||((lastLen>0) + &&((strchr(nextConn->delimiters,lastBuf[lastLen-1]) != null) + ||(strchr(delimiters,lastBuf[lastLen-1]) != null)))) + maxBytes = lastLen; + else + { + if(serial.getFlowControlType() == FCT_MANUAL) + { + if(nextConn->blankPackets == 0) + memcpy(&nextConn->lastPacket[2],&nextConn->lastPacket[1],sizeof(struct Packet)); + nextConn->blankPackets++; + headerOut(nextConn->id,nextConn->nextPacketNum++,0,0); + packetXOn = false; + } + else + if(serial.getFlowControlType() == FCT_AUTOOFF) + packetXOn = false; + return; + } + } + else + { + maxBytes = nextConn->read(nextConn->lastPacket[0].buf,maxBytes); + logSocketIn(nextConn->lastPacket[0].buf,maxBytes); + } + nextConn->lastPacket[0].num=nextConn->nextPacketNum++; + nextConn->lastPacket[0].len=maxBytes; + lastPacketId=nextConn->id; + if(nextConn->blankPackets>0) + { + nextConn->lastPacket[2].num=nextConn->nextPacketNum-1; + nextConn->lastPacket[2].len=0; + } + else + memcpy(&nextConn->lastPacket[2],&nextConn->lastPacket[1],sizeof(struct Packet)); + memcpy(&nextConn->lastPacket[1],&nextConn->lastPacket[0],sizeof(struct Packet)); + nextConn->blankPackets=0; + reSendLastPacket(nextConn,1); + if(serial.getFlowControlType() == FCT_AUTOOFF) + { + packetXOn = false; + } + else + if(serial.getFlowControlType() == FCT_MANUAL) + { + packetXOn = false; + return; + } + break; + } + } + else + if(!nextConn->isConnected()) + { + if(nextConn->wasConnected) + { + nextConn->wasConnected=false; + if(!suppressResponses) + { + if(numericResponses) + { + preEOLN(EOLN); + serial.prints("3"); + serial.prints(EOLN); + } + else + if(nextConn->isAnswered()) + { + preEOLN(EOLN); + serial.prints("NO CARRIER "); + serial.printi(nextConn->id); + serial.prints(EOLN); + serial.flush(); + } + if(serial.getFlowControlType() == FCT_MANUAL) + { + return; + } + } + checkOpenConnections(); + } + if(nextConn->serverClient) + { + delete nextConn; + nextConn = null; + break; // messes up the order, so just leave and start over + } + } + + if(nextConn->next == null) + nextConn = null; // will become CONNs + else + nextConn = nextConn->next; + if(nextConn == firstConn) + break; + } + if((serial.getFlowControlType() == FCT_MANUAL) && (packetXOn)) + { + packetXOn = false; + firstConn = conns; + if(firstConn != NULL) + headerOut(firstConn->id,firstConn->nextPacketNum++,0,0); + else + headerOut(0,0,0,0); + while(firstConn != NULL) + { + firstConn->lastPacket[0].len = 0; + if(firstConn->blankPackets == 0) + memcpy(&firstConn->lastPacket[2],&firstConn->lastPacket[1],sizeof(struct Packet)); + firstConn->blankPackets++; + firstConn = firstConn->next; + } + } +} + +void ZCommand::sendConnectionNotice(int id) +{ + preEOLN(EOLN); + if(numericResponses) + { + if(!longResponses) + serial.prints("1"); + else + if(baudRate < 1200) + serial.prints("1"); + else + if(baudRate < 2400) + serial.prints("5"); + else + if(baudRate < 4800) + serial.prints("10"); + else + if(baudRate < 7200) + serial.prints("11"); + else + if(baudRate < 9600) + serial.prints("24"); + else + if(baudRate < 12000) + serial.prints("12"); + else + if(baudRate < 14400) + serial.prints("25"); + else + if(baudRate < 19200) + serial.prints("13"); + else + serial.prints("28"); + } + else + { + serial.prints("CONNECT"); + if(longResponses) + { + serial.prints(" "); + serial.printi(id); + } + } + serial.prints(EOLN); +} + +void ZCommand::acceptNewConnection() +{ + WiFiServerNode *serv = servs; + while(serv != null) + { + if(serv->hasClient()) + { + WiFiClient newClient = serv->server->available(); + if(newClient.connected()) + { + int port=newClient.localPort(); + String remoteIPStr = newClient.remoteIP().toString(); + const char *remoteIP=remoteIPStr.c_str(); + bool found=false; + WiFiClientNode *c=conns; + while(c!=null) + { + if((c->isConnected()) + &&(c->port==port) + &&(strcmp(remoteIP,c->host)==0)) + found=true; + c=c->next; + } + if(!found) + { + //BZ:newClient.setNoDelay(true); + int futureRings = (ringCounter > 0)?(ringCounter-1):5; + WiFiClientNode *newClientNode = new WiFiClientNode(newClient, serv->flagsBitmap, (futureRings+1) * 2); + setCharArray(&(newClientNode->delimiters),serv->delimiters); + setCharArray(&(newClientNode->maskOuts),serv->maskOuts); + setCharArray(&(newClientNode->stateMachine),serv->stateMachine); + newClientNode->machineState = newClientNode->stateMachine; + s_pinWrite(pinRI,riActive); + preEOLN(EOLN); + serial.prints(numericResponses?"2":"RING"); + serial.prints(EOLN); + lastServerClientId = newClientNode->id; + if(newClientNode->isAnswered()) + { + if(autoStreamMode) + { + sendConnectionNotice(baudRate); + doAnswerCommand(0, (uint8_t *)"", 0, false, ""); + break; + } + else + sendConnectionNotice(newClientNode->id); + } + } + } + } + serv=serv->next; + } + // handle rings properly + WiFiClientNode *conn = conns; + unsigned long now=millis(); + while(conn != null) + { + WiFiClientNode *nextConn = conn->next; + if((!conn->isAnswered())&&(conn->isConnected())) + { + if(now > conn->nextRingTime(0)) + { + conn->nextRingTime(3000); + int rings=conn->ringsRemaining(-1); + if(rings <= 1) + { + s_pinWrite(pinRI,riInactive); + if(ringCounter > 0) + { + preEOLN(EOLN); + conn->answer(); + if(autoStreamMode) + { + sendConnectionNotice(baudRate); + doAnswerCommand(0, (uint8_t *)"", 0, false, ""); + break; + } + else + sendConnectionNotice(conn->id); + } + else + delete conn; + } + else + if((rings % 2) == 0) + { + s_pinWrite(pinRI,riActive); + preEOLN(EOLN); + serial.prints(numericResponses?"2":"RING"); + serial.prints(EOLN); + } + else + s_pinWrite(pinRI,riInactive); + } + } + conn = nextConn; + } +} + +void ZCommand::serialIncoming() +{ + bool crReceived=readSerialStream(); + clearPlusProgress(); // every serial incoming, without a plus, breaks progress + if((!crReceived)||(eon==0)) + return; + //delay(200); // give a pause after receiving command before responding + // the delay doesn't affect xon/xoff because its the periodic transmitter that manages that. + doSerialCommand(); +} + +void ZCommand::loop() +{ + checkPlusEscape(); + acceptNewConnection(); + if(serial.isSerialOut()) + { + sendNextPacket(); + serialOutDeque(); + } + checkBaudChange(); +} + diff --git a/src/zconfigmode.ino b/src/zconfigmode.ino new file mode 100644 index 0000000..ed3f017 --- /dev/null +++ b/src/zconfigmode.ino @@ -0,0 +1,875 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +void ZConfig::switchTo() +{ + currMode=&configMode; + serial.setFlowControlType(commandMode.serial.getFlowControlType()); + serial.setPetsciiMode(commandMode.serial.isPetsciiMode()); + savedEcho=commandMode.doEcho; + newListen=commandMode.preserveListeners; + commandMode.doEcho=true; + serverSpec.port=6502; + serverSpec.flagsBitmap=commandMode.getConfigFlagBitmap() & (~FLAG_ECHO); + if(servs) + serverSpec = *servs; + serial.setXON(true); + showMenu=true; + EOLN=commandMode.EOLN; + EOLNC=EOLN.c_str(); + currState = ZCFGMENU_MAIN; + lastNumber=0; + lastAddress=""; + lastOptions=""; + settingsChanged=false; + lastNumNetworks=0; +} + +void ZConfig::serialIncoming() +{ + bool crReceived=commandMode.readSerialStream(); + commandMode.clearPlusProgress(); // re-check the plus-escape mode + if(crReceived) + { + doModeCommand(); + } +} + +void ZConfig::switchBackToCommandMode() +{ + commandMode.doEcho=savedEcho; + currMode = &commandMode; +} + +void ZConfig::doModeCommand() +{ + String cmd = commandMode.getNextSerialCommand(); + char c='?'; + char sc='?'; + for(int i=0;i32) + { + c=lc(cmd[i]); + if((i32)) + sc=lc(cmd[i+1]); + break; + } + } + switch(currState) + { + case ZCFGMENU_MAIN: + { + if((c=='q')||(cmd.length()==0)) + { + if(settingsChanged) + { + currState=ZCFGMENU_WICONFIRM; + showMenu=true; + } + else + { + commandMode.showInitMessage(); + switchBackToCommandMode(); + return; + } + } + else + if(c=='a') // add to phonebook + { + currState=ZCFGMENU_NUM; + showMenu=true; + } + else + if(c=='w') // wifi + { + currState=ZCFGMENU_WIMENU; + showMenu=true; + } + else + if(c=='h') // host + { + currState=ZCFGMENU_NEWHOST; + showMenu=true; + } + else + if(c=='f') // flow control + { + currState=ZCFGMENU_FLOW; + showMenu=true; + } + else + if((c=='p')&&(sc=='e')) // petscii translation toggle + { + commandMode.serial.setPetsciiMode(!commandMode.serial.isPetsciiMode()); + serial.setPetsciiMode(commandMode.serial.isPetsciiMode()); + settingsChanged=true; + showMenu=true; + } + else + if((c=='p')&&(sc=='r')) // print spec + { + currState=ZCFGMENU_NEWPRINT; + showMenu=true; + } + else + if(c=='e') // echo + { + savedEcho = !savedEcho; + settingsChanged=true; + showMenu=true; + } + else + if(c=='b') // bbs + { + currState=ZCFGMENU_BBSMENU; + showMenu=true; + } + else + if(c>47 && c<58) // its a phonebook entry! + { + PhoneBookEntry *pb=PhoneBookEntry::findPhonebookEntry(cmd); + if(pb == null) + { + serial.printf("%s%sPhone number not found: '%s'.%s%s",EOLNC,EOLNC,cmd.c_str(),EOLNC,EOLNC); + currState=ZCFGMENU_MAIN; + showMenu=true; + } + else + { + lastNumber = pb->number; + lastAddress = pb->address; + lastOptions = pb->modifiers; + lastNotes = pb->notes; + currState=ZCFGMENU_ADDRESS; + showMenu=true; + } + } + else + { + showMenu=true; // re-show the menu + } + break; + } + case ZCFGMENU_WICONFIRM: + { + if((cmd.length()==0)||(c=='n')) + { + commandMode.showInitMessage(); + switchBackToCommandMode(); + return; + } + else + if(c=='y') + { + if(newListen != commandMode.preserveListeners) + { + commandMode.preserveListeners=newListen; + if(!newListen) + { + SPIFFS.remove("/zlisteners.txt"); + WiFiServerNode::DestroyAllServers(); + } + else + { + commandMode.ringCounter=1; + commandMode.autoStreamMode=true; + WiFiServerNode *s=WiFiServerNode::FindServer(serverSpec.port); + if(s != null) + delete s; + s = new WiFiServerNode(serverSpec.port,serverSpec.flagsBitmap); + WiFiServerNode::SaveWiFiServers(); + } + } + else + if(commandMode.preserveListeners) + { + WiFiServerNode *s = WiFiServerNode::FindServer(serverSpec.port); + if( s != null) + { + if(s->flagsBitmap != serverSpec.flagsBitmap) + { + s->flagsBitmap = serverSpec.flagsBitmap; + } + } + else + { + WiFiServerNode::DestroyAllServers(); + s = new WiFiServerNode(serverSpec.port,serverSpec.flagsBitmap); + WiFiServerNode::SaveWiFiServers(); + commandMode.updateAutoAnswer(); + } + } + commandMode.reSaveConfig(); + serial.printf("%sSettings saved.%s",EOLNC,EOLNC); + commandMode.showInitMessage(); + WiFiServerNode::SaveWiFiServers(); + switchBackToCommandMode(); + return; + } + else + showMenu=true; + } + case ZCFGMENU_NUM: + { + PhoneBookEntry *pb=PhoneBookEntry::findPhonebookEntry(cmd); + if(pb != null) + { + serial.printf("%s%sNumber already exists '%s'.%s%s",EOLNC,EOLNC,cmd.c_str(),EOLNC,EOLNC); + currState=ZCFGMENU_MAIN; + showMenu=true; + } + else + if(cmd.length()==0) + { + currState=ZCFGMENU_MAIN; + showMenu=true; + } + else + { + lastNumber = atol((char *)cmd.c_str()); + lastAddress = ""; + ConnSettings flags(commandMode.getConfigFlagBitmap()); + lastOptions = flags.getFlagString(); + lastNotes = ""; + currState=ZCFGMENU_ADDRESS; + showMenu=true; + } + break; + } + case ZCFGMENU_NEWPORT: + { + if(cmd.length()>0) + { + serverSpec.port = atoi((char *)cmd.c_str()); + settingsChanged=true; + } + currState=ZCFGMENU_BBSMENU; + showMenu=true; + break; + } + case ZCFGMENU_ADDRESS: + { + PhoneBookEntry *entry = PhoneBookEntry::findPhonebookEntry(lastNumber); + if(cmd.equalsIgnoreCase("delete") && (entry != null)) + { + delete entry; + currState=ZCFGMENU_MAIN; + serial.printf("%sPhonebook entry deleted.%s%s",EOLNC,EOLNC,EOLNC); + } + else + if((cmd.length()==0) && (entry != null)) + currState=ZCFGMENU_NOTES; // just keep old values + else + { + if(!validateHostInfo((uint8_t *)cmd.c_str())) + serial.printf("%sInvalid address format (hostname:port) or (user:pass@hostname:port) for '%s'.%s%s",EOLNC,cmd.c_str(),EOLNC,EOLNC); + else + { + lastAddress = cmd; + currState=ZCFGMENU_NOTES; + if(lastAddress.indexOf("@")>0) + { + int x = lastOptions.indexOf("S"); + if(x<0) + lastOptions += "S"; + else + lastOptions = lastOptions.substring(0,x) + lastOptions.substring(x+1); + } + } + } + showMenu=true; // re-show the menu + break; + } + case ZCFGMENU_BBSMENU: + { + if(cmd.length()==0) + currState=ZCFGMENU_MAIN; + else + { + ConnSettings flags(serverSpec.flagsBitmap); + switch(c) + { + case 'h': + currState=ZCFGMENU_NEWPORT; + break; + case 'd': + newListen=false; + settingsChanged=true; + break; + case 'p': + if(newListen) + { + flags.petscii=!flags.petscii; + settingsChanged=true; + } + break; + case 't': + if(newListen) + { + flags.telnet=!flags.telnet; + settingsChanged=true; + } + break; + case 'e': + if(newListen) + flags.echo=!flags.echo; + else + newListen=true; + settingsChanged=true; + break; + case 'f': + if(newListen) + { + if(flags.xonxoff) + { + flags.xonxoff=false; + flags.rtscts=true; + } + else + if(flags.rtscts) + flags.rtscts=false; + else + flags.xonxoff=true; + settingsChanged=true; + } + break; + case 's': + if(newListen) + { + flags.secure=!flags.secure; + settingsChanged=true; + } + break; + + default: + serial.printf("%sInvalid option '%s'.%s%s",EOLNC,cmd.c_str(),EOLNC,EOLNC); + break; + } + settingsChanged=true; + serverSpec.flagsBitmap = flags.getBitmap(); + } + showMenu=true; + break; + } + case ZCFGMENU_SUBNET: + { + if(cmd.length()==0) + currState=ZCFGMENU_NETMENU; + else + { + IPAddress *newaddr = ConnSettings::parseIP(cmd.c_str()); + if(newaddr==null) + serial.printf("%sBad address (%s).%s",EOLNC,cmd.c_str(),EOLNC); + else + { + currState=ZCFGMENU_NETMENU; + switch(netOpt) + { + case 'i': lastIP = *newaddr; break; + case 'd': lastDNS = *newaddr; break; + case 'g': lastGW = *newaddr; break; + case 's': lastSN = *newaddr; break; + } + free(newaddr); + } + } + showMenu=true; + break; + } + case ZCFGMENU_NETMENU: + { + if(cmd.length()==0) + { + currState=ZCFGMENU_MAIN; + if((staticIP==null)&&(useDHCP)) + {} + else + if(useDHCP) + { + setNewStaticIPs(null,null,null,null); + if(!connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN)) + serial.printf("%sUnable to connect to %s. :(%s",EOLNC,wifiSSI.c_str(),EOLNC); + settingsChanged=true; + } + else + if((staticIP==null) + ||(*staticIP != lastIP) + ||(*staticDNS != lastDNS) + ||(*staticGW != lastGW) + ||(*staticSN != lastSN)) + { + IPAddress *ip=new IPAddress(); + *ip = lastIP; + IPAddress *dns=new IPAddress(); + *dns = lastDNS; + IPAddress *gw=new IPAddress(); + *gw = lastGW; + IPAddress *sn=new IPAddress(); + *sn = lastSN; + setNewStaticIPs(ip,dns,gw,sn); + if(!connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN)) + serial.printf("%sUnable to connect to %s. :(%s",EOLNC,wifiSSI.c_str(),EOLNC); + settingsChanged=true; + } + } + else + if(useDHCP) + { + if(c=='d') + useDHCP=false; + } + else + { + switch(c) + { + case 'e': + useDHCP=true; + break; + case 'i': + case 'd': + case 'g': + case 's': + netOpt=c; + currState=ZCFGMENU_SUBNET; + break; + default: + serial.printf("%sInvalid option '%s'.%s%s",EOLNC,cmd.c_str(),EOLNC,EOLNC); + break; + } + } + showMenu=true; + break; + } + case ZCFGMENU_NOTES: + { + if(cmd.length()>0) + lastNotes=cmd; + currState=ZCFGMENU_OPTIONS; + showMenu=true; // re-show the menu + break; + } + case ZCFGMENU_OPTIONS: + { + if(cmd.length()==0) + { + PhoneBookEntry *entry = PhoneBookEntry::findPhonebookEntry(lastNumber); + if(entry != null) + { + serial.printf("%sPhonebook entry updated.%s%s",EOLNC,EOLNC,EOLNC); + delete entry; + } + else + serial.printf("%sPhonebook entry added.%s%s",EOLNC,EOLNC,EOLNC); + entry = new PhoneBookEntry(lastNumber,lastAddress.c_str(),lastOptions.c_str(),lastNotes.c_str()); + PhoneBookEntry::savePhonebook(); + currState=ZCFGMENU_MAIN; + } + else + { + ConnSettings flags(lastOptions.c_str()); + switch(c) + { + case 'p': + flags.petscii=!flags.petscii; + break; + case 't': + flags.telnet=!flags.telnet; + break; + case 'e': + flags.echo=!flags.echo; + break; + case 'f': + if(flags.xonxoff) + { + flags.xonxoff=false; + flags.rtscts=true; + } + else + if(flags.rtscts) + flags.rtscts=false; + else + flags.xonxoff=true; + break; + case 's': + flags.secure=!flags.secure; + break; + default: + serial.printf("%sInvalid toggle option '%s'.%s%s",EOLNC,cmd.c_str(),EOLNC,EOLNC); + break; + } + lastOptions = flags.getFlagString(); + } + showMenu=true; // re-show the menu + break; + } + case ZCFGMENU_WIMENU: + { + if(cmd.length()==0) + { + currState=ZCFGMENU_MAIN; + showMenu=true; + } + else + { + int num=atoi(cmd.c_str()); + if((num<=0)||(num>lastNumNetworks)) + serial.printf("%sInvalid number. Try again.%s",EOLNC,EOLNC); + else + if(WiFi.encryptionType(num-1) == ENC_TYPE_NONE) + { + if(!connectWifi(WiFi.SSID(num-1).c_str(),"",null,null,null,null)) + { + serial.printf("%sUnable to connect to %s. :(%s",EOLNC,WiFi.SSID(num-1).c_str(),EOLNC); + } + else + { + wifiSSI=WiFi.SSID(num-1); + wifiPW=""; + settingsChanged=true; + serial.printf("%sConnected!%s",EOLNC,EOLNC); + currState=ZCFGMENU_NETMENU; + } + showMenu=true; + } + else + { + lastNumber=num-1; + currState=ZCFGMENU_WIFIPW; + showMenu=true; + } + } + break; + } + case ZCFGMENU_NEWHOST: + if(cmd.length()==0) + currState=ZCFGMENU_WIMENU; + else + { + hostname=cmd; + hostname.replace(',','.'); + if((wifiSSI.length() > 0) && (WiFi.status()==WL_CONNECTED)) + { + if(!connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN)) + serial.printf("%sUnable to connect to %s. :(%s",EOLNC,wifiSSI.c_str(),EOLNC); + settingsChanged=true; + } + currState=ZCFGMENU_MAIN; + showMenu=true; + } + break; + case ZCFGMENU_NEWPRINT: + if(cmd.length()>0) + { + if(!printMode.testPrinterSpec(cmd.c_str(),cmd.length(),commandMode.serial.isPetsciiMode())) + { + serial.printf("%sBad format. Try ?::/.%s",EOLNC,EOLNC); + serial.printf("? = A)scii P)etscii or R)aw.%s",EOLNC); + serial.printf("Example: R:192.168.1.71:631/ipp/printer%s",EOLNC); + } + else + { + printMode.setLastPrinterSpec(cmd.c_str()); + settingsChanged=true; + } + } + currState=ZCFGMENU_MAIN; + showMenu=true; + break; + case ZCFGMENU_WIFIPW: + if(cmd.length()==0) + { + currState=ZCFGMENU_WIMENU; + showMenu=true; + } + else + { + for(int i=0;i<500;i++) + { + if(serial.isSerialOut()) + serialOutDeque(); + delay(1); + } + if(!connectWifi(WiFi.SSID(lastNumber).c_str(),cmd.c_str(),null,null,null,null)) + serial.printf("%sUnable to connect to %s.%s",EOLNC,WiFi.SSID(lastNumber).c_str(),EOLNC); + else + { + //setNewStaticIPs(null,null,null,null); + useDHCP=(staticIP==null); + lastIP=(staticIP != null)?*staticIP:WiFi.localIP(); + lastDNS=(staticDNS != null)?*staticDNS:IPAddress(192,168,0,1); + lastGW=(staticGW != null)?*staticGW:IPAddress(192,168,0,1); + lastSN=(staticSN != null)?*staticSN:IPAddress(255,255,255,0); + wifiSSI=WiFi.SSID(lastNumber); + wifiPW=cmd; + settingsChanged=true; + currState=ZCFGMENU_NETMENU; + } + showMenu=true; + } + break; + case ZCFGMENU_FLOW: + if(cmd.length()==0) + { + currState=ZCFGMENU_WIMENU; + showMenu=true; + } + else + { + currState=ZCFGMENU_MAIN; + showMenu=true; + if(c=='x') + commandMode.serial.setFlowControlType(FCT_NORMAL); + else + if(c=='r') + commandMode.serial.setFlowControlType(FCT_RTSCTS); + else + if(c=='d') + commandMode.serial.setFlowControlType(FCT_DISABLED); + else + { + serial.printf("%sUnknown flow control type '%s'. Try again.%s",EOLNC,cmd.c_str(),EOLNC); + currState=ZCFGMENU_FLOW; + } + settingsChanged = settingsChanged || (currState ==ZCFGMENU_MAIN); + serial.setFlowControlType(commandMode.serial.getFlowControlType()); + serial.setXON(true); + } + break; + } +} + +void ZConfig::loop() +{ + if(showMenu) + { + showMenu=false; + switch(currState) + { + case ZCFGMENU_MAIN: + { + serial.printf("%sMain Menu%s",EOLNC,EOLNC); + serial.printf("[HOST] name: %s%s",hostname.c_str(),EOLNC); + serial.printf("[WIFI] connection: %s%s",(WiFi.status() == WL_CONNECTED)?wifiSSI.c_str():"Not connected",EOLNC); + String flowName; + switch(commandMode.serial.getFlowControlType()) + { + case FCT_NORMAL: + flowName = "XON/XOFF"; + break; + case FCT_RTSCTS: + flowName = "RTS/CTS"; + break; + case FCT_DISABLED: + flowName = "DISABLED"; + break; + default: + flowName = "OTHER"; + break; + } + String bbsMode = "DISABLED"; + if(newListen) + { + bbsMode = "Port "; + bbsMode += serverSpec.port; + } + serial.printf("[FLOW] control: %s%s",flowName.c_str(),EOLNC); + serial.printf("[ECHO] keystrokes: %s%s",savedEcho?"ON":"OFF",EOLNC); + serial.printf("[BBS] host: %s%s",bbsMode.c_str(),EOLNC); + serial.printf("[PRINT] spec: %s%s",printMode.getLastPrinterSpec(),EOLNC); + serial.printf("[PETSCII] translation: %s%s",commandMode.serial.isPetsciiMode()?"ON":"OFF",EOLNC); + serial.printf("[ADD] new phonebook entry%s",EOLNC); + PhoneBookEntry *p = phonebook; + if(p != null) + { + serial.printf("Phonebook entries:%s",EOLNC); + while(p != null) + { + if(strlen(p->notes)>0) + serial.printf(" [%lu] %s (%s)%s",p->number, p->address, p->notes, EOLNC); + else + serial.printf(" [%lu] %s%s",p->number, p->address, EOLNC); + p=p->next; + } + } + serial.printf("%sEnter command or entry or ENTER to exit: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_NUM: + serial.printf("%sEnter a new fake phone number (digits ONLY)%s: ",EOLNC,EOLNC); + break; + case ZCFGMENU_NEWPORT: + serial.printf("%sEnter a port number to listen on%s: ",EOLNC,EOLNC); + break; + case ZCFGMENU_ADDRESS: + { + PhoneBookEntry *lastEntry = PhoneBookEntry::findPhonebookEntry(lastNumber); + if(lastEntry == null) + serial.printf("%sEnter hostname:port, or user:pass@hostname:port for SSH%s: ",EOLNC,EOLNC); + else + serial.printf("%sModify address, or enter DELETE (%s)%s: ",EOLNC,lastAddress.c_str(),EOLNC); + break; + } + case ZCFGMENU_OPTIONS: + { + ConnSettings flags(lastOptions.c_str()); + serial.printf("%sConnection Options:%s",EOLNC,EOLNC); + serial.printf("[PETSCII] Translation: %s%s",flags.petscii?"ON":"OFF",EOLNC); + serial.printf("[TELNET] Translation: %s%s",flags.telnet?"ON":"OFF",EOLNC); + serial.printf("[ECHO]: %s%s",flags.echo?"ON":"OFF",EOLNC); + serial.printf("[FLOW] Control: %s%s",flags.xonxoff?"XON/XOFF":flags.rtscts?"RTS/CTS":"DISABLED",EOLNC); + serial.printf("[SSH/SSL] Security: %s%s",flags.secure?"ON":"OFF",EOLNC); + serial.printf("%sEnter option to toggle or ENTER to exit%s: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_NOTES: + { + serial.printf("%sEnter some notes for this entry (%s)%s: ",EOLNC,lastNotes.c_str(),EOLNC); + break; + } + case ZCFGMENU_BBSMENU: + { + serial.printf("%sBBS host settings:%s",EOLNC,EOLNC); + if(newListen) + { + ConnSettings flags(serverSpec.flagsBitmap); + serial.printf("%s[HOST] Listener Port: %d%s",EOLNC,serverSpec.port,EOLNC); + serial.printf("[PETSCII] Translation: %s%s",flags.petscii?"ON":"OFF",EOLNC); + serial.printf("[TELNET] Translation: %s%s",flags.telnet?"ON":"OFF",EOLNC); + serial.printf("[ECHO]: %s%s",flags.echo?"ON":"OFF",EOLNC); + serial.printf("[FLOW] Control: %s%s",flags.xonxoff?"XON/XOFF":flags.rtscts?"RTS/CTS":"DISABLED",EOLNC); + serial.printf("[DISABLE] BBS host listener%s",EOLNC); + } + else + serial.printf("%s[ENABLE] BBS host listener%s",EOLNC,EOLNC); + serial.printf("%sEnter option to toggle or ENTER to exit%s: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_SUBNET: + { + String str; + switch(netOpt) + { + case 'i': + { + ConnSettings::IPtoStr(&lastIP,str); + serial.printf("%sIP Address (%s)%s: %s",EOLNC,str.c_str(),EOLNC,EOLNC); + break; + } + case 'g': + { + ConnSettings::IPtoStr(&lastGW,str); + serial.printf("%sGateway Address (%s)%s: %s",EOLNC,str.c_str(),EOLNC,EOLNC); + break; + } + case 's': + { + ConnSettings::IPtoStr(&lastSN,str); + serial.printf("%sSubnet Mask Address (%s)%s: %s",EOLNC,str.c_str(),EOLNC,EOLNC); + break; + } + case 'd': + { + ConnSettings::IPtoStr(&lastDNS,str); + serial.printf("%sDNS Address (%s)%s: %s",EOLNC,str.c_str(),EOLNC,EOLNC); + break; + } + } + break; + } + case ZCFGMENU_NETMENU: + { + serial.printf("%sNetwork settings:%s",EOLNC,EOLNC); + if(!useDHCP) + { + String str; + ConnSettings::IPtoStr(&lastIP,str); + serial.printf("%s[IP]: %s%s",EOLNC,str.c_str(),EOLNC); + ConnSettings::IPtoStr(&lastSN,str); + serial.printf("[SUBNET]: %s%s",str.c_str(),EOLNC); + ConnSettings::IPtoStr(&lastGW,str); + serial.printf("[GATEWAY]: %s%s",str.c_str(),EOLNC); + ConnSettings::IPtoStr(&lastDNS,str); + serial.printf("[DNS]: %s%s",str.c_str(),EOLNC); + serial.printf("[ENABLE] Enable DHCP%s",EOLNC); + } + else + serial.printf("%s[DISABLE] DHCP%s",EOLNC,EOLNC); + serial.printf("%sEnter option to toggle or ENTER to exit%s: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_WIMENU: + { + int n = WiFi.scanNetworks(); + if(n>20) + n=20; + serial.printf("%sWiFi Networks:%s",EOLNC,EOLNC); + lastNumNetworks=n; + for (int i = 0; i < n; ++i) + { + serial.printf("[%d] ",(i+1)); + serial.prints(WiFi.SSID(i).c_str()); + serial.prints(" ("); + serial.printi(WiFi.RSSI(i)); + serial.prints(")"); + serial.prints((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*"); + serial.prints(EOLN.c_str()); + serial.flush(); + delay(10); + } + serial.printf("%sEnter number to connect, or ENTER: ",EOLNC); + break; + } + case ZCFGMENU_NEWHOST: + { + serial.printf("%sEnter a new hostname: ",EOLNC); + break; + } + case ZCFGMENU_NEWPRINT: + { + serial.printf("%sEnter ipp printer spec (?::/path)%s: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_WIFIPW: + { + serial.printf("%sEnter your WiFi Password: ",EOLNC); + break; + } + case ZCFGMENU_FLOW: + { + serial.printf("%sEnter RTS/CTS, XON/XOFF, or DISABLE flow control%s: ",EOLNC,EOLNC); + break; + } + case ZCFGMENU_WICONFIRM: + { + serial.printf("%sYour setting changed. Save (y/N)?",EOLNC); + break; + } + } + } + if(commandMode.checkPlusEscape()) + { + switchBackToCommandMode(); + } + else + if(serial.isSerialOut()) + { + serialOutDeque(); + } +} + diff --git a/src/zhostcmmode.ino b/src/zhostcmmode.ino new file mode 100644 index 0000000..7960ec9 --- /dev/null +++ b/src/zhostcmmode.ino @@ -0,0 +1,48 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_HOSTCM +void ZHostCMMode::switchBackToCommandMode() +{ + if(proto != 0) + delete proto; + proto = 0; + currMode = &commandMode; +} + +void ZHostCMMode::switchTo() +{ + currMode=&hostcmMode; + if(proto == 0) + proto = new HostCM(&SD); +} + +void ZHostCMMode::serialIncoming() +{ + if(proto != 0) + proto->receiveLoop(); +} + +void ZHostCMMode::loop() +{ + serialOutDeque(); + if((proto != 0) && (proto->isAborted())) + switchBackToCommandMode(); +} + +#endif +#endif diff --git a/src/zimodem.ino b/src/zimodem.ino new file mode 100644 index 0000000..35b10d1 --- /dev/null +++ b/src/zimodem.ino @@ -0,0 +1,560 @@ +#include +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +//#define TCP_SND_BUF 4 * TCP_MSS +#define ZIMODEM_VERSION "4.0.0" +const char compile_date[] = __DATE__ " " __TIME__; +#define DEFAULT_NO_DELAY true +#define null 0 +#define INCLUDE_IRCC true +//# define USE_DEVUPDATER true // only enable this if your name is Bo + +#ifdef ARDUINO_ESP32_DEV +# define ZIMODEM_ESP32 +#elif defined(ESP32) +# define ZIMODEM_ESP32 +#elif defined(ARDUINO_ESP320) +# define ZIMODEM_ESP32 +#elif defined(ARDUINO_NANO32) +# define ZIMODEM_ESP32 +#elif defined(ARDUINO_LoLin32) +# define ZIMODEM_ESP32 +#elif defined(ARDUINO_ESPea32) +# define ZIMODEM_ESP32 +#elif defined(ARDUINO_QUANTUM) +# define ZIMODEM_ESP32 +#else +# define ZIMODEM_ESP8266 +#endif + +#ifdef SUPPORT_LED_PINS +# ifdef GPIO_NUM_0 +# define DEFAULT_PIN_AA GPIO_NUM_16 +# define DEFAULT_PIN_HS GPIO_NUM_15 +# define DEFAULT_PIN_WIFI GPIO_NUM_0 +# else +# define DEFAULT_PIN_AA 16 +# define DEFAULT_PIN_HS 15 +# define DEFAULT_PIN_WIFI 0 +# endif +# define DEFAULT_HS_BAUD 38400 +# define DEFAULT_AA_ACTIVE LOW +# define DEFAULT_AA_INACTIVE HIGH +# define DEFAULT_HS_ACTIVE LOW +# define DEFAULT_HS_INACTIVE HIGH +# define DEFAULT_WIFI_ACTIVE LOW +# define DEFAULT_WIFI_INACTIVE HIGH +#endif + +#ifdef ZIMODEM_ESP32 +# define PIN_FACTORY_RESET GPIO_NUM_0 +# define DEFAULT_PIN_DCD GPIO_NUM_14 +# define DEFAULT_PIN_CTS GPIO_NUM_13 +# define DEFAULT_PIN_RTS GPIO_NUM_15 // unused +# define DEFAULT_PIN_RI GPIO_NUM_32 +# define DEFAULT_PIN_DSR GPIO_NUM_12 +# define DEFAULT_PIN_SND GPIO_NUM_25 +# define DEFAULT_PIN_OTH GPIO_NUM_4 +# define DEFAULT_PIN_DTR GPIO_NUM_27 +# define debugPrintf Serial.printf +# define INCLUDE_SD_SHELL true +# define DEFAULT_FCT FCT_DISABLED +# define SerialConfig uint32_t +# define UART_CONFIG_MASK 0x8000000 +# define UART_NB_BIT_MASK 0B00001100 | UART_CONFIG_MASK +# define UART_NB_BIT_5 0B00000000 | UART_CONFIG_MASK +# define UART_NB_BIT_6 0B00000100 | UART_CONFIG_MASK +# define UART_NB_BIT_7 0B00001000 | UART_CONFIG_MASK +# define UART_NB_BIT_8 0B00001100 | UART_CONFIG_MASK +# define UART_PARITY_MASK 0B00000011 +# define UART_PARITY_NONE 0B00000000 +# define UART_NB_STOP_BIT_MASK 0B00110000 +# define UART_NB_STOP_BIT_0 0B00000000 +# define UART_NB_STOP_BIT_1 0B00010000 +# define UART_NB_STOP_BIT_15 0B00100000 +# define UART_NB_STOP_BIT_2 0B00110000 +# define preEOLN serial.prints +# define echoEOLN serial.write +//# define HARD_DCD_HIGH 1 +//# define HARD_DCD_LOW 1 +# define INCLUDE_HOSTCM true // safe to remove if you need space +# define INCLUDE_PING true +# define INCLUDE_SSH true +#else // ESP-8266, e.g. ESP-01, ESP-12E, inverted for C64Net WiFi Modem +# define DEFAULT_PIN_DSR 13 +# define DEFAULT_PIN_DTR 12 +# define DEFAULT_PIN_RI 14 +# define DEFAULT_PIN_RTS 4 +# define DEFAULT_PIN_CTS 5 // is 0 for ESP-01, see getDefaultCtsPin() below. +# define DEFAULT_PIN_DCD 2 +# define DEFAULT_FCT FCT_DISABLED +# define RS232_INVERTED 1 +# define debugPrintf doNothing +# define preEOLN(...) +# define echoEOLN(...) serial.prints(EOLN) +#endif +#define INCLUDE_SLIP true + +#ifdef RS232_INVERTED +# define DEFAULT_DCD_HIGH HIGH +# define DEFAULT_DCD_LOW LOW +# define DEFAULT_CTS_HIGH HIGH +# define DEFAULT_CTS_LOW LOW +# define DEFAULT_RTS_HIGH HIGH +# define DEFAULT_RTS_LOW LOW +# define DEFAULT_RI_HIGH HIGH +# define DEFAULT_RI_LOW LOW +# define DEFAULT_DSR_HIGH HIGH +# define DEFAULT_DSR_LOW LOW +# define DEFAULT_DTR_HIGH HIGH +# define DEFAULT_DTR_LOW LOW +#else +# define DEFAULT_DCD_HIGH LOW +# define DEFAULT_DCD_LOW HIGH +# define DEFAULT_CTS_HIGH LOW +# define DEFAULT_CTS_LOW HIGH +# define DEFAULT_RTS_HIGH LOW +# define DEFAULT_RTS_LOW HIGH +# define DEFAULT_RI_HIGH LOW +# define DEFAULT_RI_LOW HIGH +# define DEFAULT_DSR_HIGH LOW +# define DEFAULT_DSR_LOW HIGH +# define DEFAULT_DTR_HIGH LOW +# define DEFAULT_DTR_LOW HIGH +#endif + +#define DEFAULT_BAUD_RATE 1200 +#define DEFAULT_SERIAL_CONFIG SERIAL_8N1 +#define MAX_PIN_NO 50 +#define INTERNAL_FLOW_CONTROL_DIV 380 +#define DEFAULT_RECONNECT_DELAY 60000 +#define MAX_RECONNECT_DELAY 1800000 + +class ZMode +{ + public: + virtual void serialIncoming(); + virtual void loop(); +}; + +#include "pet2asc.h" +#include "rt_clock.h" +#include "filelog.h" +#include "serout.h" +#include "connSettings.h" +#include "wificlientnode.h" +#include "stringstream.h" +#include "phonebook.h" +#include "wifiservernode.h" +#include "zstream.h" +#include "proto_http.h" +#include "proto_ftp.h" +#include "zconfigmode.h" +#include "zcommand.h" +#include "zprint.h" + +#ifdef INCLUDE_SD_SHELL +# ifdef INCLUDE_HOSTCM +# include "zhostcmmode.h" +# endif +# include "proto_xmodem.h" +# include "proto_zmodem.h" +# include "proto_kermit.h" +# include "zbrowser.h" +#endif +#ifdef INCLUDE_SLIP +# include "zslipmode.h" +#endif +#ifdef INCLUDE_IRCC +# include "zircmode.h" +#endif + +static WiFiClientNode *conns = null; +static WiFiServerNode *servs = null; +static PhoneBookEntry *phonebook = null; +static bool pinSupport[MAX_PIN_NO]; +static String termType = DEFAULT_TERMTYPE; +static String busyMsg = DEFAULT_BUSYMSG; + +static ZMode *currMode = null; +static ZStream streamMode; +//static ZSlip slipMode; // not yet implemented +static ZCommand commandMode; +static ZPrint printMode; +static ZConfig configMode; +static RealTimeClock zclock(0); +#ifdef INCLUDE_SD_SHELL +# ifdef INCLUDE_HOSTCM + static ZHostCMMode hostcmMode; +# endif + static ZBrowser browseMode; +#endif +#ifdef INCLUDE_SLIP + static ZSLIPMode slipMode; +#endif +#ifdef INCLUDE_IRCC + static ZIRCMode ircMode; +#endif + +enum BaudState +{ + BS_NORMAL, + BS_SWITCH_TEMP_NEXT, + BS_SWITCHED_TEMP, + BS_SWITCH_NORMAL_NEXT +}; + +static String wifiSSI; +static String wifiPW; +static String hostname; +static IPAddress *staticIP = null; +static IPAddress *staticDNS = null; +static IPAddress *staticGW = null; +static IPAddress *staticSN = null; +static unsigned long lastConnectAttempt = 0; +static unsigned long nextReconnectDelay = 0; // zero means don't attempt reconnects +static SerialConfig serialConfig = DEFAULT_SERIAL_CONFIG; +static int baudRate=DEFAULT_BAUD_RATE; +static int dequeSize=1+(DEFAULT_BAUD_RATE/INTERNAL_FLOW_CONTROL_DIV); +static BaudState baudState = BS_NORMAL; +static unsigned long resetPushTimer=0; +static int tempBaud = -1; // -1 do nothing +static int dcdStatus = DEFAULT_DCD_LOW; +static int pinDCD = DEFAULT_PIN_DCD; +static int pinCTS = DEFAULT_PIN_CTS; +static int pinRTS = DEFAULT_PIN_RTS; +static int pinDSR = DEFAULT_PIN_DSR; +static int pinDTR = DEFAULT_PIN_DTR; +static int pinRI = DEFAULT_PIN_RI; +static int dcdActive = DEFAULT_DCD_HIGH; +static int dcdInactive = DEFAULT_DCD_LOW; +static int ctsActive = DEFAULT_CTS_HIGH; +static int ctsInactive = DEFAULT_CTS_LOW; +static int rtsActive = DEFAULT_RTS_HIGH; +static int rtsInactive = DEFAULT_RTS_LOW; +static int riActive = DEFAULT_RI_HIGH; +static int riInactive = DEFAULT_RI_LOW; +static int dtrActive = DEFAULT_DTR_HIGH; +static int dtrInactive = DEFAULT_DTR_LOW; +static int dsrActive = DEFAULT_DSR_HIGH; +static int dsrInactive = DEFAULT_DSR_LOW; + +static int getDefaultCtsPin() +{ +#ifdef ZIMODEM_ESP32 + return DEFAULT_PIN_CTS; +#else + if((ESP.getFlashChipRealSize()/1024)>=4096) // assume this is a striketerm/esp12e + return DEFAULT_PIN_CTS; + else + return 0; +#endif +} + +static void doNothing(const char* format, ...) +{ +} + +static void s_pinWrite(uint8_t pinNo, uint8_t value) +{ + if(pinSupport[pinNo]) + { + digitalWrite(pinNo, value); + } +} + +static void setHostName(const char *hname) +{ +#ifdef ZIMODEM_ESP32 + tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, hname); +#else + WiFi.hostname(hname); +#endif +} + +static void setNewStaticIPs(IPAddress *ip, IPAddress *dns, IPAddress *gateWay, IPAddress *subNet) +{ + if(staticIP != null) + free(staticIP); + staticIP = ip; + if(staticDNS != null) + free(staticDNS); + staticDNS = dns; + if(staticGW != null) + free(staticGW); + staticGW = gateWay; + if(staticSN != null) + free(staticSN); + staticSN = subNet; +} + +static bool connectWifi(const char* ssid, const char* password, IPAddress *ip, IPAddress *dns, IPAddress *gateWay, IPAddress *subNet) +{ + while(WiFi.status() == WL_CONNECTED) + { + WiFi.disconnect(); + delay(100); + yield(); + } +#ifndef ZIMODEM_ESP32 + if(hostname.length() > 0) + setHostName(hostname.c_str()); +#endif + WiFi.mode(WIFI_STA); + if((ip != null)&&(gateWay != null)&&(dns != null)&&(subNet!=null)) + { + if(!WiFi.config(*ip,*gateWay,*subNet,*dns)) + return false; + } + WiFi.begin(ssid, password); + if(hostname.length() > 0) + setHostName(hostname.c_str()); + bool amConnected = (WiFi.status() == WL_CONNECTED) && (strcmp(WiFi.localIP().toString().c_str(), "0.0.0.0")!=0); + int WiFiCounter = 0; + while ((!amConnected) && (WiFiCounter < 20)) + { + WiFiCounter++; + if(!amConnected) + delay(500); + amConnected = (WiFi.status() == WL_CONNECTED) && (strcmp(WiFi.localIP().toString().c_str(), "0.0.0.0")!=0); + } + lastConnectAttempt = millis(); + if(lastConnectAttempt == 0) // it IS possible for millis() to be 0, but we need to ignore it. + lastConnectAttempt = 1; // 0 is a special case, so skip it + + if(!amConnected) + { + nextReconnectDelay = 0; // assume no retry is desired.. let the caller set it up, as it could be bad PW + WiFi.disconnect(); + } + else + nextReconnectDelay = DEFAULT_RECONNECT_DELAY; // if connected, we always want to try reconns in the future + +#ifdef SUPPORT_LED_PINS + s_pinWrite(DEFAULT_PIN_WIFI,(WiFi.status() == WL_CONNECTED)?DEFAULT_WIFI_ACTIVE:DEFAULT_WIFI_INACTIVE); +#endif + return (WiFi.status() == WL_CONNECTED); +} + +static void checkBaudChange() +{ + switch(baudState) + { + case BS_SWITCH_TEMP_NEXT: + changeBaudRate(tempBaud); + baudState = BS_SWITCHED_TEMP; + break; + case BS_SWITCH_NORMAL_NEXT: + changeBaudRate(baudRate); + baudState = BS_NORMAL; + break; + default: + break; + } +} + +static void changeBaudRate(int baudRate) +{ + flushSerial(); // blocking, but very very necessary + delay(500); // give the client half a sec to catch up + logPrintfln("Baud change to %d.\n",baudRate); + debugPrintf("Baud change to %d.\n",baudRate); + dequeSize=1+(baudRate/INTERNAL_FLOW_CONTROL_DIV); + debugPrintf("Deque constant now: %d\n",dequeSize); +#ifdef ZIMODEM_ESP32 + HWSerial.updateBaudRate(baudRate); +#else + HWSerial.begin(baudRate, serialConfig); //Change baud rate +#endif +#ifdef SUPPORT_LED_PINS + s_pinWrite(DEFAULT_PIN_HS,(baudRate>=DEFAULT_HS_BAUD)?DEFAULT_HS_ACTIVE:DEFAULT_HS_INACTIVE); +#endif +} + +static void changeSerialConfig(SerialConfig conf) +{ + flushSerial(); // blocking, but very very necessary + delay(500); // give the client half a sec to catch up + debugPrintf("Config changing to %d.\n",(int)conf); + dequeSize=1+(baudRate/INTERNAL_FLOW_CONTROL_DIV); + debugPrintf("Deque constant now: %d\n",dequeSize); + HWSerial.begin(baudRate, conf); //Change baud rate + debugPrintf("Config changed.\n"); +} + +static int checkOpenConnections() +{ + int num=WiFiClientNode::getNumOpenWiFiConnections(); + if(num == 0) + { + if((dcdStatus == dcdActive) + &&(dcdStatus != dcdInactive)) + { + logPrintfln("DCD going inactive.\n"); + dcdStatus = dcdInactive; + s_pinWrite(pinDCD,dcdStatus); + if(baudState == BS_SWITCHED_TEMP) + baudState = BS_SWITCH_NORMAL_NEXT; + if(currMode == &commandMode) + clearSerialOutBuffer(); + } + } + else + { + if((dcdStatus == dcdInactive) + &&(dcdStatus != dcdActive)) + { + logPrintfln("DCD going active.\n"); + dcdStatus = dcdActive; + s_pinWrite(pinDCD,dcdStatus); + if((tempBaud > 0) && (baudState == BS_NORMAL)) + baudState = BS_SWITCH_TEMP_NEXT; + } + } + return num; +} + +void setup() +{ + for(int i=0;i=4096) // assume this is a strykelink/esp12e + { + pinSupport[4]=true; + pinSupport[5]=true; + for(int i=9;i<=16;i++) + pinSupport[i]=true; + pinSupport[11]=false; + } +#endif + initSDShell(); + currMode = &commandMode; + if(!SPIFFS.begin()) + { + SPIFFS.format(); + SPIFFS.begin(); + debugPrintf("SPIFFS Formatted."); + } + HWSerial.begin(DEFAULT_BAUD_RATE, DEFAULT_SERIAL_CONFIG); //Start Serial +#ifdef ZIMODEM_ESP8266 + HWSerial.setRxBufferSize(1024); +#endif + commandMode.loadConfig(); + PhoneBookEntry::loadPhonebook(); + dcdStatus = dcdInactive; + s_pinWrite(pinDCD,dcdStatus); + flushSerial(); +#ifdef SUPPORT_LED_PINS + s_pinWrite(DEFAULT_PIN_WIFI,(WiFi.status() == WL_CONNECTED)?DEFAULT_WIFI_ACTIVE:DEFAULT_WIFI_INACTIVE); + s_pinWrite(DEFAULT_PIN_HS,(baudRate>=DEFAULT_HS_BAUD)?DEFAULT_HS_ACTIVE:DEFAULT_HS_INACTIVE); +#endif +} + +void checkReconnect() +{ + if((WiFi.status() != WL_CONNECTED) + &&(nextReconnectDelay>0) + &&(lastConnectAttempt>0) + &&(wifiSSI.length()>0)) + { + unsigned long now=millis(); + if(lastConnectAttempt > now) + lastConnectAttempt=1; + if(now > lastConnectAttempt + nextReconnectDelay) + { + debugPrintf("Attempting Reconnect to %s\n",wifiSSI.c_str()); + unsigned long oldReconnectDelay = nextReconnectDelay; + if(!connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN)) + debugPrintf("%sUnable to reconnect to %s.\n",wifiSSI.c_str()); + nextReconnectDelay = oldReconnectDelay * 2; + if(nextReconnectDelay > MAX_RECONNECT_DELAY) + nextReconnectDelay = DEFAULT_RECONNECT_DELAY; + } + } +} + +void checkFactoryReset() +{ +#ifdef ZIMODEM_ESP32 + if(!digitalRead(PIN_FACTORY_RESET)) + { + if(resetPushTimer != 1) + { + if(resetPushTimer==0) + { + resetPushTimer=millis(); + if(resetPushTimer==1) + resetPushTimer++; + } + else + if((millis() - resetPushTimer) > 5000) + { + SPIFFS.remove(CONFIG_FILE); + SPIFFS.remove(CONFIG_FILE_OLD); + SPIFFS.remove("/zphonebook.txt"); + SPIFFS.remove("/zlisteners.txt"); + PhoneBookEntry::clearPhonebook(); + SPIFFS.end(); + SPIFFS.format(); + SPIFFS.begin(); + PhoneBookEntry::clearPhonebook(); + if(WiFi.status() == WL_CONNECTED) + WiFi.disconnect(); + baudRate = DEFAULT_BAUD_RATE; + commandMode.loadConfig(); + PhoneBookEntry::loadPhonebook(); + dcdStatus = dcdInactive; + s_pinWrite(pinDCD,dcdStatus); + wifiSSI=""; + delay(500); + zclock.reset(); + commandMode.reset(); + resetPushTimer=1; + } + } + } + else + if(resetPushTimer != 0) + resetPushTimer=0; +#endif +} + +void loop() +{ + checkFactoryReset(); + checkReconnect(); + if(HWSerial.available()) + { + currMode->serialIncoming(); + } + currMode->loop(); + zclock.tick(); +} diff --git a/src/zircmode.ino b/src/zircmode.ino new file mode 100644 index 0000000..b7ed4b0 --- /dev/null +++ b/src/zircmode.ino @@ -0,0 +1,539 @@ +#include +/* + * zircmode.ino + * + * Created on: May 18, 2022 + * Author: Bo Zimmerman + */ +#ifdef INCLUDE_IRCC +//https://github.com/bl4de/irc-client/blob/master/irc_client.py +void ZIRCMode::switchBackToCommandMode() +{ + serial.println("Back in command mode."); + if(current != null) + { + delete current; + current = null; + } + currMode = &commandMode; +} + +void ZIRCMode::switchTo() +{ + currMode=&ircMode; + savedEcho=commandMode.doEcho; + commandMode.doEcho=true; + serial.setFlowControlType(commandMode.serial.getFlowControlType()); + serial.setPetsciiMode(commandMode.serial.isPetsciiMode()); + showMenu=true; + EOLN=commandMode.EOLN; + EOLNC=EOLN.c_str(); + currState=ZIRCMENU_MAIN; + lastNumber=0; + lastAddress=""; + lastOptions=""; + channelName=""; + joinReceived=false; + if(nick.length()==0) + { + randomSeed(millis()); + char tempNick[50]; + sprintf(tempNick,"ChangeMe#%ld",random(1,999)); + nick = tempNick; + } +} + +void ZIRCMode::doIRCCommand() +{ + String cmd = commandMode.getNextSerialCommand(); + char c='?'; + while((cmd.length()>0) + &&(cmd[0]==32)||(cmd[0]==7)) + cmd = cmd.substring(1); + if(cmd.length()>0) + c=lc(cmd[0]); + switch(currState) + { + case ZIRCMENU_MAIN: + { + if((c=='q')||(cmd.length()==0)) + { + commandMode.showInitMessage(); + switchBackToCommandMode(); + return; + } + else + if(c=='a') // add to phonebook + { + currState=ZIRCMENU_NUM; + showMenu=true; + } + else + if(c=='n') // change nick + { + currState=ZIRCMENU_NICK; + showMenu=true; + } + else + if(c>47 && c<58) // its a phonebook entry! + { + PhoneBookEntry *pb=PhoneBookEntry::findPhonebookEntry(cmd); + if(pb == null) + { + serial.printf("%s%sPhone number not found: '%s'.%s%s",EOLNC,EOLNC,cmd.c_str(),EOLNC,EOLNC); + currState=ZIRCMENU_MAIN; + showMenu=true; + } + else + { + serial.printf("%s%sConnecting to %s...%s%s",EOLNC,EOLNC,pb->address,EOLNC,EOLNC); + String address = pb->address; + char vbuf[address.length()+1]; + strcpy(vbuf,pb->address); + char *colon=strstr((char *)vbuf,":"); + int port=6666; + if(colon != null) + { + (*colon)=0; + port=atoi((char *)(++colon)); + } + int flagsBitmap=0; + { + ConnSettings flags(""); + flagsBitmap = flags.getBitmap(serial.getFlowControlType()); + } + logPrintfln("Connnecting: %s %d %d",(char *)vbuf,port,flagsBitmap); + WiFiClientNode *c = new WiFiClientNode((char *)vbuf,port,flagsBitmap); + if(!c->isConnected()) + { + logPrintln("Connnect: FAIL"); + serial.prints("Connection failed.\n\r"); //TODO: maybe get rid of? + currState=ZIRCMENU_MAIN; + showMenu=true; + } + else + { + logPrintfln("Connnect: SUCCESS: %d",c->id); + serial.prints("Connected.\n\r"); //TODO: maybe get rid of? + current=c; + buf = ""; + currState=ZIRCMENU_COMMAND; + ircState=ZIRCSTATE_WAIT; + timeout=millis()+6000; + showMenu=true; // only wait to get it to act + } + } + } + else + { + showMenu=true; // re-show the menu + } + break; + } + case ZIRCMENU_NUM: + { + PhoneBookEntry *pb=PhoneBookEntry::findPhonebookEntry(cmd); + if(pb != null) + { + serial.printf("%s%sNumber already exists '%s'.%s%s",EOLNC,EOLNC,cmd.c_str(),EOLNC,EOLNC); + currState=ZIRCMENU_MAIN; + showMenu=true; + } + else + if(cmd.length()==0) + { + currState=ZIRCMENU_MAIN; + showMenu=true; + } + else + { + lastNumber = atol((char *)cmd.c_str()); + lastAddress = ""; + ConnSettings flags(commandMode.getConfigFlagBitmap()); + lastOptions = flags.getFlagString(); + lastNotes = ""; + currState=ZIRCMENU_ADDRESS; + showMenu=true; + } + break; + } + case ZIRCMENU_ADDRESS: + { + PhoneBookEntry *entry = PhoneBookEntry::findPhonebookEntry(lastNumber); + if(cmd.equalsIgnoreCase("delete") && (entry != null)) + { + delete entry; + currState=ZIRCMENU_MAIN; + serial.printf("%sPhonebook entry deleted.%s%s",EOLNC,EOLNC,EOLNC); + } + else + if((cmd.length()==0) && (entry != null)) + currState=ZIRCMENU_NOTES; // just keep old values + else + { + boolean fail = cmd.indexOf(',') >= 0; + int colonDex=cmd.indexOf(':'); + fail = fail || (colonDex <= 0) || (colonDex == cmd.length()-1); + fail = fail || (colonDex != cmd.lastIndexOf(':')); + if(!fail) + { + for(int i=colonDex+1;i0) + lastNotes=cmd; + currState=ZIRCMENU_OPTIONS; + showMenu=true; // re-show the menu + break; + } + case ZIRCMENU_NICK: + { + if(cmd.length()>0) + nick=cmd; + currState=ZIRCMENU_MAIN; + showMenu=true; // re-show the menu + break; + } + case ZIRCMENU_OPTIONS: + { + if(cmd.length()==0) + { + serial.printf("%sPhonebook entry added.%s%s",EOLNC,EOLNC,EOLNC); + PhoneBookEntry *entry = new PhoneBookEntry(lastNumber,lastAddress.c_str(),lastOptions.c_str(),lastNotes.c_str()); + PhoneBookEntry::savePhonebook(); + currState=ZIRCMENU_MAIN; + } + else + { + ConnSettings flags(lastOptions.c_str()); + switch(c) + { + case 'p': + flags.petscii=!flags.petscii; + break; + case 't': + flags.telnet=!flags.telnet; + break; + case 'e': + flags.echo=!flags.echo; + break; + case 'f': + if(flags.xonxoff) + { + flags.xonxoff=false; + flags.rtscts=true; + } + else + if(flags.rtscts) + flags.rtscts=false; + else + flags.xonxoff=true; + break; + case 's': + flags.secure=!flags.secure; + break; + default: + serial.printf("%sInvalid toggle option '%s'.%s%s",EOLNC,cmd.c_str(),EOLNC,EOLNC); + break; + } + lastOptions = flags.getFlagString(); + } + showMenu=true; // re-show the menu + break; + } + case ZIRCMENU_COMMAND: + { + if(c=='/') + { + String lccmd = cmd; + lccmd.toLowerCase(); + if(lccmd.startsWith("/join ")) + { + int cs=5; + while((cmd.length()>cs) + &&((cmd[cs]==' ')||(cmd[cs]==7))) + cs++; + if(cs < cmd.length()) + { + if(channelName.length()>0 && joinReceived) + { + serial.println("* Already in "+channelName+": Not Yet Implemented"); + // we are already joined somewhere + } + else + { + channelName = cmd.substring(cs); + if(current != null) + current->print("JOIN :"+channelName+"\r\n"); + } + } + else + serial.println("* A channel name is required *"); + } + else + if(lccmd.startsWith("/quit")) + { + if(current != null) + { + current->print("QUIT Good bye!\r\n"); + current->flush(); + delay(1000); + current->markForDisconnect(); + delete current; + current = null; + } + switchBackToCommandMode(); + } + else + { + serial.println("* Unknown command: "+lccmd); + serial.println("* Try /?"); + } + } + else + if((current != null) + &&(joinReceived)) + current->printf("PRIVMSG %s :%s\r\n",channelName.c_str(),cmd.c_str()); + break; + } + } +} + +void ZIRCMode::loopMenuMode() +{ + if(showMenu) + { + showMenu=false; + switch(currState) + { + case ZIRCMENU_MAIN: + { + if(nick.length()==0) + nick = hostname; + serial.printf("%sInternet Relay Chat (IRC) Main Menu%s",EOLNC,EOLNC); + serial.printf("[NICK] name: %s%s",nick.c_str(),EOLNC); + serial.printf("[ADD] new phonebook entry%s",EOLNC); + PhoneBookEntry *p = phonebook; + if(p != null) + { + serial.printf("Phonebook entries:%s",EOLNC); + while(p != null) + { + if(strlen(p->notes)>0) + serial.printf(" [%lu] %s (%s)%s",p->number, p->address, p->notes, EOLNC); + else + serial.printf(" [%lu] %s%s",p->number, p->address, EOLNC); + p=p->next; + } + } + serial.printf("%sEnter command, number to connect to, or ENTER to exit: ",EOLNC,EOLNC); + break; + } + case ZIRCMENU_NUM: + serial.printf("%sEnter a new fake phone number (digits ONLY)%s: ",EOLNC,EOLNC); + break; + case ZIRCMENU_ADDRESS: + { + serial.printf("%sEnter a new hostname:port%s: ",EOLNC,EOLNC); + break; + } + case ZIRCMENU_NICK: + { + serial.printf("%sEnter a new nick-name%s: ",EOLNC,EOLNC); + break; + } + case ZIRCMENU_OPTIONS: + { + ConnSettings flags(lastOptions.c_str()); + serial.printf("%sConnection Options:%s",EOLNC,EOLNC); + serial.printf("[PETSCII] Translation: %s%s",flags.petscii?"ON":"OFF",EOLNC); + serial.printf("[TELNET] Translation: %s%s",flags.telnet?"ON":"OFF",EOLNC); + serial.printf("[ECHO]: %s%s",flags.echo?"ON":"OFF",EOLNC); + serial.printf("[FLOW] Control: %s%s",flags.xonxoff?"XON/XOFF":flags.rtscts?"RTS/CTS":"DISABLED",EOLNC); + serial.printf("%sEnter option to toggle or ENTER to exit%s: ",EOLNC,EOLNC); + break; + } + case ZIRCMENU_NOTES: + { + serial.printf("%sEnter some notes for this entry (%s)%s: ",EOLNC,lastNotes.c_str(),EOLNC); + break; + } + case ZIRCMENU_COMMAND: + { + showMenu=true; // keep coming back here, over and over and over + if((current==null)||(!current->isConnected())) + switchBackToCommandMode(); + else + { + String cmd; + while((current->available()>0) && (current->isConnected())) + { + uint8_t c = current->read(); + if((c == '\r')||(c == '\n')||(buf.length()>510)) + { + if((c=='\r')||(c=='\n')) + { + cmd=buf; + buf=""; + break; + } + buf=""; + } + else + buf += (char)c; + } + if(cmd.length()>0) + { + if(cmd.indexOf("PING :")==0) + { + int x = cmd.indexOf(':'); + if(x>0) + current->print("PONG "+cmd.substring(x+1)+"\r\n"); + } + else + switch(ircState) + { + case ZIRCSTATE_WAIT: + { + if(cmd.indexOf("376")>=0) + { + ircState = ZIRCSTATE_COMMAND; + //TODO: say something? + } + else + if(cmd.indexOf("No Ident response")>=0) + { + current->print("NICK "+nick+"\r\n"); + current->print("USER guest 0 * :"+nick+"\r\n"); + } + else + if(cmd.indexOf("433")>=0) + { + if(nick.indexOf("_____")==0) + { + ircState = ZIRCSTATE_WAIT; + delete current; + current = null; + currState = ZIRCMENU_MAIN; + } + else + { + nick = "_" + nick; + current->print("NICK "+nick+"\r\n"); + current->print("USER guest 0 * :"+nick+"\r\n"); + // above was user nick * * : nick + } + } + else + if(cmd.indexOf(":")==0) + { + int x = cmd.indexOf(":",1); + if(x>1) + { + serial.prints(cmd.substring(x+1)); + serial.prints(EOLNC); + } + } + break; + } + case ZIRCSTATE_COMMAND: + { + if((!joinReceived) + && (channelName.length()>0) + && (cmd.indexOf("366")>=0)) + { + joinReceived=true; + serial.prints("Channel joined. Enter a message to send, or /quit."); + serial.prints(EOLNC); + } + int x0 = cmd.indexOf(":"); + int x1 = (x0>=0)?cmd.indexOf(" PRIVMSG ", x0+1):-1; + x1 = (x1>0)?cmd.indexOf(":", x1+1):(x0>=0)?cmd.indexOf(":", x0+1):-1; + if(x1>0) + { + String msg2=cmd.substring(x1+1); + msg2.trim(); + String msg1=cmd.substring(x0+1,x1); + msg1.trim(); + int x2=msg1.indexOf("!"); + if(x2>=0) + serial.print("< "+msg1.substring(0,x2)+"> "+msg2); + else + serial.prints(msg2); + serial.prints(EOLNC); + } + break; + } + default: + serial.prints("unknown state\n\r"); + switchBackToCommandMode(); + break; + } + } + else + if(ircState == ZIRCSTATE_WAIT) + { + if(millis()>timeout) + { + timeout = millis()+60000; + current->print("NICK "+nick+"\r\n"); + current->print("USER guest 0 * :"+nick+"\r\n"); + } + } + } + break; + } + } + } + if(commandMode.checkPlusEscape()) + { + switchBackToCommandMode(); + } + else + if(serial.isSerialOut()) + { + serialOutDeque(); + } +} + +void ZIRCMode::serialIncoming() +{ + bool crReceived=commandMode.readSerialStream(); + commandMode.clearPlusProgress(); // re-check the plus-escape mode + if(crReceived) + { + doIRCCommand(); + } +} + +void ZIRCMode::loop() +{ + loopMenuMode(); + if(commandMode.checkPlusEscape()) + { + switchBackToCommandMode(); + } + else + if(serial.isSerialOut()) + { + serialOutDeque(); + } +} + +#endif /* INCLUDE_IRCC */ diff --git a/src/zprint.ino b/src/zprint.ino new file mode 100644 index 0000000..3989497 --- /dev/null +++ b/src/zprint.ino @@ -0,0 +1,417 @@ +#include +/* + Copyright 2020-2020 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +size_t ZPrint::writeStr(char *s) +{ + if(outStream != null) + { + int len=strlen(s); + outStream->write((uint8_t *)s,len); + if(logFileOpen) + { + for(int i=0;i 50) + timeoutDelayMs = ms; +} + +size_t ZPrint::writeChunk(char *s, int len) +{ + char buf[25]; + sprintf(buf,"%x\r\n",len); + writeStr(buf); + outStream->write((uint8_t *)s,len); + if(logFileOpen) + { + for(int i=0;isetNoDelay(false); // we want a delay in this case + outStream = wifiSock; + result = finishSwitchTo(hostIp, req, port, doSSL); + if(result == ZERROR) + { + outStream = null; + delete wifiSock; + wifiSock = null; + } + yield(); + if(pinSupport[pinRTS]) + s_pinWrite(pinRTS, rtsActive); + if((result != ZERROR) + &&(prefix != 0) + &&(strlen(prefix)>0)) + { + writeChunk(prefix,strlen(prefix)); + serialIncoming(); + } + free(workBuf); + return result; +} + +bool ZPrint::testPrinterSpec(const char *vbuf, int vlen, bool petscii) +{ + char *workBuf = (char *)malloc(vlen+1); + strcpy(workBuf, vbuf); + if(petscii) + { + for(int i=0;isetNoDelay(false); // we want a delay in this case + outStream = wifiSock; + announcePrintJob(hostIp,port,req); + ZResult result = finishSwitchTo(hostIp, req, port, doSSL); + free(workBuf); + if(result == ZERROR) + delete wifiSock; + return result; +} + +ZResult ZPrint::finishSwitchTo(char *hostIp, char *req, int port, bool doSSL) +{ + if((wifiSock != null) && (!wifiSock->isConnected())) + return ZERROR; + char portStr[10]; + sprintf(portStr,"%d",port); + // send the request and http headers: + sprintf(pbuf,"POST /%s HTTP/1.1\r\n",req); + writeStr(pbuf); + writeStr("Transfer-Encoding: chunked\r\n"); + writeStr("Content-Type: application/ipp\r\n"); + sprintf(pbuf,"Host: %s:%d\r\n",hostIp,port); + writeStr(pbuf); + writeStr("Connection: Keep-Alive\r\n"); + writeStr("User-Agent: Zimodem\r\n"); + writeStr("Accept-Encoding: gzip,deflate\r\n"); + writeStr("\r\n"); + outStream->flush(); + // send the ipp header + if((wifiSock != null)&&(!wifiSock->isConnected())) + return ZERROR; + + char jobChar1 = '0' + (jobNum / 10); + char jobChar2 = '0' + (jobNum % 10); + if(++jobNum>94) + jobNum=0; + // version operatid reqid------------------ attribtabid + sprintf(pbuf,"%c%c%c%c%c%c%c%c%c",0x01,0x01,0x00,0x02,0x00,0x00,0x00,jobNum+1,0x01); + writeChunk(pbuf,9); + sprintf(pbuf,"%c%c%cattributes-charset%c%cutf-8",0x47,0x00,0x12,0x00,0x05); + writeChunk(pbuf,28); + sprintf(pbuf,"%c%c%cattributes-natural-language%c%cen-us",0x48,0x00,0x1b,0x00,0x05); + writeChunk(pbuf,37); + + int urllen = strlen(hostIp) + strlen(req)+ strlen(portStr)+9; + sprintf(pbuf,"%c%c%cprinter-uri%c%chttp://%s:%s/%s",0x45,0x00,0x0b,0x00,urllen,hostIp,portStr,req); + writeChunk(pbuf,urllen+16); + sprintf(pbuf,"%c%c%crequesting-user-name%c%czimodem",0x42,0x00,0x14,0x00,0x07); + writeChunk(pbuf,32); + sprintf(pbuf,"%c%c%cjob-name%c%czimodem-j%c%c",0x42,0x00,0x08,0x00,0x0b,jobChar1,jobChar2); + writeChunk(pbuf,24); + sprintf(pbuf,"%c%c%c%ccopies%c%c%c%c%c%c",0x02,0x21,0x00,0x06,0x00,0x04,0x00,0x00,0x00,0x01); + writeChunk(pbuf,16); + sprintf(pbuf,"%c%c%corientation-requested%c%c%c%c%c%c",0x23,0x00,0x15,0x00,0x04,0x00,0x00,0x00,0x03); + writeChunk(pbuf,30); + sprintf(pbuf,"%c%c%coutput-mode%c%cmonochrome%c",0x44,0x00,0x0b,0x00,0x0a, 0x03); + writeChunk(pbuf,27); + outStream->flush(); + if((wifiSock != null)&&(!wifiSock->isConnected())) + return ZERROR; + checkOpenConnections(); + checkBaudChange(); + pdex=0; + coldex=0; + lastNonPlusTimeMs = 0; + plussesInARow=0; + currentExpiresTimeMs = millis()+5000; + currMode=&printMode; + return ZIGNORE; +} + +void ZPrint::serialIncoming() +{ + if(HWSerial.available() > 0) + { + while(HWSerial.available() > 0) + { + uint8_t c=HWSerial.read(); + logSerialIn(c); + if((c==commandMode.EC) + &&(plussesInARow<3) + &&((plussesInARow>0)||((millis()-lastNonPlusTimeMs)>900))) + { + plussesInARow++; + continue; + } + else + { + if(plussesInARow > 0) + { + for(int i=0;i 80) + { + pbuf[pdex++]='\n'; + pbuf[pdex++]='\r'; + coldex=1; + } + else + if((lastC == '\n')&&(lastLastC!='\r')) + pbuf[pdex++]='\r'; + else + if((lastC == '\r')&&(lastLastC!='\n')) + pbuf[pdex++]='\n'; + } + } + pbuf[pdex++]=(char)c; + lastLastC=lastC; + lastC=c; + if(pdex>=250) + { + if(((wifiSock!=null)&&(wifiSock->isConnected())) + ||((wifiSock==null)&&(outStream!=null))) + { + writeChunk(pbuf,pdex); + //wifiSock->flush(); + } + pdex=0; + } + } + if(plussesInARow == 3) + currentExpiresTimeMs = millis()+900; + else + currentExpiresTimeMs = millis()+timeoutDelayMs; + } +} + +void ZPrint::switchBackToCommandMode(bool error) +{ + if((wifiSock != null)||(outStream!=null)) + { + if(error) + commandMode.sendOfficialResponse(ZERROR); + else + commandMode.sendOfficialResponse(ZOK); + if(wifiSock != null) + delete wifiSock; + } + wifiSock = null; + outStream = null; + currMode = &commandMode; +} + +void ZPrint::loop() +{ + if(((wifiSock==null)&&(outStream==null)) + || ((wifiSock!=null)&&(!wifiSock->isConnected()))) + { + debugPrintf("No printer connection\n"); + switchBackToCommandMode(true); + } + else + if(millis()>currentExpiresTimeMs) + { + debugPrintf("Time-out in printing\n"); + if(pdex > 0) + writeChunk(pbuf,pdex); + writeStr("0\r\n\r\n"); + outStream->flush(); + switchBackToCommandMode(false); + } + checkBaudChange(); +} + diff --git a/src/zslipmode.ino b/src/zslipmode.ino new file mode 100644 index 0000000..caa6781 --- /dev/null +++ b/src/zslipmode.ino @@ -0,0 +1,176 @@ +#include +/* + * zslipmode.ino + * + * Created on: May 17, 2022 + * Author: Bo Zimmerman + */ +#ifdef INCLUDE_SLIP + +void ZSLIPMode::switchBackToCommandMode() +{ + currMode = &commandMode; + //TODO: UNDO THIS: raw_recv(_pcb, &_raw_recv, (void *) _pcb); +} + +void ZSLIPMode::switchTo() +{ + uint8_t num_slip1 = 0; + struct netif slipif1; + ip4_addr_t ipaddr_slip1, netmask_slip1, gw_slip1; + //LWIP_PORT_INIT_SLIP1_IPADDR(&ipaddr_slip1); + //LWIP_PORT_INIT_SLIP1_GW(&gw_slip1); + //LWIP_PORT_INIT_SLIP1_NETMASK(&netmask_slip1); + //printf("Starting lwIP slipif, local interface IP is %s\n", ip4addr_ntoa(&ipaddr_slip1)); + + netif_add(&slipif1, &ipaddr_slip1, &netmask_slip1, &gw_slip1, &num_slip1, slipif_init, ip_input); + + sserial.setFlowControlType(FCT_DISABLED); + if(commandMode.getFlowControlType()==FCT_RTSCTS) + sserial.setFlowControlType(FCT_RTSCTS); + sserial.setPetsciiMode(false); + sserial.setXON(true); + + inPacket=""; + started=false; + escaped=false; + if(_pcb == 0) + { + _pcb = raw_new(IP_PROTO_TCP); + if(!_pcb){ + return; + } + } + //_lock = xSemaphoreCreateMutex(); + raw_recv(_pcb, &_raw_recv, (void *) _pcb); + currMode=&slipMode; + debugPrintf("Switched to SLIP mode\n\r"); +} + +static String encodeSLIP(uint8_t *ipPacket, int ipLen) +{ + String slip; + slip += ZSLIPMode::SLIP_END; + for(int i=0;inext; + this_pb->next = NULL; + for(int i=0;ilen;i+=4) + { + String pkt = encodeSLIP((uint8_t *)this_pb->payload, this_pb->len); + int plen = pkt.length(); + if(plen > 0) + { + if(logFileOpen) + logPrintln("SLIP-in packet:"); + uint8_t *buf = (uint8_t *)pkt.c_str(); + for(int p=0;p0) + { + uint8_t c = HWSerial.read(); + if(logFileOpen) + logSerialIn(c); + if (c == SLIP_END) + { + if(started) + { + if(inPacket.length()>0) + { + if(logFileOpen) + logPrintln("SLIP-out packet."); + struct pbuf p = { 0 }; + p.next = NULL; + p.payload = (void *)inPacket.c_str(); + p.len = inPacket.length(); + p.tot_len = inPacket.length(); + raw_send(_pcb, &p); + } + } + else + started=true; + inPacket=""; + } + else + if(c == SLIP_ESC) + escaped=true; + else + if(c == SLIP_ESC_END) + { + if(escaped) + { + inPacket += SLIP_END; + escaped = false; + } + else + inPacket += c; + } + else + if(c == SLIP_ESC_ESC) + { + if(escaped) + { + inPacket += SLIP_ESC; + escaped=false; + } + else + inPacket += c; + } + else + if(escaped) + { + debugPrintf("SLIP Protocol Error\n"); + if(logFileOpen) + logPrintln("SLIP error."); + inPacket=""; + escaped=false; + } + else + { + inPacket += c; + started=true; + } + } +} + +void ZSLIPMode::loop() +{ + if(sserial.isSerialOut()) + serialOutDeque(); +} + +#endif /* INCLUDE_SLIP_ */ diff --git a/src/zstream.ino b/src/zstream.ino new file mode 100644 index 0000000..8dfb683 --- /dev/null +++ b/src/zstream.ino @@ -0,0 +1,316 @@ +/* + Copyright 2016-2019 Bo Zimmerman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +void ZStream::switchTo(WiFiClientNode *conn) +{ + current = conn; + currentExpiresTimeMs = 0; + lastNonPlusTimeMs = 0; + plussesInARow=0; + serial.setXON(true); + serial.setPetsciiMode(isPETSCII()); + serial.setFlowControlType(getFlowControl()); + currMode=&streamMode; + checkBaudChange(); + if(pinSupport[pinDTR]) + lastDTR = digitalRead(pinDTR); +} + +bool ZStream::isPETSCII() +{ + return (current != null) && (current->isPETSCII()); +} + +bool ZStream::isEcho() +{ + return (current != null) && (current->isEcho()); +} + +FlowControlType ZStream::getFlowControl() +{ + return (current != null) ? (current->getFlowControl()) : FCT_DISABLED; +} + +bool ZStream::isTelnet() +{ + return (current != null) && (current->isTelnet()); +} + +bool ZStream::isDisconnectedOnStreamExit() +{ + return (current != null) && (current->isDisconnectedOnStreamExit()); +} + +void ZStream::baudDelay() +{ + if(baudRate<1200) + delay(5); + else + if(baudRate==1200) + delay(3); + else + delay(1); + yield(); +} + +void ZStream::serialIncoming() +{ + int bytesAvailable = HWSerial.available(); + if(bytesAvailable == 0) + return; + uint8_t escBufDex = 0; + while(--bytesAvailable >= 0) + { + uint8_t c=HWSerial.read(); + if(((c==27)||(escBufDex>0)) + &&(!isPETSCII())) + { + escBuf[escBufDex++] = c; + if(((c>='a')&&(c<='z')) + ||((c>='A')&&(c<='Z')) + ||(escBufDex>=ZSTREAM_ESC_BUF_MAX) + ||((escBufDex==2)&&(c!='['))) + { + logSerialIn(c); + break; + } + if(bytesAvailable==0) + { + baudDelay(); + bytesAvailable=HWSerial.available(); + } + } + logSerialIn(c); + if((c==commandMode.EC) + &&((plussesInARow>0)||((millis()-lastNonPlusTimeMs)>800))) + plussesInARow++; + else + if(c!=commandMode.EC) + { + plussesInARow=0; + lastNonPlusTimeMs=millis(); + } + if((c==19)&&(getFlowControl()==FCT_NORMAL)) + serial.setXON(false); + else + if((c==17)&&(getFlowControl()==FCT_NORMAL)) + serial.setXON(true); + else + { + if(isEcho()) + serial.printb(c); + if(isPETSCII()) + c = petToAsc(c); + if(escBufDex==0) + socketWrite(c); + } + } + + if(escBufDex>0) + socketWrite(escBuf,escBufDex); + currentExpiresTimeMs = 0; + if(plussesInARow==3) + currentExpiresTimeMs=millis()+800; +} + +void ZStream::switchBackToCommandMode(bool logout) +{ + if(logout && (current != null) && isDisconnectedOnStreamExit()) + { + if(!commandMode.suppressResponses) + { + if(commandMode.numericResponses) + { + preEOLN(commandMode.EOLN); + serial.prints("3"); + serial.prints(commandMode.EOLN); + } + else + if(current->isAnswered()) + { + preEOLN(commandMode.EOLN); + serial.prints("NO CARRIER"); + serial.prints(commandMode.EOLN); + } + } + delete current; + } + current = null; + currMode = &commandMode; +} + +void ZStream::socketWrite(uint8_t *buf, uint8_t len) +{ + if(current->isConnected()) + { + uint8_t escapedBuf[len*2]; + if(isTelnet()) + { + int eDex=0; + for(int i=0;iwrite(buf,len); + nextFlushMs=millis()+250; + } +} + +void ZStream::socketWrite(uint8_t c) +{ + if(current->isConnected()) + { + if(c == 0xFF && isTelnet()) + current->write(c); + current->write(c); + logSocketOut(c); + nextFlushMs=millis()+250; + //current->flush(); // rendered safe by available check + //delay(0); + //yield(); + } +} + +void ZStream::loop() +{ + WiFiServerNode *serv = servs; + while(serv != null) + { + if(serv->hasClient()) + { + WiFiClient newClient = serv->server->available(); + if(newClient.connected()) + { + int port=newClient.localPort(); + String remoteIPStr = newClient.remoteIP().toString(); + const char *remoteIP=remoteIPStr.c_str(); + bool found=false; + WiFiClientNode *c=conns; + while(c!=null) + { + if((c->isConnected()) + &&(c->port==port) + &&(strcmp(remoteIP,c->host)==0)) + found=true; + c=c->next; + } + if(!found) + new WiFiClientNode(newClient, serv->flagsBitmap, 5); // constructing is enough + // else // auto disconnect when from same + } + } + serv=serv->next; + } + + WiFiClientNode *conn = conns; + unsigned long now=millis(); + while(conn != null) + { + WiFiClientNode *nextConn = conn->next; + if((!conn->isAnswered()) + &&(conn->isConnected()) + &&(conn!=current) + &&(!conn->isMarkedForDisconnect())) + { + conn->write((uint8_t *)busyMsg.c_str(), busyMsg.length()); + conn->flushAlways(); + conn->markForDisconnect(); + } + conn = nextConn; + } + + WiFiClientNode::checkForAutoDisconnections(); + + if(pinSupport[pinDTR]) + { + if(lastDTR==dtrActive) + { + lastDTR = digitalRead(pinDTR); + if((lastDTR==dtrInactive) + &&(dtrInactive != dtrActive)) + { + if(current != null) + current->setDisconnectOnStreamExit(true); + switchBackToCommandMode(true); + } + } + lastDTR = digitalRead(pinDTR); + } + if((current==null)||(!current->isConnected())) + { + switchBackToCommandMode(true); + } + else + if((currentExpiresTimeMs > 0) && (millis() > currentExpiresTimeMs)) + { + currentExpiresTimeMs = 0; + if(plussesInARow == 3) + { + plussesInARow=0; + if(current != 0) + { + commandMode.sendOfficialResponse(ZOK); + switchBackToCommandMode(false); + } + } + } + else + if(serial.isSerialOut()) + { + if(current->available()>0) + //&&(current->isConnected()) // not a requirement to have available bytes to read + { + int bufferRemaining=serialOutBufferBytesRemaining(); + if(bufferRemaining > 0) + { + int bytesAvailable = current->available(); + if(bytesAvailable > bufferRemaining) + bytesAvailable = bufferRemaining; + if(bytesAvailable>0) + { + for(int i=0;(iavailable()>0);i++) + { + if(serial.isSerialCancelled()) + break; + uint8_t c=current->read(); + logSocketIn(c); + if((!isTelnet() || handleAsciiIAC((char *)&c,current)) + && (!isPETSCII() || ascToPet((char *)&c,current))) + serial.printb(c); + } + } + } + } + if(serial.isSerialOut()) + { + if((nextFlushMs > 0) && (millis() > nextFlushMs)) + { + nextFlushMs = 0; + serial.flush(); + } + serialOutDeque(); + } + } + checkBaudChange(); +} + diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html