diff --git a/NOTICE b/NOTICE index 02189d2..b36f384 100644 --- a/NOTICE +++ b/NOTICE @@ -1,6 +1,10 @@ C64Net WiFi Firmware / Zimodem - Copyright 2016-2021 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman This product includes software developed by Bo Zimmerman http://www.zimmers.net + + It also contains portions of libssh2, licensed + under the BSD-3 license: + https://github.com/libssh2/libssh2 \ No newline at end of file diff --git a/README b/README index 01cdcd0..5ddd104 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Zimodem 3.6.4 (C)2016-2021 Bo Zimmerman +Zimodem 4.0 (C)2016-2025 Bo Zimmerman Please read the LICENSE file for license information Please read the NOTICE file for credits information @@ -12,73 +12,41 @@ Building: --------- To build this firmware for the ESP-01 or ESP-12, I used the Arduino IDE rev1.8.10 with the 2.7.4 Arduino ESP8266 libraries using the Generic ESP8266 Module. Versions newer than 2.7.4 may not work. Install the libraries from the Boards Manager using http://arduino.esp8266.com/stable/package_esp8266com_index.json. Both versions of the firmware are built with SPIFFS enabled. On the ESP8266, I used settings of 1M/160k, but you may want to alter that depending on your hardware and needs. -To build this firmware for the ESP-32, I also used the Arduino IDE rev 1.8.10 with the Arduino ESP32 Dev Module installed from the board manager * , with the following settings: QIO, 4MB, 80MHZ, 921600, NONE, Avrisp Mk II **. I found that baud changing corrupts the serial bus as of 1/13/2018, so the following changes were made to the base libraries: -> Added to esp32-hal-uart.c: -void uartChangeBaudRate(uint8_t uart_nr, uint32_t baudrate) -{ - uartSetBaudRate(&_uart_bus_array[uart_nr], baudrate); -} -void uartChangeConfig(uint8_t uart_nr, uint32_t config) -{ - uart_t* uart = &_uart_bus_array[uart_nr]; - UART_MUTEX_LOCK(); - uart->dev->conf0.val = config; - #define TWO_STOP_BITS_CONF 0x3 - #define ONE_STOP_BITS_CONF 0x1 - - if ( uart->dev->conf0.stop_bit_num == TWO_STOP_BITS_CONF) { - uart->dev->conf0.stop_bit_num = ONE_STOP_BITS_CONF; - uart->dev->rs485_conf.dl1_en = 1; - } - UART_MUTEX_UNLOCK(); -} -size_t uartWritesRemaining(uart_t* uart) -{ - if(uart == NULL) { - return 0; - } - size_t remain; - UART_MUTEX_LOCK(); - remain = 0x7F - uart->dev->status.txfifo_cnt; - UART_MUTEX_UNLOCK(); - return remain; -} -> Added to esp32-hal-uart.h: -void uartChangeBaudRate(uint8_t uart_nr, uint32_t baudrate); -void uartChangeConfig(uint8_t uart_nr, uint32_t config); -size_t uartWritesRemaining(uart_t* uart); -> Added to HardwareSerial.cpp: -void HardwareSerial::changeBaudRate(int baudRate) { uartChangeBaudRate(_uart_nr, baudRate); } -void HardwareSerial::changeConfig(uint32_t config) { uartChangeConfig(_uart_nr, config); } -> Added to HardwareSerial.h: -void changeBaudRate(int baudRate); -void changeConfig(uint32_t config); - -* On the ESP32, if you want to build firmware that is Update compatible with the official production zimodem, you'll need to install the exact libraries I use from http://www.zimmers.net/otherprojs/esp32_v0.zip . If you don't care about that, using the latest libs work just fine. +To build this firmware for the ESP-32, I also used the Arduino IDE rev 1.8.10 with the Arduino ESP32 Dev Module installed from the board manager * , with the following settings: QIO, 8MB, 80MHZ, 921600, NONE, Avrisp Mk II **.*, and 3MB App space. Currently on library version 2.07. + +** Before you build, you'll want to go through the file zimodem.ino and configure the various #defines to reflect hardware pin assignments and features. These especially include: INCLUDE_IRCC, SUPPORT_LED_PINS, INCLUDE_SD_SHELL, INCLUDE_OTH_UPDATES, DEFAULT_BAUD_RATE, PIN_FACTORY_RESET, all the various DEFAULT_PIN_* assignments for your hardware, INCLUDE_SSH, and MAX_PIN_NO. In addition, at the top of pet2asc.h is where the default main and debug uarts are assigned (MAIN_UART_NUM). Make sure those reflect your wiring as well. Using: ------ Upon initialization, or any time the ESP module is reset, the modem will display its version, and some information about the host hardware, and then read a configuration file from the internal SPIFFS to re-establish the previously set baud rate, and to attempt to re-connect to the previously connected wireless router. The first time it is run, the firmware will set a baud rate of 1200 and display INITALIZED to let you know that no previous wifi configuration was found. Once the serial terminal displays READY, it is ready to receive commands. -The first command you'll probably want to enter is AT+CONFIG to connect to a wireless router, and set your flow control and other command mode settings. +AT+CONFIG: The first command you'll probably want to enter is used to connect to a wireless router, and set your flow control and other command mode settings. + +Linefeeds: If you plan to use this primarily from a computer that doesn't need linefeeds, such as the C64, you'll want to enter ATR0 to go into carriage-return only mode, and then at&w to save this setting as well. + +Baud Rate: If you want to operate at a higher baud, you'll want to enter ATB9600 (or whatever baud rate you want to try), and then reconnect your terminal program to the modem at that new baud rate. If everything looks good, and you want to keep the new baud rate across restarts, save the new baud rate with AT&W. Warning though: Most of the example C64 programs assume the modem defaults to 1200 baud. + +AT+PRINT: If you want to try printing to a CUPS/IPP-printer, enter AT+PRINT?::/, followed by your data. Where ? is A)scii, P)etscii, or R)aw. A 5 second pause in incoming data completes the document and returns to command mode. Example: AT+PRINTR:192.168.1.10:631/ipp/printer -- followed by ENTER and then the data to print, without any pause longer than 5 seconds. Subsequent to doing this, using AT+PRINT will repeat the previous URL. + +AT+IRC: If you want to chat on IRC, enter AT+IRC, enter a nickname and a irc host as a phone number entry, then /join channels, chat, or /quit. -Afterwards, if you plan to use this primarily from a computer that doesn't need linefeeds, such as the C64, you'll want to enter ATR0 to go into carriage-return only mode, and then at&w to save this setting as well. +AT+SHELL: ESP32 Modem users with SD-cards can enter AT+SHELL to get a shell command prompt. Enter ? to get a list of shell commands. -If you want to operate at a higher baud, you'll want to enter ATB9600 (or whatever baud rate you want to try), and then reconnect your terminal program to the modem at that new baud rate. If everything looks good, and you want to keep the new baud rate across restarts, save the new baud rate with AT&W. Warning though: Most of the example C64 programs assume the modem defaults to 1200 baud. +AT+PING: To check if your wifi is connected and DNS resolution is working, try AT+PING"google.com". An OK means a response was received. -If you want to connect to a remote telnet server, eg coffeemud.net, port 23, you'll want to enter ATDT"coffeemud.net:23". Don't forget to set your terminal program to the proper translation mode (ANSI, ASCII, or whatever). +AT+COMET64: For modems with SD-cards, this puts the modem into 2400 bps, CSIP protocol mode for reading/writing directly to the card (for V-1541). +++ to exit. -If you are using a Commodore Graphics terminal program and want to connect to a Commodore BBS, eg cottonwoodbbs.dyndns.org port 6502, you'll want to enter ATD"cottonwoodbbs.dyndns.org:6502". +AT+HOSTCM: For modems with SD-cards, this puts the modem into HOSTCM protocol mode for reading/writing directly to the card (for Commodore SupetPET SP9000). +++ to exit. -If you want to use Q-Link, you need to add a phone number alias first. To do this, enter ATP"5551212=q-link.net:5190" or enter it from the config menu AT+CONFIG. From the C64 Q-Link client, select "Hayes compatible" 1200 baud modem when prompted. +AT+1650, 1660, 1670: Puts the modem into a emulation mode for Commodore 1650, 1660, and 1670. Changes baud rates accordingly. Use AT&L or AT&F to clear emulation settings. -If you want to run a Commodore BBS program using the modem, you'll want to configure the BBS program to the same idle baud rate that your modem is using (1200 baud by default), configure it for a Hayes style modem (or the C=1670), and either create a persistant listener using AT+CONFIG, or use an initialization string of "ATR0E0S0=1S41=1A6400" plus any other recommended settings from the BBS program. This creates a listener at port 6400 that switches directly to stream mode on the first ring, with no linefeed carriage returns, and no keystroke echo. Your BBS program may require you add certain other settings, such as V0 or X1.. which you should also do. +Telnet Dialing: If you want to connect to a remote telnet server, eg coffeemud.net, port 23, you'll want to enter AT&S62=1DT"coffeemud.net:23". Don't forget to set your terminal program to the proper translation mode (ANSI, ASCII, or whatever). -If you want to try printing to a CUPS/IPP-printer, enter AT+PRINT?::/, followed by your data. Where ? is A)scii, P)etscii, or R)aw. A 5 second pause in incoming data completes the document and returns to command mode. Example: AT+PRINTR:192.168.1.10:631/ipp/printer -- followed by ENTER and then the data to print, without any pause longer than 5 seconds. Subsequent to doing this, using AT+PRINT will repeat the previous URL. +Normal Dialing: If you are using a Commodore Graphics terminal program and want to connect to a Commodore BBS, or a standard ASCII terminal program for a non-Commodore BBS, you'll want to enter e.g. ATD"cottonwoodbbs.dyndns.org:6502". The "T" in "ATDT" is optional if register 62 is 0 (default). -If you want to chat on IRC, enter AT+IRC, enter a nickname and a irc host as a phone number entry, then /join channels, chat, or /quit. +Q-Link Setup: If you want to use Q-Link, you need to add a phone number alias first. To do this, enter ATP"5551212=q-link.net:5190" or enter it from the config menu AT+CONFIG. From the C64 Q-Link client, select "Hayes compatible" 1200 baud modem when prompted. -ESP32 Guru Modem users with SD-cards can enter AT+SHELL to get a shell command prompt. Enter ? to get a list of shell commands. +BBS/Port Listeners: If you want to run a Commodore BBS program using the modem, you'll want to configure the BBS program to the same idle baud rate that your modem is using (1200 baud by default), configure it for a Hayes style modem (or the C=1670), and either create a persistant listener using AT+CONFIG, or use an initialization string of "ATR0E0S0=1S41=1A6400" plus any other recommended settings from the BBS program. This creates a listener at port 6400 that switches directly to stream mode on the first ring, with no linefeed carriage returns, and no keystroke echo. Your BBS program may require you add certain other settings, such as V0 or X1.. which you should also do. Command Set: ----------- @@ -111,7 +79,7 @@ ATAEn : Adding a E modifier causes connection terminal echo to be enabled when t ATAXn : Adding a X modifier causes connection XON/XOFF flow control to be enabled when the changed to Stream mode. ATN0 : Shuts down all listeners, leaving client connections open -ATNn : if n > 0 then same as ATAn +ATNn : if n > 0 then closes a Server (ATA) listener with the given id; does not close any connections received from that listener. ATE0 : Turns serial terminal echo off for command mode. ATE1 : Turns serial terminal echo on for command mode. @@ -127,6 +95,7 @@ ATF1 : Turns on xon/xoff flow control. ATF2 : Turns on xon/xoff flow control, sets XON mode (if necessary), and, in command mode, will immediately go to XOFF when a single connection packet is received. This is very useful when the client wants to ensure it only receives one packet to process. You can think of this as an alternative way to use xon/xoff by having XOFF automatic between packets. ATF3 : Similar to ATF2 except that the default is XOFF, and, in command mode, a XON code from the user will immediately trigger either an empty packet response [ 0 0 0 ], or a real packet if one is available. After this, as in ATF2, XOFF is automatically set. ATF4 : Turns off flow control for command mode + * : in AT+1670 mode, ATF0 will instead turn on stream mode echo, and ATF1 will turn it off. ATQ0 : Turns off quiet mode (Sends response codes) ATQ1 : Turns on quiet mode (Stops sending response codes) @@ -149,9 +118,10 @@ ATD : Start a streaming connection between the current opened connection. Use " ATDn : Where n > 0, this will start a streaming connection between the previously opened connection with an id the same as n. Use "+++" to exit back to Command mode. ATD"[HOSTNAME]:[PORT]" : This opens a streaming connection between the terminal and the given host/port. Use "+++" to disconnect and exit back to command mode. ATDP"[HOSTNAME]:[PORT]" : Adding a P modifier causes connection input to be translated to PETSCII during the streaming session. -ATDT"[HOSTNAME]:[PORT]" : Adding a T modifier causes connection input to be translated per TELNET during the streaming session. +ATDT"[HOSTNAME]:[PORT]" : Adding a T modifier allows connection input to be translated per TELNET during the streaming session (see S62). ATDE"[HOSTNAME]:[PORT]" : Adding a E modifier causes terminal echo to be enabled that streaming session. ATDX"[HOSTNAME]:[PORT]" : Adding a X modifier causes XON/XOFF flow control to be enabled that streaming session. +ATDS"[NAME]:[PASS]@[HOSTNAME]:[PORT]" : Adding a S modifier, along with username and password, causes an SSH shell connection to be made. ATDnnnnnnn : Where n=0-9, if the digits exist in the phonebook (see ATP), it will try connect to that host, with those modifiers, from the phonebook. ATC : Shows information about the current network connection in the following format "[CONNECTION STATE] [CONNECTION ID] [CONNECTED TO HOST]:[CONNECTED TO PORT]" @@ -165,7 +135,8 @@ ATCX"[HOSTNAME]:[PORT]" : Adding a X modifier causes XON/XOFF flow control to be ATH : Hangs up (disconnects and deletes) all open connections. Does not close Server listeners. ATH0 : Hangs up (disconnects and deletes) the current opened connection. -ATHn : Hangs up (disconnects and deletes) the open connection with the given id. Closing a Server (ATA) listener does not close any connections received from that listener. +ATH1 : If no client connection with id 1 is open, this will put server listeners into 'busy' mode. +ATHn : Hangs up (disconnects and deletes) the open client connection with the given id. ATO : Re-enters a Streaming session (see ATD) under the previous settings, with the current (previous) connection. @@ -196,15 +167,17 @@ ATS48=n : Changes CTS status. n=0 is default CTS=HIGH=active. n=1 is CTS=LOW=act ATS49=n : Changes CTS pin number, n=0 is default on ESP01, and default is 5 otherwise ATS50=n : Changes RTS status. n=0 is default RTS=HIGH=active. n=1 is RTS=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) ATS51=n : Changes RTS pin number, n=4 is default (N/A on ESP01) -ATS52=n : Changes RI status. n=0 is default RI=HIGH=active. n=1 is RTS=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) +ATS52=n : Changes RI status. n=0 is default RI=HIGH=active. n=1 is RI=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) ATS53=n : Changes RI pin number, n=14 is default (N/A on ESP01) -ATS54=n : Changes DTR status. n=0 is default DTR=HIGH=active. n=1 is RTS=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) +ATS54=n : Changes DTR status. n=0 is default DTR=HIGH=active. n=1 is DTR=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) ATS55=n : Changes DTR pin number, n=12 is default (N/A on ESP01) -ATS56=n : Changes DSR status. n=0 is default DSR=HIGH=active. n=1 is RTS=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) +ATS56=n : Changes DSR status. n=0 is default DSR=HIGH=active. n=1 is DSR=LOW=active. n=2 always HIGH. n=3 always LOW. (N/A on ESP01) ATS57=n : Changes DSR pin number, n=13 is default (N/A on ESP01) ATS60=n : When n > 0, immediately saves existing listeners and automatically restores them later. n=0 to clear. ATS61=n : When n > 0, sets the number of seconds to timeout a print job stream (AT+PRINT). Default is 5 seconds -ATS62=n : When n > 0, enables/disables telnet support in the ATD (Dial) command. Default is 1 (enabled). +ATS62=n : When n > 0, enables/disables telnet support in the ATD (Dial) command. Default is 0 (disabled). +ATS63=n : When n > 0, enables/disables +++ (n=1), DTR (n=2), or OTH/PDP (n=3) hangup while in stream mode +ATSn? : Where n >=0 will show the current value of status register n +++ : With a 1 second pause with no other characters afterwards, this will disconnect the current opened connection. @@ -219,7 +192,7 @@ ATLn : Re-sends the most recently sent data packet for connection id n. Prefix AT&H : Shows a help file from the web, or brief help otherwise. Use &H6502 to reiforce web download. AT&L : Reloads the saved configuration. -AT&W : Saves the current configuration: WiFi settings(ATW), baud rate (ATB), end of line (ATR) settings, flow control (ATF), echo mode (ATE), extended responses (ATX), verbose responses (ATV), quiet responses (ATQ), PETSCII mode (AT&P1), pin statuses (ATS46 - S58), Rings (ATS0), Listener Stream-mode (ATS41), and Listener restore (ATS60), printer spec (AT+PRINT), and busy message. +AT&W : Saves the current configuration: WiFi settings(ATW), baud rate (ATB), end of line (ATR) settings, flow control (ATF), echo mode (ATE), extended responses (ATX), verbose responses (ATV), quiet responses (ATQ), PETSCII mode (AT&P1), pin statuses (ATS46 - S58), Rings (ATS0), Listener Stream-mode (ATS41), and Listener restore (ATS60), printer spec (AT+PRINT), the busy message, telnet (S62), hangup mode (S63), and operating mode (AT+16xx). AT&F : Restores the modem to factory default settings. Use &F86 to reformat the SPIFFS. AT&On : n is 1 to turn on internal serial-reception log, n is 0 to turn off or view a previously turned-on log, n is 88 to turn on ESP32 debug port. AT&U : Checks the firmware home page to see if a new version is available. @@ -238,7 +211,7 @@ AT&S"41=[TERMTYPE]" : Change the telnet 'termtype' response string AT&S"42=[BUSYMSG]" : Change the stream connection 'busy message' AT&T"[TIMEZONE],[TIME FORMAT],[NTP URL]" : set up the NTP clock. DISABLE to disable. Format is like Java SimpleDateFormat, but with % escapes. Each argument is optional. -AT&G"[HOSTNAME]:[PORT]/[FILENAME]" : Streams a file from an HTTP source on the internet. The header contains channel 0, file length, and an 8-bit sum of all the bytes in the forthcoming file, followed by the bytes of the file, all formatted as a normal packet. An ASCII 3 (CNTRL-C) received during the transfer will abort. The S44 register can be used to create artificial delays in this output. XON/XOFF Flow control also remains in effect with, on a byte-by-byte basis for the auto and manual flow control systems. Requires flash space for caching, or S45=3 to eliminate the SUM header. +AT&G"[HOSTNAME]:[PORT]/[FILENAME]" : Streams a file from an HTTP/FTP/GOPHER source on the internet. The header contains channel 0, file length, and an 8-bit sum of all the bytes in the forthcoming file, followed by the bytes of the file, all formatted as a normal packet. An ASCII 3 (CNTRL-C) received during the transfer will abort. The S44 register can be used to create artificial delays in this output. XON/XOFF Flow control also remains in effect with, on a byte-by-byte basis for the auto and manual flow control systems. Requires flash space for caching, or S45=3 to eliminate the SUM header. AT&Y : Resets the state machine string. No state machine will be executed. AT&Yn : Change the current state (for command mode AND current connection) to state n, where n is a decimal number. AT&Y"[CODED STATE MACHINE]" : Adds the coded format string to a state machine. If this command is followed by a C, N, or A command on the SAME LINE, then the setting will apply ONLY to that connection or listener. State Machine Format: MMcCCNN ... States are numbered by their order in the list starting with 00. Non-matches automatically go to the next state until a match is made. 'MM' is hex byte to match (or 00 to match all). 'c' is one of these commands :e=eat byte, p=push byte to que, d=send byte, q=send all queued, x=flush queue, r=replace with byte represented by hex CC. 'C' is either '-', one of the command letters above, or a hex byte value if the first command was 'r'. 'NN' is the next state to go to, with 00 being the first state. diff --git a/TODO b/TODO index a9c1f0b..5b1cb64 100644 --- a/TODO +++ b/TODO @@ -1,21 +1,54 @@ -Bugs Reported (nor reproduced): +TODO: + *. Add SuppressGoAhead support for telnet, maybe S register to enable. + *. See if there's a way to show DNS, and other info using ATI +Bugs Reported (nor reproduced): + *. Test incoming network packets of various sizes at different periods + *. AT+IRC, if you are in PETSCII mode, will forward the PETSCII characters. + -- the code doesn't seem to make this possible. need more info. + -- is petscii terminal in petscii cl? non petscii cl? + New Features: - *. Ability to use protocols for at&g, and maybe have alternative at+web - *. make a persistent-ish ftp shell - *. Ability to ftp file streamdown, like at&g.. maybe at+ftp - *. Support switching between CONNECT 1 and CONNECT 1200 messages - *. Baud rate test commend - *. ATW0 should be same as ATW + *. UART-compliant signals to switch to command mode, and to reset modem to Saved settings + *. Add Multi-Punter support + *. Allow protocols to work with ranges + *. Create a Better LOAD and SAVEr (like CometMode), but base it on ... something else. *. SLIP support? *. PPP support? + *. Support SSH interactive + *. @19200, on C64 modem, extend start bit by 30 cycles + *. Baud rate test command (what good is this? how do you send cmd if baud is wrong?!) + *. SSL/SSH server sockets? + *. LYNX-like browser? -New hardware hopes: -C+B 0 FLAG2+PB0 RxD Receive Data (Must be applied to both pins!) -D! 1 RTS Ready To Send -F! 3 RI Ring Indicator (1650 ring indicator - output of modem -> input of computer ) -H! 4 DCD Data Carrier Detect -J! 5 1650 Pulse Dial Pin (output of computer -> input to modem) -+K 6 CTS Clear To Send -M! 8 TxD Transmit Data - +GPIO Pin assignments: ESP12-E ESP32 ESP-01 User port + DEFAULT_PIN_AA 16 + DEFAULT_PIN_HS 15 + DEFAULT_PIN_WIFI 0 + PIN_FACTORY_RESET 0 + DEFAULT_PIN_DCD 2 14 2 H (bit 16) + DEFAULT_PIN_CTS 5 13 0 D (bit 2 - Actual RTS (modem input)) + DEFAULT_PIN_RTS 4 15 (5V) K (bit 64 - Actual CTS (computer input)) + DEFAULT_PIN_RI 14 32 F (bit 8) + DEFAULT_PIN_DSR 13 12 L (bit 128) + DEFAULT_PIN_DTR 12 27 E (bit 4) + DEFAULT_PIN_RX C (bit 1) + DEFAULT_PIN_TX M (port A somewhere) + DEFAULT_PIN_OTH 4 J (bit 32) + **** remember that default_pin_cts and rts are mis-labeled here! + +Connects to: TXDDSRRXD + R U U U + G S P P P G + N 5 E 9 9 9 9 9 N + D V T 6 6 6 V V D + + 1 2 3 4 5 6 7 8 9 10 11 12 + -- --------------------------- ------ + A B C D E F H J K L M N + + G R--R R D R D P C U T G + N X--X T T I C D T P X N + D S R D P S 9 D + 6 + Connects to: *5v DSR diff --git a/cbm8bit/64net_apps.d64 b/cbm8bit/64net_apps.d64 index 1470ee8..5c89e38 100644 Binary files a/cbm8bit/64net_apps.d64 and b/cbm8bit/64net_apps.d64 differ diff --git a/cbm8bit/64net_prgdocs.txt b/cbm8bit/64net_prgdocs.txt index ca83512..7008fa8 100644 --- a/cbm8bit/64net_prgdocs.txt +++ b/cbm8bit/64net_prgdocs.txt @@ -200,6 +200,29 @@ When you start the program, it will initialize your C64Net modem with certain co After initialization, you will be given a menu where you can enter the url of the disk image file to download and write, and the unit number of the disk drive containing the *formatted* blank disk to write the image to. When all options are correct, hit RETURN and answer Y to the confirmation to connect and begin the download and disk write. When the download completes, which could take awhile, the program will exit. +GOPHER +************************************* + +GOPHER program is a client for browsing gopher servers and their text resources. It is written in BASIC, but uses the PML64.BIN, PMLVIC.BIN, or PML128.BIN libraries for speed (see below). This program requires a C64, C128 in either 64 or 128 mode, or a VIC-20 with 16K expansion or better, and your C64Net WiFi Modem configured for 1200 baud, which is the default factory configuration. To run the program, enter LOAD"GOPHER",8 and then RUN. + +When you start the program, it will initialize your C64Net modem with certain commands. If you receive an error at this point, or if the program hangs for longer than a minute, try turning your computer off and on again to clear memory and reset the modem, and then load and run it again. + +After initialization, you will be asked to enter the host name and port of the GOPHER Server to connect to. The program will then attempt to connect to the host and port, and then read the main index page of the site. This may take several minutes before it displays. + +Once the main index is displayed, you may use the following keys to navigate: + +Cursor Right/Left: +Each page is formatted for an 80 column screen. If you are using a C128 in 80 column mode, this will be fine, but for C64 and VIC-20 users, it means a portion of the page will not be visible. Use the Cursor Right key on your keyboard to see more of the page, and Cursor Left to go back. If you press Cursor Left while you are already on the left-hand side of the page, nothing will happen. But if you have navigated to a Sub-Menu of the gopher site, then Cursor-Left may prompt you to return to the previous page. + +Cursor Down/Up: +If the page you are looking at has Links, then Cursor Down will highlight the next Link on the page, and Cursor Up the previous. If you are on the last link on the screen, and the GOPHER page is several screen pages long, Cursor Down will load next screen of data for the page. Cursor Up from the top Link on a page will return to the previous screen in the page. + +RETURN/Enter: +If you have a Link highlighted, then pressing RETURN will prompt you to Load that new link. It may be a readable text file, or a gopher sub-menu. Cursor Left can be used to return to a previous gopher menu page. + +Q)uit: +Exit the GOPHER program + TELNETD64 ************************************* TELNETD program is for controlling your C64 remotely over the internet. It is written mostly in machine language which is contained in the file "RDS64.BIN", with the source code in "RDS64.BAS". The program TELNETD64 is written in BASIC, and is for configuring and launching your server. To run the program, enter LOAD"TELNETD64",8 and then RUN. @@ -218,6 +241,20 @@ To connect to your server from the internet, you should set your ISP router to f The only way to shut down your server is either to reset the computer, or turn it off and back on again. +LOADER64 +************************************* +If your WiFi Modem has an SD-Card interface, then this C64-only wedge program can be used to load and save files to and from the SD-Card. The program is a small BASIC loader that initializes your modem and puts it into 'COMET64' mode using the V-1541 driver by CommodoreServer.com. You can read more about it here: +https://commodoreserver.com/BlogEntryView.asp?EID=F953E71737CB49458E983685EE3128E4 + +The COMET64 mode supports LOAD and SAVE, as well as $ viewing the directory and CD changing subdirectories, as well as the commands: FIND, SCRATCH, MF, and CF. It does not support disk images, but only writing directly to the SD card filesystem. + +COMETSERVE64 +************************************* +Previously known as COMET64, this C64-only wedge program allows you to load and save software directly from the internet, specifically from commodoreserver.com. To use this service, you will need to create an account at that site. + +The program is a small BASIC loader that initializes your modem, connects to cometserver.com, and then loads the V-1541 wedge driver and exits. You can read more about how to use LOAD and SAVE to access your online disks here: +https://commodoreserver.com/BlogEntryView.asp?EID=F953E71737CB49458E983685EE3128E4 + WEATHER64 ************************************* Weather is a two player online game for the Commodore 64 originally written for the Commodore PET. This program requires that both players have C64Net WiFi Modems configured for 1200 baud on reset, which is the default factory configuration. To run the program, enter LOAD"WEATHER64",8 and then RUN. diff --git a/cbm8bit/link232_slapps.d64 b/cbm8bit/link232_slapps.d64 index 04422ae..8f7fb07 100644 --- a/cbm8bit/link232_slapps.d64 +++ b/cbm8bit/link232_slapps.d64 @@ -59,11 +59,9 @@ DIGERR2 LDA #$FF:RTS +ԷqP3ܿyX;"`A* hI2Qp:Yx -No%Fg=^5wV˨nO, àfG$_~ ?` ʝ` ` 'ʩLK 'ʩLK 'ʩLK 'ʩLK 'ʩLK 'ʩLKʘHH݅ hh` ʍʍʍʍʍʍʍʍʍ`Lwʩʥ`Lʥ )`L̢ Wʭ /˥ ʬʥʐ %˭Lx̭ .I' S˥ ̀Lx̭ ʭ3 gʢ _ʠ o gʩ- oʢ _ʩ o g L˭L gʩ: oʢ _ʥ oḼ C o gL gʢ _ʩ e6 LL KL Aͭ  gʩ: o %L gʩ- oʭ _ʠ oL % LU (SAVVTR20))(PE DEC FLG)!LDY #$00kH!BUFCMP1 LDA ($FB),Y:BEQ BUFCMPYR!CMP BUF1,Y:BNE BUFCMPN\!INY:BNE BUFCMP1f!BUFCMPN LDY #$FFp!BUFCMPY RTS#; ; ; ; SCN OUT STRING ;FFCC$; ; ; ; MDM OUT STRING ; ; ; ; ; $OUTMSTR STX $FB:STY $FC7$LDA #$02:JSR $EFE1L$OUTMST0 LDY #$00p$OUTMST1 LDA ($FB),Y:BEQ OUTMST2~$JSR $FFD2$INY:BNE OUTMST1$OUTMST2 RTS%; ; ; ; DELAY TIMERS ; ; ; ; ;%RETIMS LDA $A'; ; ; ; ; INIT ; ; ; ; ; ;'INIT LDA #$00:STA STAT1S<'LDX #STRINIT0:LDY #STRINIT0:JSR OUTSSTRe'LDA #$05:LDX #$02:LDY #$00:JSR $FFBA$'LDA #$01:LDX #EIGHT:LDY #EIGHT:JSR $FFBD.'JSR $F409;; FFC08'; CHECK ERRB'; ERR, SET STAUTSSTR'LDX #STRINIT1:LDY #STRINIT1:JSR OUTMSTR,'JSR BUFLALN:LDA BUF1DX:BEQ INITB2H'LDX #STROK:LDY #STROKb'JSR BUFCMP:BNE INITB2'LDX #STRINITX:LDY #STRINITX:JSR OUTSSTR'INITB3 JSR RECB'LDX #STRINIT2:LDY #STRINIT2:JSR OUTM #STRINIT5:LDY #STRINIT5:JSR OUTSSTR !(LDX #STRIPIS:LDY #STRIPIS:JSR OUTSSTRF!(LDX #BUF1:LDY #BUF1:JSR OUTSSTRt!((LDX #STRINIT4:LDY #STRINIT4:JSR OUTSSTR!2(LDX #STR2:LDY #STR2:JSR OUTSSTR!)LDY #$00!)INITL1 LDA $0314,Y:STA SAVVTR,STA $0316"J)LDA #BRKOUT:STA $0317#T)LDA #OT:STA $031A#^)LDA #OT:STA $031B5#h)LDA #T:STA $031CL#r)LDA #T:STA $031Dg#|)LDA #CHKIN :STA $031E#)LDA #CHKIN :STA $031F#)LDA #CHKOUT:STA $0320#)LDA #CHKOUT:STA $0321#)LDA #CHN:TA $032C$)LDA #CLALL:STA $032D$*LDA #L:STA $0330%*LDA #L:STA $0331-%*LDA #S:STA $0332D%&*LDA #S:STA $0333r%0*LDY #$00:LDA #SAVBTR:CMP #$FF:BNE INITL3%5*LDA #$02:STA $D020:STA $D021:RTS%:*INITL3 LDA $0308,Y:STA SAVBTR,Y%D*INY:LDA #NMIRTN:STA $0318&*;LDA #NMIRTN:STA $0319'*CLI:JSR $F409:JMP $FFCC5':; ; ; ; ; KEIN 0308 ; ; ; ; ; ;V':KEIN LDA SECFLAG:BNE KEIN0g':JMP (SAVBTR)':KEIN0 JSR $FFD2:BNE KEIN1':KE JMP $FFD2':KEIN1 PHA:SBC #$80:BCS KEIN2'XBUFFILL JSR BUF1:LDY #$00:TYA:STA BUF1DX+bBUFFAL0 JSR RETIMSDlLDA #$01:STA TIMSWAThvBUFFAL1 JSR CHKTIMS:BNE BUFFADNBUFFAL2 LDA $029B:CMP $029CBEQ BUFFAL1LDA #$02:JSR $F1B8LDY BUF1DX:STA BUF1,YCMP #$00:BEQ BUFFAL1A BUF1,X:STA BUF1,YCPY BUF1DX:BEQ BUFFIL2*INX:INY:BNE BUFFIL1MBUFFIL2 DEC BUF1DX:BEQ BUFFIDNfLDY #$00:BEQ BUFFIL0BUFFIL3 INY:CPY BUF1DX:BEQ BUFFNLDA BUF1,Y:CMP #$0D:BEQ BUFFNSEC:BCS BUFFIL3BUFFN LDA #$00:STA BUF1,Y#$00:STA BUF1DX:RTSN BUFLAD4 DEY'X LDA BUF1,Y:CMP #$0D:BNE BUFLAD5:b INY:BNE BUFLAZl BUFLAD5 CPY #$00:BEQ BUFLArv DEY:SEC:BCS BUFLAD4 BUFLA LDX #$00 BUFLAF1 LDA BUF1,Y:STA BUF1,X INX:INY:CPY BUF1DX:BNE BUFLAF1 BUFLAOT LDA #$ ; ; ; ;#OUTSSTR STX $FB:STY $FC#JSR $FFCC:JMP OUTMST01"$BUF1 LDX #$00:TXAT,$BUFL STA BUF1,X:DEX:BNE BUFL\6$RTS}S$; ; ; ; E LINERS ; ; ; ; ;T$RECB LDA $029C:STA $029B:RTS^$DCDCHK LDA $DD01: #$10:RTSh$RESMIO JSR $F04F:JMP $1:STA TIMSBYT&%LDA #$04:STA TIMSWAT0%LDA #$00:STA TIMSBYT1:RTS1:%CHKTIMS LDA $A1LD%CMP TIMSBYT:BEQ TIMSDsN%CHKTIMS2 STA TIMSBYT:INC TIMSBYT1X%LDA TIMSBYT1b%CMP TIMSWAT:BCS TIMSBADl%TIMSD LDX #$00:RTSv%TIMSBAD LDX #$FF:RTST1S, RTSL'LDA #RBUF:STA $F7:LDA #RBUF:STA $F80V'LDA #TBUF:STA $F9:LDA #TBUF:STA $FADj'; BAUD CRECTI[t'LDA 678:BNE INITB1z~'LDA #73:STA 665:BNE INITB2'INITB1 LDA #43:STA 665'INITB2 JSR RECB'LDX #STRINITX:LDY #STRINITX:JSR OSTR'JSR BUFLALN:LDA BUF1DX:BEQ INITB3 'LDX #STROK:LDY #STROK/ 'JSR BUFCMP:BNE INITB3] 'LDX #STRINITX:LDY #STRINITX:JSR OUTSSTRr 'INITB4 JSR RECB (LDX #STRINIT3:LDY #STRINIT3:JSR OUTMSTR (JSR BUFFILN:LDA BUF1DX:BEQ INITB4 (LDXY!)INY:CPY #33:BCS INITFIX")TYA:ADC #SAVVTR:CMP #$00:BNE INITL27")LDA #$02:STA $D020:STA $D021:RTS[")INITL2 LDA $0314,Y:STA SAVVTR,Yv"")INY:CPY #33:BCC INITL1"')INITFIX SEI",)LDA #RPT:STA $0314"6)LDA #RPT:STA $0315"@)LDA #BRKOUT:STA $0322#)LDA #CHN:STA $0323$)LDA #CHRIN:STA $0324$)LDA #CHRIN:STA $03259$)LDA #CHROUT:STA $0326T$)LDA #CHROUT:STA $0327m$);LDA #IN:STA $032A$);LDA #IN:STA $032B$)LDA #T:STA $0328$)LDA #T:STA $0329$)LDA #CLALL:S LDA $0308,Y:STA SAVBTR,Y%N*LDA #KEIN:STA $0308&X*LDA #KEIN:STA $0309@&b*LDA #$73:STA KEIN01:LDA #$00:STA KEIN02g&l*LDA SAVBTR:CLC:ADC #$03:STA KE1&v*LDA SAVBTR1:ADC #$00:STA KE2&*;LDA #$7F:STA $DD0D&*;LDA #$80:BIT $DD0D&*; CMP #$0A:BEQ BUFFAL1INC BUF1DX:JMP BUFFAL0& BUFFADN RTSELBUFFILN LDA #$02:JSR $F04DUVJSR BUFFILLp`LDA BUF1DX:BNE BUFFIL0jBUFFIDN LDA #$00:STA BUF1DX:RTStBUFFIL0 LDA BUF1,Y~CMP #$0D:BNE BUFFIL3LDX #$00:INYBUFFIL1 LD -Y$"#5,A$:A$""ST04244#800:P$""P$:42450#5,"ATH"((SP),2):900:P$"OK"42506#@#P$""M#" ";a#A$:A$""5010w#A$(13)" ":#A$(20)A$;" ";:P$P$A$:5010#P$""5010#P$(P$,(P$)1): 6245&gY$: "";((TT),2);" BYTES TRANSFERRED.";CO$.'j8:1:5,"ATH"((SP),2):900:P$"OK"6250I't800:P$""P$:6260O'g'I(P$)1:I00:TB0' -A$(P$,I,1):A$"("6450'A$" "I0I'II1:I06410'('2I00'< X$FX$",R"(qF$""(s1,UN,15:8,UN,8,F$X$()t1,E:E08:1:"AILED TO OPEN "F$"";CO$:G)uP$::CC$"C":1300:E1^)vP$" "F$:600)5,"ATC";((SP),2):900:P$"OK"7040)Y0:Y$"":TT0)#5,A$:ST0A$""900:7100*R#5,A$:ST0A$""900:7250+\ "";((TT),2);" BYTES TRANSFERRED.";CO$++a#5,A$:A$""7265]+f8:1:5,"ATH"((SP),2):900:P$"OK"7265k+hTTTI200+k800:P$""TITT7275+pP$:800:P$""7280+z5,"ATC";((P),2):(P$)0P$":"P$, 8,UN,0,"$"P$, #8,A$,A$-& #8,A$,A$:ST0X(0):8::2000=-0 #8,A$,B$:X(A$(0))256(B$(0)):X;]-: #8,A$:A$""(13);:8230x-D B$:B$" "8::2000-N A$;:8250-(#TTTI100-2#ML12:TITT9010-<#*P":1,U,15,"S0:"F$:1:(F$),U:(F$),UTE 0;16G DISABLE .BYTE 0;17\ FLG .BYTE 0;18t PAUSETM .BYTE 0;19 TIMEFLG .BYTE 0;20 IDLMINS .BYTE 4;21 TMOHS .BYTE 0;22 TMOMINS .BYTE 85;23 SECFLAG .BYTE 1;24 -SAVABYT .BYBYTE 13 0 0 0 0 0 0 0 0 0 0 0B @STRINIT2 .BYTE 13:.BYTE "ATS0=1S41=1A"h ASTR2 .BYTE "6400":.BYTE 13 0 0 0 JSTROK .BYTE "OK":.BYTE 0 TSTRIPIS .BYTE "SERVER IP ADDR ":.BYTE 0 ^STRINIT3 .BYTE 13:.BYTE "ATV0I2":.BYTE 13 0 hSTRINIT4 .BYTE " P LDA #$00. STA $DC0B:STA $DC0A:STA $DC09:STA $DC086 RTSQ CLK2 LDA $DD0F: #$7F_ STA $DD0Fl LDA #$00 -STA $DD0B:STA $DD0A:STA $DD09:STA $DD08 RTS PCHKIDL1 LDA $DC0B ZCHKIDL10 LDA $DC0A:PHA dLDA $DC09:LDA $DC08nPLA:CM$05:RTS|CHKIDLE JMP (IDLCHVT)9o; ; ; ; WELCOME MESSAGE ; ; ; ; ;UpWELCOME TYA:PHA:TXA:PHAmzLDA $029D:STA $029ELDX #$7B:LDY #$E4:JSR OUTMXPLDA #$0D:LDX $029E:STA TBUF,X:INC $029ELDX #$78:LDY #$A3:JSR OUTMXPLDA #$0D:LDX $02N LDA #CHKIDL1:STA IDLCHVT) -LDA #CHKIDL1:STA IDLCHVT1AJSR CLK1:JSR CLK2fLDA #$00:STA DISABLE:STA TIMEFLGw(STA FLG:RTSc; ; ; ; TIMEOUT HLR ; ; ; ; ;dTIMEOUT TYA:PHA:TXA:PHAiLDA TIMEFLG:BNE TIMEOU1nJSR CLK1:JSR CLK2"  ";:5010$pFX0:X$",P,W":P$" ":600:850:P$""6000,$uP$:CC$"C":1300:E1<$zFX$(F$,2)^$(F$,1)" "F$(F$,2):6015$FX$",P"FX$",S"F$(F$,(F$)2):X$FX$",W"$F$""$P$" "F$:600$Y0:Y$"":TP$:6100%GLP$(P$,4):LP$"550 "P$:6250&LLP$"226 "Y1:Y$P$:6100A&QLP$"150 "("",(TT))P$::6400:6100r&VP0SPPL08,P$;:TTTTPL:6500:Y00:6100&[P0P6100&`Y0Y01:Y0Y0Y1TTTB6100&e#5,A$:ST0A$""900:((P$,I01),4)"BYTE"'FTB((P$,I1,I0I)):Y1TB10:.(d" "(TT):c(XFX0:X$",P,R":P$" ":600:850:P$""7000s(bFX$(F$,2)(g(F$,1)" "F$(F$,2):7015(lFX$",P"FX$",S"F$(F$,(F$)2):7100)ML12:X(MV):XE(MV1))(P$)07100*TTTT(P$):6500:650:XE07250*800A* P0P(P$,3)"550"P$:7250V**P0PP$""7100*/P0P(P$,4)"150 "("",(TT))P$::7100*4P0PP$::7100*>P0SP"?!":7100*H900:P$"OK"7290++@P$(P$,5):(P$)8(P$)168020%,JUN(P$):"URRENT DRIVE IS NOW";UN:2000^,T1,UN,15,"CD "P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000m,P$(P$,6),1,UN,15,"S0:"P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000, P$(P$,6):-#SY(65532):BC8-#SY2269140.#X(56834):56834,X1:(56834)X19140#.#BC15:SY227:56834,X:X.#(14);"EQUIRES A 64 WITH WIFTINK/INK232":h.Pß5,2,0,(8).Zá#5,A$:A$""A$;.dáA$:A$""5,A$;.nÉ 50010.U8:F$"FTTE 0 0 +7250*\ "";((TT),2);" BYTES TRANSFERRED.";CO$+a#5,A$:A$""7265:+f8:1:5,"ATH"((SP),2):900:P$"OK"7265H+hTTTI200c+k800:P$""TITT7275}+pP$:800:P$""7280+z5,"ATC";((P),2):900:P$"OK"7290++@P$( 15:8,UN,8,F$X$)t1,E:E08:1:"AILED TO OPEN "F$"";CO$:$)uP$::CC$"C":1300:E1;)vP$" "F$:600g)5,"ATC";((SP),2):900:P$"OK"7040z)Y0:Y$"":TT0)#5,A$:ST0A$""900:7100)ML12:X(MV):XE(MV1) X$FX$",R"(qF$""(s1,UN,15:8,UN,8,F$X$()t1,E:E08:1:"AILED TO OPEN "F$"";CO$:G)uP$::CC$"C":1300:E1^)vP$" "F$:600)5,"ATC";((SP),2):900:P$"OK"7040)Y0:Y$"":TT0)#5,A$:ST0A$""900: YTES TRANSFERRED.";CO$ 'j8:1:5,"ATH"((SP),2):900:P$"OK"6250&'t800:P$""P$:6260,'D'I(P$)1:I00:TB0b' +A$(P$,I,1):A$"("6450s'A$" "I0I'II1:I06410'('2I00'<((P$,I01),4)"BYTE"'FTB(P$)0P$":"P$, 8,UN,0,"$"P$, #8,A$,A$-& #8,A$,A$:ST0X(0):8::2000=-0 #8,A$,B$:X(A$(0))256(B$(0)):X;]-: #8,A$:A$""(13);:8230x-D B$:B$" "8::2000-N A$;:8250-(#TTTI100-2#ML12:TITT9010-<#0A$""900:6100$800$FX0P0SPP106200%1,UN,15:8,UN,8,"@0:"F$X$Y%$1,E:E08:1:"AILED TO OPEN "F$"";CO$:6250b%.FX1{%8PL(P$):P0P6230%=PL06100%BY00:Y1PL0Y$P$:6100%GLP$(P$,4):LP$"550 BYTE 13 0 0 0 0 0 0 0 0 0 0 0B @STRINIT2 .BYTE 13:.BYTE "ATS0=1S41=1A"h ASTR2 .BYTE "6400":.BYTE 13 0 0 0 JSTROK .BYTE "OK":.BYTE 0 TSTRIPIS .BYTE "SERVER IP ADDR ":.BYTE 0 ^STRINIT3 .BYTE 13:.BYTE "ATV0I2":.BYTE 13 0 hSTRINIT4 .BYTE " P800:P$""P$:4245 #5,"ATH"((SP),2):900:P$"OK"4250##P$""*#" ";>#A$:A$""5010T#A$(13)" ":~#A$(20)A$;" ";:P$P$A$:5010#P$""5010#P$(P$,(P$)1):"  ";:5010#pFX0:X$",P,40-#X(56834):56834,X1:(56834)X19140.#BC15:SY227:56834,X:5.#(14);"EQUIRES A 64 WITH WIFTINK/INK232":E.Pß5,2,0,(8)].Zá#5,A$:A$""A$;t.dáA$:A$""5,A$;.nÉ 50010.U8:F$"FTP":1,U,15,"S0:"F$:1:(F$),U:(F$N LDA #CHKIDL1:STA IDLCHVT) +LDA #CHKIDL1:STA IDLCHVT1AJSR CLK1:JSR CLK2fLDA #$00:STA DISABLE:STA TIMEFLGw(STA FLG:RTSc; ; ; ; TIMEOUT HLR ; ; ; ; ;dTIMEOUT TYA:PHA:TXA:PHAiLDA TIMEFLG:BNE TIMEOU1nJSR CLK1:JSR CLK2P$,5):(P$)8(P$)168020,JUN(P$):"URRENT DRIVE IS NOW";UN:2000;,T1,UN,15,"CD "P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000J,P$(P$,6),1,UN,15,"S0:"P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000, P$(P$,6):(P$)0P$":"P$, 8,UN,0,"$")(P$)07100)TTTT(P$):6500:650:XE07250)800* P0P(P$,3)"550"P$:72503**P0PP$""7100e*/P0P(P$,4)"150 "("",(TT))P$::7100{*4P0PP$::7100*>P0SP"?!":7100*H7100*R#5,A$:ST0A$""900:((P$,I01),4)"BYTE"'FTB((P$,I1,I0I)):Y1TB10:.(d" "(TT):c(XFX0:X$",P,R":P$" ":600:850:P$""7000s(bFX$(F$,2)(g(F$,1)" "F$(F$,2):7015(lFX$",P"FX$",S"F$(F$,(F$)2):((P$,I1,I0I)):Y1TB10: (d" "(TT):@(XFX0:X$",P,R":P$" ":600:850:P$""7000P(bFX$(F$,2)r(g(F$,1)" "F$(F$,2):7015(lFX$",P"FX$",S"F$(F$,(F$)2):X$FX$",R"(qF$""(s1,UN,900:P$"OK"7290++@P$(P$,5):(P$)8(P$)168020%,JUN(P$):"URRENT DRIVE IS NOW";UN:2000^,T1,UN,15,"CD "P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000m,P$(P$,6),1,UN,15,"S0:"P$:1,E,E$,E1,E2:E,E$,E1,E2:1:2000, P$(P$,6):"P$:6250%LLP$"226 "Y1:Y$P$:6100&QLP$"150 "("",(TT))P$::6400:6100O&VP0SPPL08,P$;:TTTTPL:6500:Y00:6100^&[P0P6100&`Y0Y01:Y0Y0Y1TTTB6100&e#5,A$:ST0A$""900:6245&gY$: "";((TT),2);" BTE 0 0 BUF12FL .BYTE 0* LASTDCD .BYTE 0@ IDLCHVT .BYTE 0 0V @@ -72,9 +70,9 @@ Y$ )RBUF $CE00 *TBUF $CF00 +; ; ; ; ; STRING TABLE ; ; ; ; ; ; -,EIGHT .BYTE 8 0 6STRINIT1 .BYTE 13:.BYTE "ATHZQ0V1X1F0E0N0R0":.ORT ":.BYTE 0 mSTRINIT5 .BYTE 13 0D rSTRINIT0 .BYTE "TELNETD 1.0 INIT."c |STRINITX .BYTE ".":.BYTE 0 STRATHA0 .BYTE "+++":.BYTE 0 STRATHA1 .BYTE "ATH":.BYTE 13 0 ; ; ; ; CHECK METHODS ; ; ; ; ; CLK1 LDA $DC0F: #$7F STA $DC0FP IDLMINS:RTS/CHKTMO2 LDA $DD0B:CMP TMOHS:BEQ CHKTMO2M]PHP:LDA $DD0A:LDA $DD09:LDA $DD08:PLP:RTSxCHKTMO2M LDA $DD0A:PHALDA $DD09:LDA $DD08PLA:CMP TMOMINS:RTSCHKPAS1 LDA $DC0B:LDA $DC0A"LDA $DC09:PHA:LDA $DC08,PLA:CMP # 9E:STA TBUF,X:INC $029EJSR $F028-PLA:TAX:PLA:TAY:RTSH8OUTMXP STX $FB:STY $FCULLDY #$00lVOUTMXL LDA ($FB),Y{`BEQ OUTMXDjLDX $029E:STA TBUF,X:INC $029EtINY:BNE OUTMXL~OUTMXD RTS; ; ; ; TIMEOUT SETUP ; ; ; ; ; TIMEI B0:TT0:Y00:Y110$#5,A$:ST0A$""900:6100%800 %FX0P0SPP106200A%1,UN,15:8,UN,8,"@0:"F$X$|%$1,E:E08:1:"AILED TO OPEN "F$"";CO$:6250%.FX1%8PL(P$):P0P6230%=PL06100%BY00:Y1PL0Y$ -900:VR(P$):VR2.0"IMODEM INIT FAILED: ";P$:A900:P$"OK"203X#5,A$:A$""2455,"ATI2";CR$;:900:IP$P$:P$"OK"(P$)8245P$"":I1(IP$)IFMID$(IP$,I,1)="."THENIP$=LEFT$(IP$,I-1)+","+MID$(IP$,I+1) I:"OUR U$lML:P$E$"XERR:";CO$;P$:P$OP$:600#vZOP$P$:ML9:C8$(((MV8)),2):PN$(((P$)),2)5,"ATS42=";C8$;"T+";PN$:5,P$:E$"OK":VR3E$C8$ML:P$E$"XERR:";P$:P$OP$:650 --- GET P$ FROM SOCKET P P$"": t(P$,3)EC$P$:881u(P$,4,1)"-"881vE --- GET E$ FROM MODEM, OR ERROROE$""WMLE$""P$E$"OMM ERROR. XPECTED ";E$;", OT ";P$;CO$;"" ---- LOW LVL PACKET READPR0:#5,P$:P$""9305,  LOOPQU$(34): BEGIN!3AT$"":XB0BA0XBBAAT$"S43="((XB),2)K#5,A$:A$""1020L5,"ATH"AT$"&D10&M13&M10CP";QU$;HO$;":";((PO),2);QU$:E0Q900:SP0:P$"OK"1020V(P$)8(P$,8)"CONNECT "P((P$,9)):1200X 9:1210BAXB:UM19,1:5,"AT":9000:9000!XB24001210;NP0:(2614)0NP20rBAXB:2576,10:2578,(59490NP):2579,(59491NP)2582,170:2583,1:NP02582,1545,"AT":9000:9000850:P$""1240P$UN$"""S 00 -UN$"":PA$"":1230#5,A$:A$""1300%P$"":600:A0EAA1:850:P$""A601305UP$""1300tP$::(P$,3)"227"1300(A((P$,1)):A48A57P$(P$,(P$)1):13202X0(P$)<A((P$,X0,1)):A47A58X0X0 (P$,I1)"."(P$,I1)s:H0$P$H2$:R00x#5,P$:P$""1400=}5,"AT";CC$;QU$;H0$;QU$:900r(P$)8(P$,8)"CONNECT "SP((P$,9)): 1420R011300R0R01:"RROR: ";P$:"ETRY TO CONNECT TO ";H0$;"";CO$:1400#5,A$:A P$"DIR"(P$,4)"DIR "4000:2000%P$"EXIT"P$"QUIT"5,"ATZ":900:5:K(P$,4)"DEL "P$"DELE"(P$,4)p(P$,3)"CD "P$" "(P$,4)(P$,4)"LCD "8000(P$,5)"LDEL "8100 (P$,4)"LDIR"8200 (P$,4)"GET "F ERVER COMMANDS:";CO$4600:850:P$:2000 ) P$" ":600:850:P$""4000S P$:CC$"&M10&D10&M13CP":1300:E1v P$"":600:850:P$""4010 PRINTP$:P$=" "+IP$+",198,76":GOSUB600:GOSUB850:IFP$=""THEN4020 #5,A$:STY$P$:4100!rP0PP$""4100"|P0P(P$,4)"226 "Y1:Y$P$:4100F"P0P(P$,4)"150 "4100:PRINTP$:GOTO4100]"P0SPP$""4235"Y20:Y1Y11:S80(P$,34):4100"P$:4100"P0P4100"Y2Y21:Y0(Y12Y2Y3)4100" IP ADDRESS IS: ";IP$I*HO$"FTP.ZIMMERS.NET":PO21:UN$"ANONYMOUS":PA$"MY@EMAIL.COM"h+HO$="192.168.1.112":PO=21w, GET INFO6:"EQUEST ARMS:"A " 1) RL : FTP://";HO$B " 2) SERNAME : ";UN$C " 3) ASSWORD : ";PA$ X3"NTER ASSWORD: ";:5000:PA$P$:300ZX4"NTER OUTPUT DEVICE/UNIT: ";:5000:UN(P$):300d 300V --- TRANSMIT P$ TO THE OPEN SOCKET !XOP$P$:ML9:C8$(((MV8)),2):E$"OK":VR3E$C8$b5,"ATS42=";C8$;"TP+";QU$;P$;Q E0'*930:P0PP00"NEXPECTED PACKET ID: ";P0;"/";P:>4P00E1:: FAILD>MRPC0t\PCPC1:800:P$""E1PC60860f(P$)4(P$,4,1)"-"pEC$(P$,3):EC(EC$)qPC0r800:P$""E1PC60882sP$""E1870 (17);ML6:P0(MV2):P1(MV4):P2(MV6);PL(MV0):CR(MV1):C8(MV8)PP00P2C8985`P10P$""qP00CR0#5,P$:P$""985"-";CO$:5,"ATL":945"XPECTED ";E$;", GOT ";A$: --- THE MAIN (P$)0E3EE1:11053["NABLE TO CONNECT TO ";HO$;" PORT";PO;"";CO$:300@`ITTI40_jP90:800:P$""ITTI10otTIIT1130"ONNECTED TO ";HO$;" ON CHANNEL";P;", TANDBY...";CO$XB96001205UM:UM3:(789)234UM ERNAME: ";:5000:UN$P$:P$""1230*P$" "UN$:600:850:P$""1240eP$:(P$,3)"331""SERNAME REJECTED?!";CO$:5:PA$"""ASSWORD: ";:5000:PA$P$:P$""1260P$" "PA$:600:850:P$""1270P$:(P$,3)"230"20 1:1340 FX2((P$,X01)):P$(P$,X01):X0(P$):PA((P$,X0,1)):A47A58X0X01:1360yZX1((P$,X01)):P$(P$,X01):H2$":"(((X1256)X2),2)_P$(P$,5)dA((P$,1)):A48A57P$(P$,2):1380nI1(P$):(P$,I,1)","P$ $""1420 5,"ATC";((P),2):900:P$"OK"E0:?"ETRY TO CHANGE BACK TO ";H0$;"";CO$:1420["OMMAND (?): ";:5000((DD)16)0"OST CONNECTION";CO$:P$"QUIT"P$""800:P$:2000P$"LS"(P$,3)"LS "4000:2000 $(P$,5):6000:2000(P$,4)"PUT "F$(P$,5):7000:2000(P$"?"P$"HELP"R P$"HELP""GET PUT LS CD DIR DEL"y!P$"HELP""LCD LDIR LDEL QUIT""P$"HELP""SE ,S AND ,P IN GET/PUT FILENAMES!"#P$"HELP""ELOW ARE S 0A$""900:4025 P$:P$"":600$!GOSUB900:IFLEFT$(P$,5)="RING "THEN4040P!Y0:Y$"":Y10:Y20:Y35:BA4800Y310!GET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4100!A$:A$" "A$"":"BORTED.":4250!6800!hP0PY1P$"" D " 4) ISK EVICE:";UNILH4/rHO$"":P$"1":400]|:"YPE A NUMBER OR ΠTO CONNECT:";s5000:P$""1000X(P$):X1XLH300X1"NTER : FTP://";:5000:HO$P$:300X2"NTER SERNAME: ";:5000:UN$P$:300 +,EIGHT .BYTE 8 0 6STRINIT1 .BYTE 13:.BYTE "ATHZQ0V1X1F0E0N0R0":.W":P$" ":600:850:P$""6000 $uP$:CC$"C":1300:E1$zFX$(F$,2);$(F$,1)" "F$(F$,2):6015o$FX$",P"FX$",S"F$(F$,(F$)2):X$FX$",W"|$F$""$P$" "F$:600$Y0:Y$"":TB0:TT0:Y00:Y110$#5,A$:ST),U #8,A$,A$,& #8,A$,A$:ST0X(0):8::2000-0 #8,A$,B$:X(A$(0))256(B$(0)):X;:-: #8,A$:A$""(13);:8230U-D B$:B$" "8::2000d-N A$;:8250r-(#TTTI100-2#ML12:TITT9010-<#-#SY(65532):BC8-#SY22691 9E:STA TBUF,X:INC $029EJSR $F028-PLA:TAX:PLA:TAY:RTSH8OUTMXP STX $FB:STY $FCULLDY #$00lVOUTMXL LDA ($FB),Y{`BEQ OUTMXDjLDX $029E:STA TBUF,X:INC $029EtINY:BNE OUTMXL~OUTMXD RTS; ; ; ; TIMEOUT SETUP ; ; ; ; ; TIMEIP$, #8,A$,A$,& #8,A$,A$:ST0X(0):8::2000-0 #8,A$,B$:X(A$(0))256(B$(0)):X;:-: #8,A$:A$""(13);:8230U-D B$:B$" "8::2000d-N A$;:8250r-(#TTTI100-2#ML12:TITT9010-<#-#SY(65532):BC8-#SY22691 + --- THE MAIN LOOPQU$(34): BEGIN!DAT$"":XB0BA0XBBAAT$"S43="((XB),2)\#5,A$:A$""1020L5,"ATH"AT$"&D10&M13&M10CP";QU$;HO$;":";((PO),2);QU$:E0Q900:SP0:P$"OK"1020V(P$)8(P$,8)"CONNECT "P((P$ sP$""E1870t(P$,3)EC$P$:881*u(P$,4,1)"-"8810vV --- GET E$ FROM MODEM, OR ERROR`E$""hMLE$""P$E$"OMM ERROR. XPECTED ";E$;", OT ";P$;CO$;"" ---- LOW LVL PACKET READPR0:#5,P$:P$ "PUT "F$(P$,5):7000:2000P$"?"P$"HELP"/ P$"HELP""GET PUT LS CD DIR DEL"V!P$"HELP""LCD LDIR LDEL QUIT""P$"HELP""SE ,S AND ,P IN GET/PUT FILENAMES!"#P$"HELP""ELOW ARE SERVER COMMANDS:";CO$4600:850 8$;"TP+";QU$;P$;QU$.lML:P$E$"XERR:";CO$;P$:P$OP$:6004vkOP$P$:ML9:C8$(((MV8)),2):PN$(((P$)),2)5,"ATS42=";C8$;"T+";PN$:5,P$:E$"OK":VR3E$C8$ML:P$E$"XERR:";P$:P$OP$:650 --- GET P$ FROM SO 400#5,A$:A$""14205,"ATC";((P),2):900:P$"OK"E0:P"ETRY TO CHANGE BACK TO ";H0$;"";CO$:1420l"OMMAND (?): ";:5000P$""800:P$:2000P$"LS"(P$,3)"LS "4000:2000P$"DIR"(P$,4)"DIR "4000:20 ASSWORD : ";PA$D " 4) ISK EVICE:";UN&ILH4@rHO$"":P$"1":400n|:"YPE A NUMBER OR ΠTO CONNECT:";5000:P$""1000X(P$):X1XLH300X1"NTER : FTP://";:5000:HO$P$:300X2"NTER SERNAME: ";: A47A58X0X01:1340FX2((P$,X01)):P$(P$,X01):X0(P$)KPA((P$,X0,1)):A47A58X0X01:1360ZX1((P$,X01)):P$(P$,X01):H2$":"(((X1256)X2),2)_P$(P$,5)dA((P$,1)):A48A57P$(P$,2):1380nI1(P$): 900:P$"OK"235;VR(P$):VR2.0"IMODEM INIT FAILED: ";P$:R900:P$"OK"203i#5,A$:A$""2455,"ATI2";CR$;:900:IP$P$:P$"OK"(P$)8245P$"":I1(IP$)IFMID$(IP$,I,1)="."THENIP$=LEFT$(IP$,I-1)+","+MID$(IP$,I+ UN$"""SERNAME: ";:5000:UN$P$:P$""1230;P$" "UN$:600:850:P$""1240vP$:(P$,3)"331""SERNAME REJECTED?!";CO$:5:PA$"""ASSWORD: ";:5000:PA$P$:P$""1260P$" "PA$:600:850:P$""1270P$: ":600!GOSUB900:IFLEFT$(P$,5)="RING "THEN4040-!Y0:Y$"":Y10:Y20:Y35:BA4800Y310`!GET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4100!A$:A$" "A$"":"BORTED.":4250!6800!hP0PY1P$""Y$P$:4100!rP0PP$""4100 ,9)):1200 X(P$)0E3EE1:1105D["NABLE TO CONNECT TO ";HO$;" PORT";PO;"";CO$:300Q`ITTI40pjP90:800:P$""ITTI10tTIIT1130"ONNECTED TO ";HO$;" ON CHANNEL";P;", TANDBY...";CO$XB96001205UM:UM3 ""9305,(17);'ML6:P0(MV2):P1(MV4):P2(MV6)LPL(MV0):CR(MV1):C8(MV8)aP00P2C8985qP10P$""P00CR0#5,P$:P$""985"-";CO$:5,"ATL":945"XPECTED ";E$;", GOT ";A$: :P$:2000  P$" ":600:850:P$""40000 P$:CC$"&M10&D10&M13CP":1300:E1S P$"":600:850:P$""4010 PRINTP$:P$=" "+IP$+",198,76":GOSUB600:GOSUB850:IFP$=""THEN4020 #5,A$:ST0A$""900:4025 P$:P$" CKET P P$"":E08*930:P0PP00"NEXPECTED PACKET ID: ";P0;"/";P:O4P00E1:: FAILU>^RPC0\PCPC1:800:P$""E1PC60860f(P$)4(P$,4,1)"-"pEC$(P$,3):EC(EC$)qPC0r800:P$""E1PC60882 00P$"EXIT"P$"QUIT"5,"ATZ":900:5:((P$,4)"DEL "P$"DELE"(P$,4)M(P$,3)"CD "P$" "(P$,4)f(P$,4)"LCD "8000(P$,5)"LDEL "8100 (P$,4)"LDIR"8200 (P$,4)"GET "F$(P$,5):6000:2000(P$,4) 5000:UN$P$:300/X3"NTER ASSWORD: ";:5000:PA$P$:300kX4"NTER OUTPUT DEVICE/UNIT: ";:5000:UN(P$):300u 300V --- TRANSMIT P$ TO THE OPEN SOCKET !XOP$P$:ML9:C8$(((MV8)),2):E$"OK":VR3E$C8$b5,"ATS42=";C (P$,I,1)","P$(P$,I1)"."(P$,I1)s:H0$P$H2$:R00-x#5,P$:P$""1400N}5,"AT";CC$;QU$;H0$;QU$:900(P$)8(P$,8)"CONNECT "SP((P$,9)): 1420R011300R0R01:"RROR: ";P$:"ETRY TO CONNECT TO ";H0$;"";CO$:1 1) I:"OUR IP ADDRESS IS: ";IP$Z*HO$"FTP.ZIMMERS.NET":PO21:UN$"ANONYMOUS":PA$"MY@EMAIL.COM"y+HO$="192.168.1.112":PO=21, GET INFO6:"EQUEST ARMS:"A " 1) RL : FTP://";HO$B " 2) SERNAME : ";UN$C " 3) (P$,3)"230"2000 +UN$"":PA$"":1230#5,A$:A$""13006P$"":600:A0VAA1:850:P$""A601305fP$""1300P$::(P$,3)"227"1300(A((P$,1)):A48A57P$(P$,(P$)1):13202X0(P$)<A((P$,X0,1)):!|P0P(P$,4)"226 "Y1:Y$P$:4100#"P0P(P$,4)"150 "4100:PRINTP$:GOTO4100:"P0SPP$""4235a"Y20:Y1Y11:S80(P$,34):4100o"P$:4100~"P0P4100"Y2Y21:Y0(Y12Y2Y3)4100"Y$"#5,A$:A$""ST04244" :(789)234UM9:1210BAXB:UM19,1:5,"AT":9000:90002XB24001210LNP0:(2614)0NP20BAXB:2576,10:2578,(59490NP):2579,(59491NP)2582,170:2583,1:NP02582,1545,"AT":9000:9000850:P$""1240P$ MJIBLACMCAMPPPKAAAJBPOOGPONA";3b "ACOGPPMOBECANAOOKFPPMJCDNAAEKFPOMJLMJANIPADICAMMPPKJPPINAPCAINBACAGA"3l "KNBECADIOJIAINBECACAMPPPINBCCAKAAAJBPOOGPONAACOGPPMOBECANAPDKFPPMJCD"3v "NAAEKFPOMJLMJAKANAMIEMMMPPKOBACACAMJPPKAAALJLMCCCANCPPMINAPHEMMMPP#)t**h++,E*` <*+7,!,),#` ` ;CO$:300`ITTI409jP90:800:P$""ITTI10ItTIIT1130l"ONNECTED TO "HO$"";CO$XB96001205UM:UM3:(789)234UM9:1210BAXB:UM19,1:5,"AT":6000:6000XB24001210 S`W ' % 1(  Y! /)Vn See Y! Q VFW ( H) *` ' )V ` d(K |!K /)L` Lh t! (' (e ) Y o(֝e ) g( J %e @@ -115,8 +113,14 @@ $ LAADLIFAIKGAKJPPINAPCAKNBCCAINBACAKNBDCAINBGCAKNBCCAIN"W2D "BFCAGAKOBACACAMJPPKJCDIFPPKJLMIFPOKAAALBPOCANCPPOGPONAACOGPPKFPPMNBG"2N "CANAAFKFPOMNBFCAJAOFEMMMPPKOAPCACAMGPPKJCCIFPPKJLMIFPOKOBACAOAPPNAAO"2X "KAAACAMPPPJJLMCCMINAPHEMMMPPCAMPPPINBECA X$MN$M1$:400',OM12MN1MNMN1:160J6OM11MN(MN$)MNMN1:160[@OM13OMNiJOM1220|OMNO:200:OMNTO2O:O500,600,700,350,1000,2000,800:^VMVM1:VM2VM0h100O0:A$:A$""400A(A$)* I1(X !D:X$(((A$)),2):B!XB$"EST. ":XDU:X$DD$:550:DUX:DD$X$:100}!"[]INGLE OR []ULTI-ILE:  ";:X$""(13):400!F1$("INGLE ULTIPLE"F1$" ",((O1)8)1,8):F1$:!"[]ORMAL OR []OMPRESSED:  ";:X$""(13):400 ;"> ";:CO$""#>A$:A$""830,#HA(A$):A13900f#RA20CO$""" "A$A$" ";:CO$(CO$,(CO$)1):830#\A31A96CO$CO$A$:A$;" ";:830#fA191A218CO$CO$A$:A$;" ";:830#p830#1::""SP$:"";#CO$""100 2,A$,B$:X(A$(0))256(B$(0)):X;4%#2,A$:A$""(13);:970F%B$:B$" "T%A$;:990%1,DU,0,"$"DD$":Z=U":I135:#1,A$::#1,B$:1%X(A$(0))256(B$(0)):%1900:VL0:F$""(SP$,(F$))F$100&F$F$",S,R":2,D 2,E,E$,E1,E2:E661250/'B2,"B-P";3;0:MV,3:MV1,255:(ML(03))S'LMV1,3:(F2$,1)""MV1,255o'VMV,1:(ML(23)):EFST'`(MV)255"NVALID RCHIVE!":1700'jVM01190'tT0T01:(ML(43)):(MV2)01250'~T1T11:VM21250 ,"I"DD$":":15,"I"SD$)1:15:2:3@)T00"ALIDATED";(T0T1);"/";T0;" SECTORS"Z):"IT RETURN:  ";r)A$:A$(13)1740{)100)VL$"":E0:VLVL1:(F1$,1)""IW$((VL),2)"-")(F1$,1)""VL11850)(F$,1)"R"185 ENING ARCHIVE.":50*X1+lF$"":SUDUSD$DD$"RIVES MUST DIFFER!":50:`+v"ILENAME: ";:X$(F1$,1):X$"""?-";|+1,0:1,F$:1:X(F$):+X16(X$""X14)"ILENAME TOO LONG!":"";:1900++1900:VL0:F$""100,F$ 00-MV1,3:(F2$,1)""MV1,255'- 2,"B-P";3;0:MV,3:(ML(03))O-%(MV)255"OMPRESS RROR!":1700g-4MV1,1:(ML(13))->TTTT1:TT200(F1$,1)""1800:TT0-HE66S255SS1:2040-RE70S0TT1:S0:2040-\:"ONE! BKCBKMBBCANAADEMBKCBOOBBCALJLMCCKMBCCAMIJBPOIINBPOPADIKJ"_/ "AAINBHCAKNBECAMJIALAANMJIAPAAJOOBCCAOOBECAEMGOCAKAAAKNBECAJBPOMOBBCA"/ "OOBCCAKNBCCABIGFPOIFPOKNBDCAGFPPIFPPEMFBCAOOBHCAKNBECAMJIAPANHMJIBJA"/ "AKMJPPPAMPOOBECAEMGOCAMJABNAAPKNBECABI LBPO"'1 "OGPONAACOGPPINBECAMJIBLACOKAAALBPOOGPONAACOGPPKMBCCAOOBCCANJLMCCPAAD"s1& "EMMHCBMOBECANAODKFPPMNBGCANAAFKFPOMNBFCALADKLIFAMBKNBECADIOJIAINBECA"10 "KAAALBPOOGPONAACOGPPKMBCCAOOBCCANJLMCCPAADEMMHCBMOBECANAONKFPPMNBGCA" 2: "NAAFKFPOMNBFCA $):B((X$,I,1)):((AB)((A 128)B))(O0)OI: I:O0400@ s B$"OURCE ":XSU:X$SD$:550:SUX:SD$X$:100 &""B$"NIT :"(X):""14);:1,0:1,A$:1 0:X(A$):X7X30"";:550 :""B$"RIVE: "X$:""14);:1,0:1,A$:1Lfhhh@H ݭ)@  h(`)h(`HhL)hސ` H+ޭxɍɍHəh F h(` /HLV  G 83400:P0P1,(ML1536)256::3400:P,0::3400j MVML(35):SP$" ":3300SU8:SD$"0":DU8:DD$"0":F1$"INGLE ":F2$"ORMAL ":MN1"MUTIL V4.0":"O IMMERMAN":"NDRE ACHAT" -("...LANET NK.": A #DO:STA $031D LDA #DOPUT:STA $03267LDA #DOPUT:STA $0327RLDA #DOCLAL:STA $032CmLDA #DOCLAL:STA $032D~<E JSR UPDCDPLA:TAY:PLA:PLP:RTS;;;;;;;;;;;;NMI PHA:TXA:PHA:TYA:PHA;LDX #%00000011:STX COMM ; DISABLE STU A:TAY:PLA:TAX:PLA:RTI;;;;;;;;;;;;;;;;,SAVBYTE .BYTE 0>UPDCD PHP:PHA^LDA $DD03:A #$10:STA $DD03|LDA STATUS: #64:BEQ DCDLDA $DD01:A #$10:STA $DD01PLA:PLP:RTSDCD LDA $DD01: #$EF:STA $DD01OPLA:PLP:RTSo;;;;;;; bPHP:PHALDA $BA:CMP #$02:BNE NOCLOS4DOCLOS2 LDA #3 ; 00000011RSTA COMM ; DISABLE STUFFxLDA NMINV1:CMP #NMI:BNE NOCLOSSEILDA OLDNMISTA NMINVLDA OLDNMI1STA NMINV1TYA:PHA:LDY #20INCLO LDA VECS,Y: BYTE 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.FOLDNMI .BYTE 71 254AJRCOUNT .BYTE 0STERRS .BYTE 0f^ZPXMO .BYTE 0yhZPXMF .BYTE 0ٙ NOP;:1,8,15,"S0:SWIFTDRVR*":1:"SWIFTDRVR.BAS",8ٙ2 NOP;:"SWIFTDRVR.BAS",8ٙ3 NOP;"SWIFT :PT0:MVML18" "NITIALIZING MODEM..."9 #5,A$:A$""203k 5,CR$;"ATHZ0&P0F0E0";CR$;:900:P$"OK"203 #5,A$:A$""208 5,CR$;"ATE0N0R0V1Q0";CR$; 900:P$"OK"208 5,"ATE0V1X1F3Q0S40=248S0=1S41=0I4";CR$;(19);:L9248* ,87:52,870 P$"OK":186,(254):BA1200:XB1200b CR$(13):(14);:9100:53280,254:53281,246 CO$"":SY226ML49152:665,73((678)30):UMML2048:XB9600 !SY227ML49152:UMML2048 -#SY34ML22273:(ML)765:"PMLVIC.BIN",(254 4),1:UM:A 2S80:SY61ML4864:981,15:S8(215)128:S812830643u <SY61(ML1)2175:"PML128.BIN",(254),1: FSY61S8128XB2400:CO$(159) PSY226UM0UM:UM3:X(789):UM9:X234XB1200 ZSY3456579,0: WHY DOES THIS 50:80:1002TI$"000000"*<TI200600FnPVM0: VM$(3):VM$(0)"ONE":VM$(1)"UTO":VM$(2)"NLY":d"MUTIL ENU":"[] OURCE EVICE:";SU;", ";SD$n"[] ESTINATION EVICE:";DU;", ";DD$x"[] ORMAT: ";F1$;" / ";F2$"[]   +("...LANET NK.": A #DO:STA $031D LDA #DOPUT:STA $03267LDA #DOPUT:STA $0327RLDA #DOCLAL:STA $032CmLDA #DOCLAL:STA $032D~<E JSR UPDCDPLA:TAY:PLA:PLP:RTS;;;;;;;;;;;;NMI PHA:TXA:PHA:TYA:PHA;LDX #%00000011:STX COMM ; DISABLE STU A:TAY:PLA:TAX:PLA:RTI;;;;;;;;;;;;;;;;,SAVBYTE .BYTE 0>UPDCD PHP:PHA^LDA $DD03:A #$10:STA $DD03|LDA STATUS: #64:BEQ DCDLDA $DD01:A #$10:STA $DD01PLA:PLP:RTSDCD LDA $DD01: #$EF:STA $DD01OPLA:PLP:RTSo;;;;;;; bPHP:PHALDA $BA:CMP #$02:BNE NOCLOS4DOCLOS2 LDA #3 ; 00000011RSTA COMM ; DISABLE STUFFxLDA NMINV1:CMP #NMI:BNE NOCLOSSEILDA OLDNMISTA NMINVLDA OLDNMI1STA NMINV1TYA:PHA:LDY #20INCLO LDA VECS,Y: BYTE 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.FOLDNMI .BYTE 71 254AJRCOUNT .BYTE 0STERRS .BYTE 0f^ZPXMO .BYTE 0yhZPXMF .BYTE 0ٙ NOP;:1,8,15,"S0:SWIFTDRVR*":1:"SWIFTDRVR.BAS",8ٙ2 NOP;:"SWIFTDRVR.BAS",8ٙ3 NOP;"SWIFT  FTP64SW 19200B 2.0+< UPDATED 10/13/2021 12:54A]254,8:(186)7254,(186)s9100: SET SY,BC +SY6158,254: SY34X23777:X,170:(X)170"<16K": SY227(51201)351200 5,2,0,(BC):(65532)3456,87:54 ),1: +&SY3436879,27:CO$(31)0 +(SY22645] +*(ML1)2095:"PML64.BIN",(254),1: +,UM0(UM1)245:"UP9600.BIN",(254),1: +-SY22750 +/(ML1)2095:"PML64.BIN",(254),1: 1UM0(UM1)35:"SWIFTDRVR.BIN",(25  WORK8 dMVML18:MV14,8:DD56577:SY34DD37136: FIX BUFAP> eD fJ n xCO$;" V1.7":"EQUIRES IMODEM FIRMWARE 2.0+" "Y O IMMERMAN (BO@ZIMMERS.NET)":: ---- ZIMODEM SETUP UN(254):IP$"":CR$(13)(10) PH0 50:80:1002TI$"000000"*<TI200600FnPVM0: VM$(3):VM$(0)"ONE":VM$(1)"UTO":VM$(2)"NLY":d"MUTIL ENU":"[] OURCE EVICE:";SU;", ";SD$n"[] ESTINATION EVICE:";DU;", ";DD$x"[] ORMAT: ";F1$;" / ";F2$"[]   0.D TELNETML.BIND#;;TERMINAL ML 64 BY BO ZIMMERMANd$;;UPDATED 20170613 07:44Pd;;TERMSETUP LDA $DD01: #$10:STA DCDCHKiTERMINAL LDX #$05:JSR $FFC6nJSR $FFE4:CMP #$00:BEQ TERMKsCMP #$0A:BEQ TERMKxJSR $FFD2" yLDA1 TERMK LDX #$00:JSR $FFC6> JSR $FFE4:CMP #$00:BEQ TERMINALk @@ -125,14 +129,8 @@ $ TERMOUT PHA:LDX #$05:JSR $FFC9:PLA JSR $FFD2:LDX #$00:JSR $FFC9 SEC:BCS TERMINAL:RTSUL 8S TYECHO# A #9:;%00001001 ; SET RECV BUF LY1 STA ZPXMO> VEC SEIc LDA NMINV1:CMP #NMI:BEQ NVEC STA OLDNMI1:LDA NMINV:STA OLDNMI LDA #NMI STA NMINV LDA #NMI STA NMINV1 NVEC CLI LDA #DO:STA $031C LD FFCLD:LDA STATUS! #8:; MASK OUT NINDI/BEQ NREVD>;STA ERRSLLDY RTAILZLDA PORTlSTA (RBUFF),YzINC RTAILINC RCOUNTNREVD NOP:;LDA ZPXMF:STA COMM$;JMP $FEBC.JSR $FFE1:BNE NMOUT8JMP $FE66VNMOUT PL ;;;;;;;;;;;pDOPUT PHAzLDA $9A)CMP #$02:BEQ DOPUT31PLAFDOPUT2 JMP $F1CA\DOPUT3 LDA STATUSq #16:;%00010000BEQ DOPUT3CLC:PLA:STA PORTBCC DOPUT4 -LDA #$00DOPUT4 RTSW;;;;;;;;;;;;;;;;;;XDO JSR $F291 STA $0319,Y DEY:BNE INCLO:PLA:TAY+ JSR NOINIT: ; DO, CLI!LNOCLOS JSR UPDCD:PLA:PLP:RTSuLDOCLAL JSR $F32F:PHP:PHA:JMP DOCLOS2?;;;;;;;;;;;;;;;;;;@BAUDS .BYTE 9 0 0 1 2 2 5 6 7 8 8 9 10 11 12 14 15 130 129 128 0 0 0 0 0 0EVECS .2DRVR.BAS":"%LADS":"SYS11000"/BEQ NREVD>;STA ERRSLLDY RTAILZLDA PORTlSTA (RBUFF),YzINC RTAILINC RCOUNTNREVD NOP:;LDA ZPXMF:STA COMM$;JMP $FEBC.JSR $FFE1:BNE NMOUT8JMP $FE66VNMOUT PL  FTP64SW 19200B 2.0+< UPDATED 10/13/2021 12:54A]254,8:(186)7254,(186)s9100: SET SY,BC -SY6158,254: SY34X23777:X,170:(X)170"<16K": SY227(51201)351200 5,2,0,(BC):(65532)3456,87:54 ),1: -&SY3436879,27:CO$(31)0 -(SY22645] -*(ML1)2095:"PML64.BIN",(254),1: -,UM0(UM1)245:"UP9600.BIN",(254),1: --SY22750 -/(ML1)2095:"PML64.BIN",(254),1: 1UM0(UM1)35:"SWIFTDRVR.BIN",(25  WORK8 dMVML18:MV14,8:DD56577:SY34DD37136: FIX BUFAP> eD fJ n xCO$;" V1.7":"EQUIRES IMODEM FIRMWARE 2.0+" "Y O IMMERMAN (BO@ZIMMERS.NET)":: ---- ZIMODEM SETUP UN(254):IP$"":CR$(13)(10) PH0 $029B:CMP $029C:BEQ BUFUP:BCS BUFL1Y zLDA #$FF:SEC:SBC $029C:CLC:ADC $029B:SEC:BCS BUFL2q {BUFL1 SEC:SBC $029C BUFL2 CMP #200:BCC BUFL3 LDA $DD01: #$FD:STA $DD01:SEC:BCS TERMK BUFL3 CMP #20:BCS TERMK BUFUP LDA $DD01:A #$02:STA $DD0 +LDA #$00DOPUT4 RTSW;;;;;;;;;;;;;;;;;;XDO JSR $F291 STA $0319,Y DEY:BNE INCLO:PLA:TAY+ JSR NOINIT: ; DO, CLI!LNOCLOS JSR UPDCD:PLA:PLP:RTSuLDOCLAL JSR $F32F:PHP:PHA:JMP DOCLOS2?;;;;;;;;;;;;;;;;;;@BAUDS .BYTE 9 0 0 1 2 2 5 6 7 8 8 9 10 11 12 14 15 130 129 128 0 0 0 0 0 0EVECS .2DRVR.BAS":"%LADS":"SYS11000"/BEQ NREVD>;STA ERRSLLDY RTAILZLDA PORTlSTA (RBUFF),YzINC RTAILINC RCOUNTNREVD NOP:;LDA ZPXMF:STA COMM$;JMP $FEBC.JSR $FFE1:BNE NMOUT8JMP $FE66VNMOUT PL ,87:52,870 P$"OK":186,(254):BA1200:XB1200b CR$(13):(14);:9100:53280,254:53281,246 CO$"":SY226ML49152:665,73((678)30):UMML2048:XB9600 !SY227ML49152:UMML2048 +#SY34ML22273:(ML)765:"PMLVIC.BIN",(254 4),1:UM:A 2S80:SY61ML4864:981,15:S8(215)128:S812830643u <SY61(ML1)2175:"PML128.BIN",(254),1: FSY61S8128XB2400:CO$(159) PSY226UM0UM:UM3:X(789):UM9:X234XB1200 ZSY3456579,0: WHY DOES THIS :PT0:MVML18" "NITIALIZING MODEM..."9 #5,A$:A$""203k 5,CR$;"ATHZ0&P0F0E0";CR$;:900:P$"OK"203 #5,A$:A$""208 5,CR$;"ATE0N0R0V1Q0";CR$; 900:P$"OK"208 5,"ATE0V1X1F3Q0S40=248S0=1S41=0I4";CR$;(19);:L9248 $029B:CMP $029C:BEQ BUFUP:BCS BUFL1Y zLDA #$FF:SEC:SBC $029C:CLC:ADC $029B:SEC:BCS BUFL2q {BUFL1 SEC:SBC $029C BUFL2 CMP #200:BCC BUFL3 LDA $DD01: #$FD:STA $DD01:SEC:BCS TERMK BUFL3 CMP #20:BCS TERMK BUFUP LDA $DD01:A #$02:STA $DD0 #5,A$:A$""23104 5,CR$;"ATF3HCTEP";QU$;HO$;":";((PO),2);QU$;CR$;] 900:(P$)7(P$,7)"CONNECT"2400$ "NABLE TO CONNECT TO ";HO$;":";((PO),2). ` "* ONNECTED. ";t 5,CR$;"ATF0O";CR$;~ SY226"IT 1 TO EXIT." 00:H$P$:P$""0"NTER PORT NUMBER (23): ";:5000:HP(P$):HP0HP23GHO$H$:POHP:2300MWP$""d" ";xA$:A$""5010A(A$):A13" ":A34A$;A$;" ";:P$P$A$:5010A20A$;" ";:P$P$A$:501 #X(56834):56834,X1:(56834)X19140%#BC15:SY227:56834,X:Z#(14);"EQUIRES A 64 WITH WIFTINK/INK232":`?@1,8,15:8,8,8,"TELNETML.BIN,P,R":LN41000:DN0J1,E:E08:1:T#8,A$:ST08:^A$""A$(0)cA ,156< 2,24,109,155,2,56,176,4,56,237,156,2,201,200,144,11,173,1^F 221,41,253,141,1,221,56,176,12,201,20,176,8,173,1,221,9,2P 141,1,221,162,0,32,198,255,32,228,255,201,0,240,171,201,133,240Z 4,201,27,208,4,32,204,255,96,72,162,5,32,201, LDA ($BB),Y TAY:LDA BAUDS,Y:BPL DOOP34 LDY #16:STY ROLS #$7F:STA ESR232:LDA #$00o DOOP3 A #16:;%00010000| STA ROL ; NO PAR, NO ECHO, XMIT , RECV LDA #9:; %00001001 ; STA COMM STA ZPXMF #%11110000 ; KEEP PARI ; B1920015 ; B23040019, ;;;;;;;;;;;= BASE $DE00O PORT = $DE00b STATUS $DE01t COMM $DE02 ROL $DE03 ESR232 $DE07 ; RHEAD 668 RTAIL 667 RBUFF $F7 NMINV $0318 3;;;;;;;;;;; JMP INI 031D:STA DO2: LDA $0326:STA DOPUT21:LDA $0327:STA DOPUT22l LDA $032C:STA DOCLAL1:LDA $032D:STA DOCLAL2 NOINIT LDA #DO:STA $031A  LDA #DO:STA $031B  CLI:RTS  ;;;;;;;;;;;  DO JSR $F34A:; CALL I ! PHP:PHA & LDA $BA:CMP v = 9 ͜& 8m88Ȑ )8  ݢ Ʌ `H h 8 m= 8L16m=D= :=L16>n=LO6=> 2z> 7>=L16> 8n=: 4>>hh>>Ll.L+>` eh80qȩqh° в````M)jIjо` # °L4```` L͜ ™ Lx;ɈSȭTȭLɭMɭ&8ɭ'9ɭ,ɭ-ɩRȍX` JHh(`HɌɌޱ )ީ ީ ލ)p xɭɩݍȍXKɍ/&ɍ'ɍ,- hh(`HHHح)ޑ  SY61"IT TO EXIT."+ 53280,0:53281,0:SY3436879,83 TM^ 53280,254:53281,246:SY3436879,27 :CO$;(14);"ANGING UP...";:6000 :I1100:#5,A$:I #5,A$:A$""2480  :"ODIFY HONEBOOK:" I1HZ 0:HP(P$):HP0HP23&: HO$(X1)H$:PO(X1)HP:XHZ1HZHZ1TD 1,UN,15:8,UN,8,"@0:TELNETPHONEBOOK,S,W"nN 1,E:E08:1:300X 8,HZ:I1HZ:8,HO$(I1):8,PO(I1)] 8,"-"HM$(I1)b I:8:1: 3000:"NTER HOSTNAME/IP: ";:50 0P$""5010P$(P$,(P$)1):"  ";:5010.p((DD)16)08z6100L5,"+++";:".";V6100s".";:5,"ATH":TTTI150ML12:TITT6050TTTI150TITT6110#SY(65532):BC8#SY2269140  (A$):A$((A),2)hDN0((LN),2);" DATA ";A$;:DNDN1:400204|",";A$;:DNDN1ODN18:DN0:LNLN10[ 40020( 162,5,32,198,255,32,228,255,201,0,240,61,201,10,240,572 32,210,255,173,155,2,205,156,2,240,38,176,13,169,255,56,237255,104,32,210 d 255,162,0,32,201,255,56,176,141 -1OÐ+Pß5,2,0,(8)CZá#5,A$:A$""A$;ZdáA$:A$""5,A$;fnÉ 50010U8:F$"TELNET":1,U,15,"S0:"F$:1:(F$),U:(F$),U)? ";A$:A$"Y"A$""A$"N"A$""4030  $C800#.D SWTDRVR.BIN;; KERNAL BAUD RATESG; B501S; B752`; B1103m; B1354z; B1505; B3006; B6007; B12008; B18009; B240010; B360011; B480012; B720013; B960014 T @@ -175,12 +173,12 @@ jLDA $FC:CMP $32:BCC SETPLP ,SY227(51201)35:"SWIFTDRVR.BIN",(254),1: -SY226UM0(UM1)245:"UP9600.BIN",(254),1:) 2SY RATE nP$"A"F xCO$;" V1.5":"EQUIRES IMODEM FIRMWARE 1.8+"r "Y O IMMERMAN (BO@ZIMMERS.NET)":: -------------------------------- GET STARTED ! ------------------------------- UN(254):I";CR$;(19);8900:VR(P$):VR1.8"IMODEM INIT FAILED: ";P$:O900:P$"OK"203d, GET THE SERVER|6:"OME SERVERS:"@"IRC.NLNOG.NET PORT 6667, #C-64"E"IRC.FREENODE.NET PORT 6667, #C64FRIENDS"J"IRC.US.IRCNET.NET PORT 666XB0BA0XBBAAT$"S43="((XB),2)-#5,A$:A$""406eQU$(34):5,"AT"AT$"H&D13&M13&M10CP";QU$;SE$;QU$n900(P$,8)"CONNECT ""NABLE TO CONNECT TO ";SE$;":";P$:300P((P$,8))XB9600430UM:UM3:(789)234$:N1(NI$)NI$""" GUESS NOT.":-P$" "NI$:600YP$" GUEST 0 * :OE NONYMOUS":600"ONNECTED, WAIT FOR . ? FOR HELP";CO$ 1000: GO START MAIN LPV --- TRANSMIT P$ TO THE OPEN SOCKETX(P$)0((P$,1;P0;"/";P:P00#PP$(PE)P$:PEPE1:PE25PE0/P$"":N --- GET P$ FROM SOCKET Pi E0:P$"":PH25PH0*PHPEP$PP$(PH):PHPH1:4700:P00E1:: FAIL>PHPEP$PP$(PH):PHPH1:HE1: ---- GET E$ FR,(17);ML6:P0(MV2):P1(MV4):P2(MV6);PL(MV0):CR(MV1):C8(MV8)PP00P2C8985`P10P$""qP00CR0ML12:P$"""-";CO$:5,"ATL":945"XPECTED ";E$;", GOT ";A$: THE MAIN LOOPjMC$(P$,1,I1):P$(P$,I1)MA$P$:MC$""1000<A((MC$,1,I)):A48A571300l(MC$)1(MC$,1,1)"0"MC$(MC$,2):1220C(MC$):C430C460"AD ICK GIVEN(";C;") : "MA$:440C400"RROR: "MA$:1000(MA$)N11 MC$"' '"MA$"'"w1000x ....OM$MA$:P$" :"MA$:600:E$"":1000CI1:MA$""1000](MA$,I,2)" :"2050mII1:2010w2900*"";MS$;": ";(MA$,I2):10004"";LP$;"":10002900:"";MS$;" HAS LEFT THE CHAN PP$(25):P$"OK":186,(254):BA1200:XB1200[ CR$(13):(14);:9200:53280,254:53281,246 CO$"":SY226ML49152:665,73((678)30):UMML2048:XB9600 !SY227ML49152:UMML2048 #SY34ML22273:(ML)765:"PMLVIC.BIN",(254),1: " CONFIGURE64SW 19200B 2.0+B UPDATED 08/15/2021 12:54Pc254,(186):(254)8254,8~ -9100:SY6158,254: SY34X23777:X,170:(X)170"<16K":SY227(51201)3512005,2,0,(BC):(65532)3456,87:54,87:52,87 -------------------------------U PH0:PT0:MVML18:CR$(13)(10):QU$(34):MV14,5y "NITIALIZING MODEM...";:CT0 #5,A$:A$""203 5,CR$;"ATHZ0F0R0E0&P1";CR$; 900:P$""CTCT1:CT10203 CT10#5,A$:A$""207 ---------------------------- E$"":P$""(MLmE$""P$E$"OMM ERROR. XPECTED ";E$;", OT ";P$;CO$;""s -------------------------------- THE MAIN LOOP ! -------------------------------"CT0:C"2000  1050:CD020004000@:"ESTING CONNECTION...";:TTTI200PTITT1055m$CT0:#5,A$:A$""1060.ML12:5,CR$;"ATC";QU$;"132.148.138.66:80";QU$;CR$;8900:CD1:P$""1080=(P$,8)" "CD0:CTCT1:CT1P$"""ONE!":::3000I(P$)2100?(P$,I,1)" "P$(P$,I1):2100OII1:2030n4WF$(WF1)P$:WFWF1:2020w PG1 LPWF:LPPG15LPPG15& IPGLP:(I)") ";WF$(I):I0 :"NTER TO UIT TO .": LPWF"NT,1,1)):A48A573100& A(A$):APG APG15 AWF3100Q WIA:"TTEMPT TO CONNECT TO: ";WF$(A)| "NTER II ASSWORD: ";:5000:PA$P$H ML12:5,CR$;"ATW";QU$;WF$(WI);",";PA$;QU$;CR$;:900R P$"""ONNECT AIL!";CO$:31003470 P$"":ML12:P$""3490 ML12:5:>"HANGE II CONNECTION (Y/)? ";`A$:A$"Y"A$"""":2000A$"N"A$""A$(13)4010"":"URRENT 'HONE' BOOK:":PB05,CR$;"ATP";CR$TTTI100"900:P$(Q$,1):QQ$"Q"QQ$""5:1QQ(Q$):(QQ0QQPB)QQ$"A"QQ$"":4100AQQ04300\")DIT OR )ELETE? ";A$:A$"E"A$"""":B1$PB$(QQ1):4310A$"D"A$""4260""5,"ATP";(34);PB$(QQ1);"=DELETE";(34):000:B2(P$)B20"RONG.":41001B3$"":"O TRANSLATION (Y/N)? ";L4980:A1B3$B3$"P"r"O TRANSLATION (Y/N)? ";4980:A1B3$B3$"T"&"O TERMINAL CHO (Y/N)? ";04980:A1B3$B3$"E":"O /" ";A$:A$""5010 A$(13)" ":5A$(20)A$;" ";:P$P$A$:5010EP$""5010nP$(P$,(P$)1):"  ";:5010#SY(65532):BC8#SY2269140#X(56834):56834,X1:(56834)X19140#BC15:SY227:. WF$(100):WF0:P$"OK":PB$(50):186,(254)` CR$(13):(14);:9100:53280,254:53281,246 CO$"":SY226SY227ML49152:665,73((678)30) #SY34ML22273:(ML)765:"PMLVIC.BIN",(254),1: &SY3436879,27:CO$(31)& +9100:SY6158,254: SY34X23777:X,170:(X)170"<16K":SY227(51201)3512005,2,0,(BC):(65532)3456,87:54,87:52,87 -------------------------------U PH0:PT0:MVML18:CR$(13)(10):QU$(34):MV14,5y "NITIALIZING MODEM...";:CT0 #5,A$:A$""203 5,CR$;"ATHZ0F0R0E0&P1";CR$; 900:P$""CTCT1:CT10203 CT10#5,A$:A$""207 ---------------------------- E$"":P$""(MLmE$""P$E$"OMM ERROR. XPECTED ";E$;", OT ";P$;CO$;""s -------------------------------- THE MAIN LOOP ! -------------------------------"CT0:C"2000  1050:CD020004000@:"ESTING CONNECTION...";:TTTI200PTITT1055m$CT0:#5,A$:A$""1060.ML12:5,CR$;"ATC";QU$;"198.251.68.243:80";QU$;CR$;8900:CD1:P$""1080=(P$,8)" "CD0:CTCT1:CT1P$"""ONE!":::3000I(P$)2100?(P$,I,1)" "P$(P$,I1):2100OII1:2030n4WF$(WF1)P$:WFWF1:2020w PG1 LPWF:LPPG15LPPG15& IPGLP:(I)") ";WF$(I):I0 :"NTER TO UIT TO .": LPWF"NT,1,1)):A48A573100& A(A$):APG APG15 AWF3100Q WIA:"TTEMPT TO CONNECT TO: ";WF$(A)| "NTER II ASSWORD: ";:5000:PA$P$H ML12:5,CR$;"ATW";QU$;WF$(WI);",";PA$;QU$;CR$;:900R P$"""ONNECT AIL!";CO$:31003470 P$"":ML12:P$""3490 ML12:5:>"HANGE II CONNECTION (Y/)? ";`A$:A$"Y"A$"""":2000A$"N"A$""A$(13)4010"":"URRENT 'HONE' BOOK:":PB05,CR$;"ATP";CR$TTTI100"900:P$(Q$,1):QQ$"Q"QQ$""5:1QQ(Q$):(QQ0QQPB)QQ$"A"QQ$"":4100AQQ04300\")DIT OR )ELETE? ";A$:A$"E"A$"""":B1$PB$(QQ1):4310A$"D"A$""4260""5,"ATP";(34);PB$(QQ1);"=DELETE";(34):000:B2(P$)B20"RONG.":41001B3$"":"O TRANSLATION (Y/N)? ";L4980:A1B3$B3$"P"r"O TRANSLATION (Y/N)? ";4980:A1B3$B3$"T"&"O TERMINAL CHO (Y/N)? ";04980:A1B3$B3$"E":"O /" ";A$:A$""5010 A$(13)" ":5A$(20)A$;" ";:P$P$A$:5010EP$""5010nP$(P$,(P$)1):"  ";:5010#SY(65532):BC8#SY2269140#X(56834):56834,X1:(56834)X19140#BC15:SY227:. WF$(100):WF0:P$"OK":PB$(50):186,(254)` CR$(13):(14);:9100:53280,254:53281,246 CO$"":SY226SY227ML49152:665,73((678)30) #SY34ML22273:(ML)765:"PMLVIC.BIN",(254),1: &SY3436879,27:CO$(31)& 'SY2 FSY61CO$(159) d" nP$"A"a xCO$;" V1.6":"EQUIRES IMODEM IRMWARE 2.0+"{ "19200 BAUD VERSION" "Y O IMMERMAN (BO@ZIMMERS.NET)":: -------------------------------- GET STARTED !  CT105,"ATHF3E0&P1";CR$;< CT10900:P$""CTCT1:207 CT10"NITIALIZATION FAILED.":"S YOUR MODEM SET TO 1200B?" CT10 ,:: 1000  --------------------------------  GET E$ FROM MODEM, OR ERROR ! ---10:CD0:LC$"1":1010:WI$P$:1010:P$WI$1030,1000JML12:5,CR$;"ATI3";CR$;a900:P$LC$1025uCTCT1:CT3C10:1010LC$P$:CT0:C1C11:C115P$"":1010WI$P$:"URRENT II : ";WI$CD0:WI$"070,1070,1070,1070,12002BI13:ML12:5,CR$;"ATH0";CR$;:900:INL5,CR$;"ATH0";CR$;:900hCD0"AIL!";CO$CD1"UCCESS!";CO$ML12:ML12::"CANNING FOR II HOTSPOTS...";5,CR$;"ATW";CR$;900:I1:P$""ER FOR EXT AGE"!D PG15"NTER FOR REV AGE"NN "NTER A NUMBER TO ONNECT TO THAT "q "? ";:5000:A$P$:A$""3100 A$"X"A$""5: A$"N"A$""LPWFPGPG15:3100 A$"P"A$""PG15PGPG15:3100 A((A$W P$""3420X P$""3400Y 900:34105\ "ONNECT SUCCESS!";CO$;f Ig TTTI300Yh TITT3432sp CD0:1050:CD03100z "AVING NEW OPTIONS..." ML12:ML12:ML12 5,CR$;"ATZ0&WE0&P1";CR$ 900:P$""P$"OK"""TITT4200,P$""4130$6I1:PP$P$:P$"OK"P$""4200E@(PP$,I,1)" "II1:4160XJPP$(PP$,I1)^PB$(PB)PP$:PBPB1:PB;") ";P$:4120h" ) DD NEW":" ) UIT TO "r:"NTER YOUR CHOICE: ";:5000:Q$P$|QQ$900:4100 "HONE NUMBER: ";:5000:B1$P$)(B1$)3"RONG.":4100^I1(B1$):B1((B1$,I,1)):B148B157B1$""I:B1$"""AD DIGITS.":4100"ARGET HOST: ";:5000:B2$P$(B2$)4"RONG.":4100"ARGET PORT: ";:5 LOW ONTROL (Y/N)? "; D4980:A1B3$B3$"X" NB4$((B2),2)VX5,"ATP";B3$;(34);B1$;"=";B2$;":";B4$;(34):900`b4100ftA$:A$"Y"A$""A$"N"A$""4980~A0:AA$"":A$"Y"A$""AA$"":A1AA$:P$"" 26(ML1)2095:"PML64.BIN",(254),1:Z (SY227(ML1)2095:"PML64.BIN",(254),1: -SY227(51201)35:"SWIFTDRVR.BIN",(254),1: 2SY61ML4864:981,15:P(215)128:P12830643 -<SY61(ML1)2175:"PML128.BIN",(254),1: l"LINK232 APPS2ACONFIGURE,8:FTP,8:( +<SY61(ML1)2175:"PML128.BIN",(254),1:TR l"LINK232 APPS2ACONFIGURE,8: FTP,8:( IRC,8: WGET,8: D64WGET,8:TELNET,8: CBMTERM,8: CBMTERM,8KK----REQ ML---- PML64.BIN TELNETML.BIN SWIFTDRVR.BIN----SOURCE---- PML64.BAS TELNETML.BAS SWIFTDRVR.BASKK ----TOOLS---- EMUTIL LADS MONITOR.49152 diff --git a/cbm8bit/src/64Net Apps.cbmprj b/cbm8bit/src/64Net Apps.cbmprj index b23e9e5..3ab8e80 100644 --- a/cbm8bit/src/64Net Apps.cbmprj +++ b/cbm8bit/src/64Net Apps.cbmprj @@ -1,6 +1,6 @@  - + 4 @@ -20,6 +20,10 @@ False + False + + + False False C64 CARTRIDGE @@ -56,6 +60,7 @@ + @@ -67,7 +72,7 @@ False - True + False cbmterm-sw64.bas @@ -78,18 +83,18 @@ False - True + False - cometmode64.bas - cometmode64.prg + cometserve64.bas + cometserve64.prg 1 True False - True + False configure64-128-vic.bas @@ -100,7 +105,7 @@ False - True + False configure-sw64.bas @@ -111,7 +116,7 @@ False - True + False d64wget64-128-vic.bas @@ -122,7 +127,7 @@ False - True + False d64wget-sw64.bas @@ -133,7 +138,7 @@ False - True + False ftp64-128-vic.bas @@ -158,8 +163,8 @@ True - irc64-128-vic.bas - irc.prg + gopher64-128-vic.bas + gopher.prg 1 True @@ -168,6 +173,17 @@ True True + + irc64-128-vic.bas + irc.prg + 1 + True + + + + False + False + irc-sw64.bas irc-sw.prg @@ -177,6 +193,17 @@ False + False + + + loader64.bas + loader64.prg + 1 + True + + + + False True @@ -199,7 +226,7 @@ False - False + True pmlvic.asm @@ -243,7 +270,7 @@ False - True + False telnetd64.bas @@ -254,7 +281,7 @@ False - True + False telnetml.asm @@ -276,7 +303,7 @@ False - True + False up9600.asm @@ -298,7 +325,7 @@ False - True + False wget64-128-vic.bas @@ -309,7 +336,7 @@ False - True + False wget-sw64.bas @@ -320,7 +347,7 @@ False - True + False x-xfer128.asm diff --git a/cbm8bit/src/cbmterm64-128-vic-p4.bas b/cbm8bit/src/cbmterm64-128-vic-p4.bas index 0cb27f5..5ed688f 100644 --- a/cbm8bit/src/cbmterm64-128-vic-p4.bas +++ b/cbm8bit/src/cbmterm64-128-vic-p4.bas @@ -36,7 +36,7 @@ 101 REM 102 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}C= TERM v1.5":PRINT"Requires C64Net WiFi firmware 1.8+" +120 PRINTCO$;"{clear}{down*2}C= TERM v1.5":PRINT"Requires Zimodem firmware 1.8+" 130 PRINT"1200 baud version" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- diff --git a/cbm8bit/src/cbmterm64-128-vic.bas b/cbm8bit/src/cbmterm64-128-vic.bas index 32de0ef..0622488 100644 --- a/cbm8bit/src/cbmterm64-128-vic.bas +++ b/cbm8bit/src/cbmterm64-128-vic.bas @@ -31,7 +31,7 @@ 101 REM 102 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}C= TERM v1.5":PRINT"Requires C64Net WiFi firmware 1.8+" +120 PRINTCO$;"{clear}{down*2}C= TERM v1.5":PRINT"Requires Zimodem firmware 1.8+" 130 PRINT"1200 baud version" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- diff --git a/cbm8bit/src/cometmode64.bas b/cbm8bit/src/cometserve64.bas similarity index 91% rename from cbm8bit/src/cometmode64.bas rename to cbm8bit/src/cometserve64.bas index a4b0887..c13b93a 100644 --- a/cbm8bit/src/cometmode64.bas +++ b/cbm8bit/src/cometserve64.bas @@ -6,7 +6,7 @@ !-------------------------------------------------- 10 IFPEEK(49153)<>54THENLOAD"v-1541",8,1:RUN 20 OPEN5,2,0,CHR$(8):TI$="000000":T1=0 -30 PRINT#5,"at&p0r0":TT=TI+100:GOSUB900:PRINT"connecting..."; +30 PRINT#5,"at&p0r0e0f0":TT=TI+100:GOSUB900:PRINT"connecting..."; 40 GOSUB920:PRINT".";:T1=T1+1:IFT1>20THENPRINT"fail.":CLOSE5:END 50 PRINT#5,"at":TT=TI+100:GOSUB900 60 INPUT#5,A$:IFLEFT$(A$,2)="at"ORLEFT$(A$,2)="AT"THENINPUT#5,A$ diff --git a/cbm8bit/src/configure-sw64.bas b/cbm8bit/src/configure-sw64.bas index 786cc68..88094b8 100644 --- a/cbm8bit/src/configure-sw64.bas +++ b/cbm8bit/src/configure-sw64.bas @@ -67,7 +67,7 @@ 1050 PRINT:PRINT"Testing connection...";:TT=TI+200 1055 IFTI""THEN1060 -1070 SYSML+12:PRINT#5,CR$;"atc";QU$;"132.148.138.66:80";QU$;CR$; +1070 SYSML+12:PRINT#5,CR$;"atc";QU$;"198.251.68.243:80";QU$;CR$; 1080 GOSUB900:CD=1:IFP$="OK"THEN1080 1085 IFLEFT$(P$,8)<>"CONNECT "THENCD=0:CT=CT+1:ONCTGOTO1070,1070,1070,1070,1200 1090 FORI=1TO3:SYSML+12:PRINT#5,CR$;"ath0";CR$;:GOSUB900:NEXTI diff --git a/cbm8bit/src/configure64-128-vic-p4.bas b/cbm8bit/src/configure64-128-vic-p4.bas index ae41755..a4ccd51 100644 --- a/cbm8bit/src/configure64-128-vic-p4.bas +++ b/cbm8bit/src/configure64-128-vic-p4.bas @@ -25,7 +25,7 @@ 80 IFSY=246THENREM ----SET CO$,BG COLOR 100 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}CONFIGURE v1.7":PRINT"Requires C64Net WiFi Firmware 2.0+" +120 PRINTCO$;"{clear}{down*2}CONFIGURE v1.7":PRINT"Requires Zimodem Firmware 2.0+" 130 PRINT"1200 baud version" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- diff --git a/cbm8bit/src/configure64-128-vic.bas b/cbm8bit/src/configure64-128-vic.bas index 1a40f2b..b714a2d 100644 --- a/cbm8bit/src/configure64-128-vic.bas +++ b/cbm8bit/src/configure64-128-vic.bas @@ -21,7 +21,7 @@ 70 IFSY=61THENCO$=CHR$(159) 100 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}CONFIGURE v1.6":PRINT"Requires C64Net WiFi Firmware 2.0+" +120 PRINTCO$;"{clear}{down*2}CONFIGURE v1.6":PRINT"Requires Zimodem Firmware 2.0+" 130 PRINT"1200 baud version" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- @@ -30,7 +30,7 @@ 200 PH=0:PT=0:MV=ML+18:CR$=CHR$(13)+CHR$(10):QU$=CHR$(34):POKEMV+14,5 202 PRINT "Initializing modem...";:CT=0 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0f0e0r0&p1";CR$; +205 PRINT#5,CR$;"athz0f4e0r0&p1";CR$; 206 GOSUB900:IFP$<>"OK"THENCT=CT+1:IFCT<10THEN203 207 IFCT<10THENGET#5,A$:IFA$<>""THEN207 208 IFCT<10THENPRINT#5,"athf3e0&p1";CR$; @@ -64,7 +64,7 @@ 1050 PRINT:PRINT"Testing connection...";:TT=TI+200 1055 IFTI""THEN1060 -1070 SYSML+12:PRINT#5,CR$;"atc";QU$;"132.148.138.66:80";QU$;CR$; +1070 SYSML+12:PRINT#5,CR$;"atc";QU$;"198.251.68.243:80";QU$;CR$; 1080 GOSUB900:CD=1:IFP$="OK"THEN1080 1085 IFLEFT$(P$,8)<>"CONNECT "THENCD=0:CT=CT+1:ONCTGOTO1070,1070,1070,1070,1200 1090 FORI=1TO3:SYSML+12:PRINT#5,CR$;"ath0";CR$;:GOSUB900:NEXTI @@ -74,10 +74,10 @@ 1220 RETURN 2000 SYSML+12:SYSML+12:PRINT:PRINT"Scanning for WiFi hotspots..."; 2010 PRINT#5,CR$;"atw";CR$; -2020 GOSUB900:I=1:IFP$=""ORP$="OK"THENPRINT"Done!":PRINT:PRINT:GOTO3000 -2030 IFI>=LEN(P$)THEN2100 +2020 GOSUB900:I=LEN(P$)-1:IFP$=""ORP$="OK"THENPRINT"Done!":PRINT:PRINT:GOTO3000 +2030 IFI<=1THEN2100 2040 IFMID$(P$,I,1)=" "THENP$=LEFT$(P$,I-1):GOTO2100 -2050 I=I+1:GOTO2030 +2050 I=I-1:GOTO2030 2100 WF$(WF+1)=P$:WF=WF+1:GOTO2020 3000 PG=1 3100 LP=WF:IFLP>PG+15THENLP=PG+15 diff --git a/cbm8bit/src/d64wget-sw64.bas b/cbm8bit/src/d64wget-sw64.bas index 9f1a969..8017a05 100644 --- a/cbm8bit/src/d64wget-sw64.bas +++ b/cbm8bit/src/d64wget-sw64.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, August 17, 2021 9:44:52 AM -!- Import of : -!- z:\lost+found\d64wget.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM D64WGET64SW 19200B 2.0+ 2 REM UPDATED 10/13/2021 12:54A @@ -25,7 +22,7 @@ 55 IFSY=61ANDS8=128THENXB=2400:CO$=CHR$(159) 60 IFSY=61ANDPEEK(ML+1)<>217THENCLOSE5:LOAD"pml128.bin",PEEK(254),1:RUN 80 IFSY=226ANDUM>0THENSYSUM:SYSUM+3:X=PEEK(789):SYSUM+9:IFX=234THENXB=1200 -100 IFSY<>34THENPOKE56579,0 +100 DD=37136:IFSY<>34THENPOKE56579,0:DD=56577 101 REM 102 REM 110 P$="a" @@ -161,12 +158,14 @@ 2305 PRINT"{space*15}{left*15}"+STR$(T)+","+STR$(S):PRINT"{up}"; 2310 IFOS>1ANDOS<=LEN(P$)THENPRINT#3,MID$(P$,OS);:BR=BR-(LEN(P$)-OS+1) 2320 GOSUB930:IFP0<>PORP1=0ORP0=0THEN2320 +2325 IF(PEEK(DD)AND16)=0THENPRINT:PRINT"{reverse on}{red}LOST CONNECTION{reverse off}":GOTO2385 2330 IFP10THENT=T+1:S=0:GOTO2360 -2380 PRINT:PRINT"{reverse on}{light green}Done.{reverse off}";CO$:CLOSE3:CLOSE1 +2380 PRINT:PRINT"{reverse on}{light green}Done.{reverse off}";CO$ +2385 CLOSE3:CLOSE1 2390 PRINT#5,"ath0":PRINT#5,"atz":TT=TI+100 2400 IFTI""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 diff --git a/cbm8bit/src/ftp-sw64.bas b/cbm8bit/src/ftp-sw64.bas index 432c356..d0d9b12 100644 --- a/cbm8bit/src/ftp-sw64.bas +++ b/cbm8bit/src/ftp-sw64.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, August 17, 2021 9:45:06 AM -!- Import of : -!- z:\lost+found\ftp.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM FTP64SW 19200B 2.0+ 2 REM UPDATED 10/13/2021 12:54A @@ -33,17 +30,19 @@ 101 REM 102 REM 110 REM -120 PRINTCO$;"{clear}{down*2}FTP v1.7":PRINT"Requires Zimodem firmware 2.0+" +120 PRINTCO$;"{clear}{down*2}FTP v1.8":PRINT"Requires Zimodem firmware 2.0+" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 199 REM ---- ZIMODEM SETUP 200 UN=PEEK(254):IP$="":CR$=CHR$(13)+CHR$(10) 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem..." 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=248s0=1s41=0i4";CR$;CHR$(19);:L9=248 235 GOSUB900:VR=VAL(P$):IFVR<2.0THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 @@ -56,9 +55,9 @@ 299 REMHO$="192.168.1.112":PO=21 300 REM GET INFO 310 PRINT:PRINT"{down}Request Parms:" -321 PRINT " 1) Url{space*8}: ftp://";HO$ -322 PRINT " 2) Username{space*3}: ";UN$ -323 PRINT " 3) Password{space*3}: ";PA$ +321 PRINT " 1) Url : ftp://";HO$ +322 PRINT " 2) Username : ";UN$ +323 PRINT " 3) Password : ";PA$ 324 PRINT " 4) Disk Device:";UN 329 LH=4 370 IFHO$=""THENPRINT:P$="1":GOTO400 @@ -163,8 +162,9 @@ 1420 GET#5,A$:IFA$<>""THEN1420 1425 PRINT#5,"atc";MID$(STR$(P),2):GOSUB900:IFP$="ok"THENE=0:RETURN 1430 PRINT"{pink}{reverse on}Retry to change back to ";H0$;"{reverse off}";CO$:GOTO1420 +1440 REM ---- main command prompt 2000 PRINT"Command (?): ";:GOSUB5000 -2005 IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:P$="quit" +2005 REMIF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:P$="quit" 2010 IFP$=""THENGOSUB800:PRINTP$:GOTO2000 2020 IFP$="ls"ORLEFT$(P$,3)="ls "THENGOSUB4000:GOTO2000 2030 IFP$="dir"ORLEFT$(P$,4)="dir "THENGOSUB4000:GOTO2000 @@ -186,27 +186,27 @@ 4000 P$="TYPE A":GOSUB600:GOSUB850:IFP$=""THEN4000 4005 PRINTP$:CC$="&m10&d10&m13cp":GOSUB1300:IFE=1THENRETURN 4010 P$="PWD":GOSUB600:GOSUB850:IFP$=""THEN4010 -4020 REMPRINTP$:P$="PORT "+IP$+",198,76":GOSUB600:GOSUB850:IFP$=""THEN4020 +4020 REM 4025 GET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4025 4030 PRINTP$:P$="LIST":GOSUB600 -4040 REMGOSUB900:IFLEFT$(P$,5)="ring "THEN4040 +4040 REM 4050 Y=0:Y$="":Y1=0:Y2=0:Y3=5:IFBA>4800THENY3=10 -4100 REMGET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4100 -4110 GETA$:IFA$=" "ORA$="{ct c}"THENPRINT:PRINT"Aborted.":GOTO4250 +4100 REM +4110 GETA$:IFA$=" "ORA$="{ct c}"THENPRINT:PRINT"Aborted.":GOTO4295 4150 GOSUB800 4200 IFP0=PANDY=1ANDP$<>""THENY$=P$:GOTO4100 -4210 IFP0=PANDP$=""THEN4100 +4210 IFP0=SPANDP$=""THEN4100 4220 IFP0=PANDLEFT$(P$,4)="226 "THENY=1:Y$=P$:GOTO4100 4225 IFP0=PANDLEFT$(P$,4)="150 "THEN4100:REMPRINTP$:GOTO4100 -4230 IFP0<>SPORP$=""THEN4235 +4230 IFP0<>SPORP$=""THEN4250 4231 Y2=0:Y1=Y1+1:IFS8=0THENPRINTMID$(P$,34):GOTO4100 4232 PRINTP$:GOTO4100 -4235 IFP0=PTHEN4100 -4240 Y2=Y2+1:IFY=0AND(Y1>2ORY2""ANDST=0THEN4244 -4245 GOSUB800:IFP$<>""THENPRINTP$:GOTO4245 -4250 PRINT#5,"ath"+MID$(STR$(SP),2):GOSUB900:IFP$<>"ok"THEN4250 +4250 REM +4260 Y2=Y2+1:IFY=0AND(Y1>2ORY2""ANDST=0THEN4280 +4290 GOSUB800:IFP$<>""THENPRINTP$:GOTO4290 +4295 PRINT#5,"ath"+MID$(STR$(SP),2):GOSUB900:IFP$<>"ok"THEN4295 4299 RETURN 5000 P$="" 5005 PRINT"{reverse on} {reverse off}{left}"; @@ -229,14 +229,13 @@ 6170 OPEN1,UN,15:OPEN8,UN,8,"@0:"+F$+X$ 6180 INPUT#1,E:IFE<>0THENCLOSE8:CLOSE1:PRINT"{reverse on}{red}Failed to open "+F$+"{reverse off}";CO$:GOTO6250 6190 FX=1 -6200 PL=LEN(P$):IFP0<>PTHEN6230 -6205 IFPL=0THEN6100 +6200 PL=LEN(P$):IFP0<>PORPL=0THEN6230 6210 Y0=0:IFY=1ANDPL>0THENY$=P$:GOTO6100 6215 LP$=LEFT$(P$,4):IFLP$="550 "THENPRINTP$:GOTO6250 6220 IFLP$="226 "THENY=1:Y$=P$:GOTO6100 6225 IFLP$="150 "THENPRINTLEFT$("{up}",SGN(TT))+P$:PRINT:GOSUB6400:GOTO6100 6230 IFP0=SPANDPL>0THENPRINT#8,P$;:TT=TT+PL:GOSUB6500:Y0=0:GOTO6100 -6235 IFP0=PTHEN6100 +6235 IFP0=SPTHEN6100 6240 Y0=Y0+1:IFY=0THENIFY0""THENGOSUB900:GOTO6245 6247 PRINTY$:PRINT "{reverse on}{light green}";MID$(STR$(TT),2);" bytes transferred.{reverse off}";CO$ @@ -251,7 +250,7 @@ 6450 IFI0=0THENRETURN 6460 IFLEFT$(MID$(P$,I0+1),4)<>"byte"THENRETURN 6470 TB=VAL(MID$(P$,I+1,I0-I)):Y1=TB+10:RETURN -6500 PRINT"{up}{space*15}{left*15}"+STR$(TT):RETURN +6500 PRINT"{up} {left*15}"+STR$(TT):RETURN 7000 FX=0:X$=",p,r":P$="TYPE I":GOSUB600:GOSUB850:IFP$=""THEN7000 7010 FX$=RIGHT$(F$,2) 7015 IFLEFT$(F$,1)=" "THENF$=MID$(F$,2):GOTO7015 diff --git a/cbm8bit/src/ftp64-128-vic.bas b/cbm8bit/src/ftp64-128-vic.bas index 62a1343..329e497 100644 --- a/cbm8bit/src/ftp64-128-vic.bas +++ b/cbm8bit/src/ftp64-128-vic.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Wednesday, July 19, 2017 11:57:42 AM -!- Import of : -!- c:\src\zimodem\cbm8bit\src\ftp64-128.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM FTP64/128 1200B 2.0+ 2 REM UPDATED 09/02/2021 02:39A @@ -26,17 +23,19 @@ 101 REM 102 REM 110 REM -120 PRINTCO$;"{clear}{down*2}FTP v1.8":PRINT"Requires C64Net WiFi firmware 2.0+" +120 PRINTCO$;"{clear}{down*2}FTP v1.8":PRINT"Requires Zimodem firmware 2.0+" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 199 REM ---- ZIMODEM SETUP 200 UN=PEEK(254):IP$="":CR$=CHR$(13)+CHR$(10) 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem..." 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=248s0=1s41=0i4";CR$;CHR$(19);:L9=248 235 GOSUB900:VR=VAL(P$):IFVR<2.0THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 @@ -66,11 +65,13 @@ 598 REM --- TRANSMIT P$ TO THE OPEN SOCKET ! 600 OP$=P$:SYSML+9:C8$=MID$(STR$(PEEK(MV+8)),2):E$="ok":IFVR>3THENE$=C8$ 610 PRINT#5,"ats42=";C8$;"tp+";QU$;P$;QU$ -620 SYSML:IFP$<>E$THENPRINT"{reverse on}{red}xerr:{reverse off}";CO$;P$:P$=OP$:GOTO600 +620 SYSML:IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:RETURN +625 IFP$<>E$THENPRINT"{reverse on}{red}xerr:{reverse off}";CO$;P$:P$=OP$:GOTO600 630 RETURN 650 OP$=P$:SYSML+9:C8$=MID$(STR$(PEEK(MV+8)),2):PN$=MID$(STR$(LEN(P$)),2) 660 PRINT#5,"ats42=";C8$;"t+";PN$:PRINT#5,P$:E$="ok":IFVR>3THENE$=C8$ -670 SYSML:IFP$<>E$THENPRINT"xerr:";P$:P$=OP$:GOTO650 +670 SYSML:IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:RETURN +675 IFP$<>E$THENPRINT"xerr:";P$:P$=OP$:GOTO650 680 RETURN 798 REM --- GET P$ FROM SOCKET P 800 P$="":E=0 @@ -156,6 +157,7 @@ 1420 GET#5,A$:IFA$<>""THEN1420 1425 PRINT#5,"atc";MID$(STR$(P),2):GOSUB900:IFP$="ok"THENE=0:RETURN 1430 PRINT"{pink}{reverse on}Retry to change back to ";H0$;"{reverse off}";CO$:GOTO1420 +1440 REM ---- main command prompt 2000 PRINT"Command (?): ";:GOSUB5000 2005 IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:P$="quit" 2010 IFP$=""THENGOSUB800:PRINTP$:GOTO2000 @@ -179,27 +181,27 @@ 4000 P$="TYPE A":GOSUB600:GOSUB850:IFP$=""THEN4000 4005 PRINTP$:CC$="&m10&d10&m13cp":GOSUB1300:IFE=1THENRETURN 4010 P$="PWD":GOSUB600:GOSUB850:IFP$=""THEN4010 -4020 REMPRINTP$:P$="PORT "+IP$+",198,76":GOSUB600:GOSUB850:IFP$=""THEN4020 +4020 REM 4025 GET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4025 4030 PRINTP$:P$="LIST":GOSUB600 -4040 REMGOSUB900:IFLEFT$(P$,5)="ring "THEN4040 +4040 REM 4050 Y=0:Y$="":Y1=0:Y2=0:Y3=5:IFBA>4800THENY3=10 -4100 REMGET#5,A$:IFST=0ANDA$<>""THENGOSUB900:GOTO4100 -4110 GETA$:IFA$=" "ORA$="{ct c}"THENPRINT:PRINT"Aborted.":GOTO4250 +4100 REM +4110 GETA$:IFA$=" "ORA$="{ct c}"THENPRINT:PRINT"Aborted.":GOTO4295 4150 GOSUB800 4200 IFP0=PANDY=1ANDP$<>""THENY$=P$:GOTO4100 -4210 IFP0=PANDP$=""THEN4100 +4210 IFP0=SPANDP$=""THEN4100 4220 IFP0=PANDLEFT$(P$,4)="226 "THENY=1:Y$=P$:GOTO4100 4225 IFP0=PANDLEFT$(P$,4)="150 "THEN4100:REMPRINTP$:GOTO4100 -4230 IFP0<>SPORP$=""THEN4235 +4230 IFP0<>SPORP$=""THEN4250 4231 Y2=0:Y1=Y1+1:IFS8=0THENPRINTMID$(P$,34):GOTO4100 4232 PRINTP$:GOTO4100 -4235 IFP0=PTHEN4100 -4240 Y2=Y2+1:IFY=0AND(Y1>2ORY2""ANDST=0THEN4244 -4245 GOSUB800:IFP$<>""THENPRINTP$:GOTO4245 -4250 PRINT#5,"ath"+MID$(STR$(SP),2):GOSUB900:IFP$<>"ok"THEN4250 +4250 REM +4260 Y2=Y2+1:IFY=0AND(Y1>2ORY2""ANDST=0THEN4280 +4290 GOSUB800:IFP$<>""THENPRINTP$:GOTO4290 +4295 PRINT#5,"ath"+MID$(STR$(SP),2):GOSUB900:IFP$<>"ok"THEN4295 4299 RETURN 5000 P$="" 5005 PRINT"{reverse on} {reverse off}{left}"; @@ -222,14 +224,13 @@ 6170 OPEN1,UN,15:OPEN8,UN,8,"@0:"+F$+X$ 6180 INPUT#1,E:IFE<>0THENCLOSE8:CLOSE1:PRINT"{reverse on}{red}Failed to open "+F$+"{reverse off}";CO$:GOTO6250 6190 FX=1 -6200 PL=LEN(P$):IFP0<>PTHEN6230 -6205 IFPL=0THEN6100 +6200 PL=LEN(P$):IFP0<>PORPL=0THEN6230 6210 Y0=0:IFY=1ANDPL>0THENY$=P$:GOTO6100 6215 LP$=LEFT$(P$,4):IFLP$="550 "THENPRINTP$:GOTO6250 6220 IFLP$="226 "THENY=1:Y$=P$:GOTO6100 6225 IFLP$="150 "THENPRINTLEFT$("{up}",SGN(TT))+P$:PRINT:GOSUB6400:GOTO6100 6230 IFP0=SPANDPL>0THENPRINT#8,P$;:TT=TT+PL:GOSUB6500:Y0=0:GOTO6100 -6235 IFP0=PTHEN6100 +6235 IFP0=SPTHEN6100 6240 Y0=Y0+1:IFY=0THENIFY0""THENGOSUB900:GOTO6245 6247 PRINTY$:PRINT "{reverse on}{light green}";MID$(STR$(TT),2);" bytes transferred.{reverse off}";CO$ diff --git a/cbm8bit/src/gopher64-128-vic.bas b/cbm8bit/src/gopher64-128-vic.bas new file mode 100644 index 0000000..4898e64 --- /dev/null +++ b/cbm8bit/src/gopher64-128-vic.bas @@ -0,0 +1,208 @@ +!-------------------------------------------------- +!- 3/2024 +!-------------------------------------------------- +1 REM GOPHER64/128 1200B 2.0+ +2 REM UPDATED 03/02/2024 01:33A +5 POKE254,8:IFPEEK(186)>7THENPOKE254,PEEK(186) +10 SY=PEEK(65532):IFSY=61THENPOKE58,254:CLR +12 IFSY=34THENX=23777:POKEX,170:IFPEEK(X)<>170THENPRINT"<16k":STOP +15 OPEN5,2,0,CHR$(8):IFPEEK(65532)=34THENPOKE56,87:POKE54,87:POKE52,87 +17 P$="ok":POKE186,PEEK(254):BA=1200:TB$=CHR$(32+128) +20 CR$=CHR$(13):PRINTCHR$(14);:SY=PEEK(65532):POKE53280,254:POKE53281,246 +30 CO$="{light blue}":IFSY=226THENML=49152:POKE665,73-(PEEK(678)*30) +35 IFSY=34THENML=22273:IFPEEK(ML)<>76THENCLOSE5:LOAD"pmlvic.bin",peek(254),1:RUN +38 IFSY=34THENPOKE36879,27:CO$=CHR$(31) +40 IFSY=226ANDPEEK(ML+1)<>209THENCLOSE5:LOAD"pml64.bin",PEEK(254),1:RUN +50 S8=0:IFSY=61THENML=4864:POKE981,15:S8=PEEK(215)AND128:IFS8=128THENSYS30643 +60 IFSY=61ANDPEEK(ML+1)<>217THENCLOSE5:LOAD"pml128.bin",PEEK(254),1:RUN +70 IFSY=61ANDS8=128THENCO$=CHR$(159) +90 IFSY<>34THENPOKE56579,0:REM WHY DOES THIS WORK +100 MV=ML+18:POKEMV+14,8:DD=56577:IFSY=34THENDD=37136:REM FIX BUFAP +101 REM +102 PRINT:PRINT"{clear}{down}{left}";:NC=POS(0):NL=23:IFSY=34THENNL=21 +110 REM +120 PRINTCO$;"{clear}{down*2}GOPHER v1.0":PRINT"Requires Zimodem firmware 2.0+" +140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT +199 REM ---- ZIMODEM SETUP +200 UN=PEEK(254):IP$="":CR$=CHR$(13)+CHR$(10) +201 PH=0:PT=0:MV=ML+18 +202 PRINT "Initializing modem..." +203 GET#5,A$:IFA$<>""THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +208 GET#5,A$:IFA$<>""THEN208 +210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; +220 GOSUB900:IFP$<>"ok"THEN208 +225 GOSUB9000 +228 GET#5,A$:IFA$>""THEN228 +230 PRINT#5,"ate0v1x1f3q0s40=248s0=1s41=0i4";CR$;CHR$(19);:L9=248 +235 GOSUB900:VR=VAL(P$):IFVR<2.0THENPRINT"Zimodem init failed: ";P$:STOP +240 GOSUB900:IFP$<>"ok"THEN203 +245 GET#5,A$:IFA$<>""THEN245 +280 DIMBU$(30,1):BU=0 +290 DIMPA$(100):DIMPA(4):DIMSC$(25):DIMSC(25):REM top,bot,showtop,numlink +300 PRINT"Enter a host:port, eg:" +311 PRINT" gopher.quux.org:70" +320 PRINT":";:GOSUB9500:UR$=P$:IFUR$=""THENEND +330 IFVAL(RIGHT$(UR$,1))=0ANDRIGHT$(UR$,1)<>"0"THENUR$=UR$+":70" +500 BU$(0,0)=UR$:BU$(0,1)="":GOTO2000:REM -- GO BEGIN! +598 REM --- TRANSMIT P$ TO THE OPEN SOCKET ! +600 OP$=P$:SYSML+9:C8$=MID$(STR$(PEEK(MV+8)),2):E$="ok":IFVR>3THENE$=C8$ +610 PRINT#5,"ats42=";C8$;"tp+";QU$;P$;QU$ +620 SYSML:IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:RETURN +625 IFP$<>E$THENPRINT"{reverse on}{red}xerr:'";P$;"'";E$;"'{reverse off}";CO$;P$:P$=OP$:GOTO600 +630 RETURN +650 OP$=P$:SYSML+9:C8$=MID$(STR$(PEEK(MV+8)),2):PN$=MID$(STR$(LEN(P$)),2) +660 PRINT#5,"ats42=";C8$;"t+";PN$:PRINT#5,P$:E$="ok":IFVR>3THENE$=C8$ +670 SYSML:IF(PEEK(DD)AND16)=0THENPRINT"{red}Lost connection";CO$:RETURN +675 IFP$<>E$THENPRINT"xerr:'";P$;"'";E$;"'":P$=OP$:GOTO650 +680 RETURN +798 REM --- GET P$ FROM SOCKET P +800 P$="":E=0 +810 GOSUB930:IFP0<>PANDP0<0THENPRINT"Unexpected packet id: ";P0;"/";P:STOP +820 IFP0=0THENE=1:RETURN:REM FAIL +830 RETURN +898 REM --- GET E$ FROM MODEM, OR ERROR +900 E$="" +910 SYSML +920 IFE$<>""ANDP$<>E$THENPRINT"{reverse on}{red}Comm error. Expected ";E$;", Got ";P$;CO$;"{reverse off}" +925 RETURN +927 REM ---- LOW LVL PACKET READ +930 PR=0:GET#5,P$:IFP$<>""THEN930 +940 PRINT#5,CHR$(17); +945 SYSML+6:P0=PEEK(MV+2):P1=PEEK(MV+4):P2=PEEK(MV+6) +950 PL=PEEK(MV+0):CR=PEEK(MV+1):C8=PEEK(MV+8) +960 IFP0>0ANDP2<>C8THEN985 +970 IFP1=0THENP$="" +980 IFP0>0ORCR=0THENRETURN +985 GET#5,P$:IFP$<>""THEN985 +990 PRINT"{yellow}PACKET-RETRY";CO$:PRINT#5,"atl":GOTO945 +995 PRINT"Expected ";E$;", got ";A$:STOP +998 REM --- CONNECT TO HOST +1000 E=0:QU$=CHR$(34):REM BEGIN! +1010 AT$="" +1020 GET#5,A$:IFA$<>""THEN1020 +1100 PRINT#5,"ath"+AT$+"&d10&m13&m10cp";QU$;UR$;QU$:E=0 +1105 GOSUB900:SP=0:IFP$="ok"THEN1020 +1110 IFLEN(P$)>8ANDLEFT$(P$,8)="connect "THENP=VAL(MID$(P$,9)):GOTO1200 +1112 IFLEN(P$)=0ANDE<3THENE=E+1:GOTO1105 +1115 PRINT"{pink}{reverse on}Unable to connect to ";UR$;"{reverse off}";CO$:E=1:RETURN +1120 IT=TI+40 +1130 P9=0:GOSUB800:IFP$<>""THENIT=TI+10 +1140 IFTI10ORX>100THEN1390 +1320 GOSUB930:IFP$=""THENPT=PT+1:GOTO1310 +1330 PT=0:PA$(X)=P$:X=X+1:GOTO1310 +1390 PA(1)=X-1:PRINT#5,"ath0":GOSUB9000:RETURN +1999 REM BUILD PAGE +2000 UR$=BU$(BU,0):GOSUB1000:IFE>0THEN300 +2010 UR$=BU$(BU,1):GOSUB1300:PRINT"{reverse on}Parsing...{reverse off}"; +2100 PL=PA(2):SL=0:FORI=0to25:SC$(I)="":SC(I)=-1:NEXT +2110 IFSL>=NLORPL>=PA(1)THEN2500 +2120 P$=PA$(PL):LS$=LEFT$(P$,1):LS=VAL(LS$):P$=MID$(P$,2) +2130 IFLS$="0"ORLS=1ORLS=7THENSC(SL)=PL +2140 GOSUB9250:SC$(SL)=PN$(0) +2190 PL=PL+1:SL=SL+1:GOTO2110 +2500 SE=-1:SF=0 +2510 FORI=0TONL:IFSC(I)>=0ANDSE=-1THENSE=I +2520 NEXT +2600 PRINT"{clear}"; +2605 PRINT"{home}";:RC$=CHR$(128+13):LC=SF+NC +2610 FORI=0TONL:IFI=SETHENPRINT"{reverse on}"; +2620 IFSF=0THENPRINTLEFT$(SC$(I),NC);:GOTO2680 +2625 L=LEN(SC$(I)):IFL=LCTHENPRINTMID$(SC$(I),SF,NC);:GOTO2680 +2640 PRINTMID$(SC$(I),SF); +2680 IFI=SETHENPRINT"{reverse off}"; +2690 PRINTRC$;:NEXT +2695 PRINT"{reverse on}CRSR U/D/L/R, Q, RET:{reverse off}{up}";CHR$(128+13); +2699 REM -- MAIN MENU +2700 GETA$:IFA$=""THEN2700 +2710 IFA$="{up}"THEN3000 +2715 IFA$="{down}"THEN3100 +2720 IFA$="{left}"THEN3200 +2725 IFA$="{right}"THEN3300 +2730 IFA$="q"THEN3400 +2740 IFA$=CHR$(13)THEN3500 +2750 GOTO 2700 +3000 I=SE-1 +3010 IFI<0THEN3050 +3020 IFSC(I)>=0THENSE=I:GOTO2605 +3030 I=I-1:GOTO3010 +3050 IFPA(2)<=0THEN2700 +3060 PA(2)=PA(2)-NL:IFPA(2)<0THENPA(2)=0 +3070 PRINT"PARSING...";:GOTO2100 +3100 I=SE+1 +3110 IFI>=NLTHEN3150 +3120 IFSC(I)>=0THENSE=I:GOTO2605 +3130 I=I+1:GOTO3110 +3150 IFPL>=PA(1)THEN2700 +3160 PA(2)=PL:PRINT"PARSING...";:GOTO2100 +3200 IFSF=0THEN3230 +3210 SF=SF-NC:IFSF<0THENSF=0 +3220 GOTO2600 +3230 IFBU=0THEN2700 +3240 GOSUB9300:PRINT"PREVIOUS PAGE (Y/N)?"; +3250 GETA$:IFA$="n"THENGOSUB9300:GOTO2700 +3260 IFA$<>"y"THEN3250 +3270 BU=BU-1:GOSUB9300:PRINT"LOADING...";:GOSUB9000:GOTO2000 +3300 IFSF+NC>80THEN2700 +3310 SF=SF+NC:GOTO2600 +3400 GOSUB9300:PRINT"QUIT (Y/N)?"; +3410 GETA$:IFA$="n"THENGOSUB9300:GOTO2700 +3420 IFA$<>"y"THEN3410 +3430 PRINT#5,"atz":GOSUB900:CLOSE5:END +3500 GOSUB9300:PRINT"OPEN (Y/N)?"; +3510 GETA$:IFA$="n"THENGOSUB9300:GOTO2700 +3515 IFA$<>"y"THEN3510 +3520 GOSUB9300:PRINT"LOADING...";:GOSUB9000 +3530 IFLEFT$(PA$(SC(SE)),1)="1"THEN3600 +3540 IFLEFT$(PA$(SC(SE)),1)="0"THEN4000 +3550 IFLEFT$(PA$(SC(SE)),1)="7"THEN5000 +3590 GOSUB9300:PRINT"ERROR.";:GOTO2700 +3600 IFBU>=30THEN3590 +3610 P$=PA$(SC(SE)):GOSUB9200:IFVAL(PN$(3))=0THENPN$(3)="70" +3620 BU=BU+1:BU$(BU,0)=PN$(2)+":"+PN$(3):BU$(BU,1)=PN$(1) +3630 GOSUB9300:GOTO2000 +4000 IFBU>=30THEN3590 +4010 P$=PA$(SC(SE)):GOSUB9200:IFVAL(PN$(3))=0THENPN$(3)="70" +4020 BU=BU+1:BU$(BU,0)=PN$(2)+":"+PN$(3):BU$(BU,1)=PN$(1) +4030 UR$=PN$(2)+":"+PN$(3):GOSUB1000:IFE>0THEN3590 +4040 UR$=PN$(1):GOSUB1300 +4050 FORI=0TOPA(1):PA$(I)="i"+PA$(I):IFLEN(PA$(I))<81ORPA(1)=100THEN4090 +4060 FORX=PA(1)TOISTEP-1:PA$(X+1)=PA$(X):NEXT:PA(1)=PA(1)+1 +4070 PA$(I+1)=MID$(PA$(I),79):PA$(I)=LEFT$(PA$(I),80) +4090 NEXT:GOTO2100 +5000 GOSUB9300:PRINT"SEARCH: ";:GOSUB9500:IFP$=""THENGOSUB9300:GOTO2700 +5010 SP$=P$:P$=PA$(SC(SE)):GOSUB9200:IFVAL(PN$(3))=0THENPN$(3)="70" +5020 BU=BU+1:BU$(BU,0)=PN$(2)+":"+PN$(3):BU$(BU,1)=PN$(1)+"?"+SP$ +5030 GOTO2000 +9000 TT=TI+100 +9010 SYSML+12:IFTI""THEN9015 +9020 RETURN +9200 X=1:I=1:PN=0:REM -- TABERATE +9210 PN$(PN)="": +9220 IFMID$(P$,I,1)=TB$ANDI>XTHENPN$(PN)=MID$(P$,X,I-X):PN=PN+1:X=I+1 +9230 I=I+1:IFICHR$(20)THENPRINTA$;"{reverse on} {reverse off}{left}";:P$=P$+A$:GOTO9510 +9540 IFP$=""THEN9510 +9550 P$=LEFT$(P$,LEN(P$)-1):PRINT" {left*2} {left}{reverse on} {reverse off}{left}";:GOTO9510 +50000 OPEN5,2,0,CHR$(8) +50010 GET#5,A$:IFA$<>""THENPRINTA$; +50020 GETA$:IFA$<>""THENPRINT#5,A$; +50030 GOTO 50010 +55555 U=8:F$="gopher":OPEN1,U,15,"s0:"+F$:CLOSE1:SAVE(F$),U:VERIFY(F$),U diff --git a/cbm8bit/src/irc-sw64.bas b/cbm8bit/src/irc-sw64.bas index 3ac31ea..5b7ee5d 100644 --- a/cbm8bit/src/irc-sw64.bas +++ b/cbm8bit/src/irc-sw64.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, August 17, 2021 9:45:22 AM -!- Import of : -!- z:\lost+found\irc.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM IRC64SW 19200B 1.8+ 2 REM UPDATED 10/13/2021 12:54A @@ -37,10 +34,12 @@ 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem..." 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=250i4";CR$;CHR$(19); 235 GOSUB900:VR=VAL(P$):IFVR<1.8THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 diff --git a/cbm8bit/src/irc64-128-vic.bas b/cbm8bit/src/irc64-128-vic.bas index e1f7400..f69f142 100644 --- a/cbm8bit/src/irc64-128-vic.bas +++ b/cbm8bit/src/irc64-128-vic.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, July 18, 2017 2:54:26 AM -!- Import of : -!- c:\src\zimodem\cbm8bit\src\irc64-128.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM IRC64/128 1200B 1.8+ 2 REM UPDATED 10/13/2021 02:58A @@ -24,7 +21,7 @@ 90 IFSY<>34THENPOKE56579,0:REM WHY DOES THIS WORK 100 REM GET THE BAUD RATE 110 P$="a" -120 PRINTCO$;"{clear}{down*2}IRC CHAT v1.5":PRINT"Requires C64Net WiFi firmware 1.8+" +120 PRINTCO$;"{clear}{down*2}IRC CHAT v1.5":PRINT"Requires Zimodem firmware 1.8+" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- 198 REM GET STARTED ! @@ -33,10 +30,12 @@ 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem..." 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=250i4";CR$;CHR$(19); 235 GOSUB900:VR=VAL(P$):IFVR<1.8THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 diff --git a/cbm8bit/src/loader64.bas b/cbm8bit/src/loader64.bas new file mode 100644 index 0000000..d1023e7 --- /dev/null +++ b/cbm8bit/src/loader64.bas @@ -0,0 +1,22 @@ +!-------------------------------------------------- +!- Import of : +!- c:\src\zimodem\cbm8bit\src\loader64.prg +!- Commodore 64 +!-------------------------------------------------- +10 IFPEEK(49153)<>54THENLOAD"v-1541",8,1:RUN +20 OPEN5,2,0,CHR$(8):TI$="000000":T1=0 +30 PRINT#5,"at&p0r0e0f0":TT=TI+100:GOSUB900:PRINT"..."; +40 GOSUB920:PRINT".";:T1=T1+1:IFT1>20THENPRINT"fail.":CLOSE5:END +50 PRINT#5,"at":TT=TI+100:GOSUB900 +60 GET#5,A$:GET#5,B$:A$=A$+B$:IFLEFT$(A$,2)<>"ok"ANDLEFT$(A$,2)<>"OK"THEN40 +80 GOSUB920 +90 PRINT#5,"ats43=2400 +comet64":TT=TI+100:GOSUB900:GOSUB920 +100 GET#5,A$:IFA$="E"orA$="e"THENprint"fail.":CLOSE5:END +130 PRINT"you can now load from device#2." +210 PRINT"* reset modem to use other wifi programs." +220 CLOSE5:SYS49152 +230 END +900 IFTI""THEN920 +930 RETURN diff --git a/cbm8bit/src/telnet64-128-vic.bas b/cbm8bit/src/telnet64-128-vic.bas index 10383be..1c42ef7 100644 --- a/cbm8bit/src/telnet64-128-vic.bas +++ b/cbm8bit/src/telnet64-128-vic.bas @@ -27,7 +27,7 @@ 101 REM 102 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}TELNET v1.6":PRINT"Requires C64Net WiFi firmware 1.8+" +120 PRINTCO$;"{clear}{down*2}TELNET v1.6":PRINT"Requires Zimodem firmware 1.8+" 130 PRINT"1200 baud version" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 197 REM -------------------------------- @@ -37,7 +37,7 @@ 201 PH=0:PT=0:MV=ML+18:CR$=CHR$(13)+CHR$(10):QU$=CHR$(34) 202 PRINT "Initializing modem...";:GOSUB6000 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;"athz0&p0f0e0";CR$; +205 PRINT#5,CR$;"athz0&p0f0e0s62=1";CR$; 206 GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT".";:PRINT#5,CR$;"ate0n0r0v1f0";CR$; @@ -87,7 +87,7 @@ 2110 HO$=HO$(X-1):PO=PO(X-1) 2300 PRINT"{reverse on}{light green}Connecting to ";HO$;":";MID$(STR$(PO),2);"...{reverse off}";CO$ 2310 GET#5,A$:IFA$<>""THEN2310 -2320 PRINT#5,CR$;"atf3hctep";QU$;HO$;":";MID$(STR$(PO),2);QU$;CR$; +2320 PRINT#5,CR$;"ats62=1f3hctep";QU$;HO$;":";MID$(STR$(PO),2);QU$;CR$; 2330 GOSUB900:IFLEN(P$)>7ANDLEFT$(P$,7)="connect"THEN2400 2340 PRINT"{reverse on}{red}Unable to connect to ";HO$;":";MID$(STR$(PO),2) 2350 RETURN diff --git a/cbm8bit/src/telnetd64.bas b/cbm8bit/src/telnetd64.bas index fcdb26c..c50ed8f 100644 --- a/cbm8bit/src/telnetd64.bas +++ b/cbm8bit/src/telnetd64.bas @@ -9,7 +9,7 @@ 5 POKE254,8:IFPEEK(186)>7THENPOKE254,PEEK(186) 6 IFPEEK(49153)<>43THENLOAD"rds64.bin",PEEK(254),1:RUN 8 POKE53280,14:POKE53281,6 -10 PRINTCHR$(14);"telnetd64 v1.0":PRINT"Requires C64Net WiFi firmware 2.7+" +10 PRINTCHR$(14);"telnetd64 v1.0":PRINT"Requires Zimodem firmware 2.7+" 20 PRINT"1200 baud version" 30 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 1000 PRINT"Configure Server:":PRINT diff --git a/cbm8bit/src/weather64.bas b/cbm8bit/src/weather64.bas index 31a26c0..146b623 100644 --- a/cbm8bit/src/weather64.bas +++ b/cbm8bit/src/weather64.bas @@ -242,7 +242,7 @@ 62500 PRINT#5,"ati2" 62510 INPUT#5,P$:IFP$=""ORP$="ok"THEN62500 62520 IP$=P$:IFLEN(P$)<8THEN62500 -62530 PRINT#5,"at&m10&m13&d13a6502":INPUT#5,P$:IFP$<>"ok"THEN62530 +62530 PRINT#5,"at&m10&m13&d13s41=1s0=1a6502":INPUT#5,P$:IFP$<>"ok"THEN62530 62540 PRINT"your ip address is ";IP$ 62550 PRINT"please set your router to forward" 62560 PRINT"port 6502 to the above ip address." diff --git a/cbm8bit/src/wget-sw64.bas b/cbm8bit/src/wget-sw64.bas index ea778ff..1551d62 100644 --- a/cbm8bit/src/wget-sw64.bas +++ b/cbm8bit/src/wget-sw64.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, August 17, 2021 9:46:11 AM -!- Import of : -!- z:\lost+found\wget.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM WGET4SW 19200B 2.0+ 2 REM UPDATED 10/13/2021 12:54A @@ -36,11 +33,12 @@ 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem...":CR$=CHR$(13)+CHR$(10) 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 -225 GET#5,A$:IFA$<>""THEN225 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=248i4";CR$;CHR$(19); 235 GOSUB900:VR=VAL(P$):IFVR<2.0THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 diff --git a/cbm8bit/src/wget64-128-vic.bas b/cbm8bit/src/wget64-128-vic.bas index 9ccf43b..f887119 100644 --- a/cbm8bit/src/wget64-128-vic.bas +++ b/cbm8bit/src/wget64-128-vic.bas @@ -1,8 +1,5 @@ !-------------------------------------------------- -!- Tuesday, July 18, 2017 2:54:41 AM -!- Import of : -!- c:\src\zimodem\cbm8bit\src\wget64-128.prg -!- Commodore 64 +!- 2/2024 !-------------------------------------------------- 1 REM WGET4/128 1200B 2.0+ 2 REM UPDATED 10/13/2021 12:54A @@ -21,22 +18,23 @@ 55 IFSY=61ANDS8=128THENXB=2400:CO$=CHR$(159) 60 IFSY=61ANDPEEK(ML+1)<>217THENCLOSE5:LOAD"pml128.bin",PEEK(254),1:RUN 80 IFSY=226ANDUM>0THENSYSUM:SYSUM+3:X=PEEK(789):SYSUM+9:IFX=234THENXB=1200 -100 IFSY<>34THENPOKE56579,0 +100 DD=37136:IFSY<>34THENPOKE56579,0:DD=56577 101 REM 102 REM 110 P$="a" -120 PRINTCO$;"{clear}{down*2}WGET v1.5":PRINT"Requires C64Net WiFi firmware 2.0+" +120 PRINTCO$;"{clear}{down*2}WGET v1.5":PRINT"Requires Zimodem firmware 2.0+" 140 PRINT"By Bo Zimmerman (bo@zimmers.net)":PRINT:PRINT 198 REM --- MODEM INIT 200 UN=PEEK(254) 201 PH=0:PT=0:MV=ML+18 202 PRINT "Initializing modem...":CR$=CHR$(13)+CHR$(10) 203 GET#5,A$:IFA$<>""THEN203 -205 PRINT#5,CR$;CR$;"athz0&p0f0e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 +205 PRINT#5,CR$;CR$;"athz0&p0f4e0";CR$;:GOSUB900:IFP$<>"ok"THEN203 208 GET#5,A$:IFA$<>""THEN208 210 PRINT#5,CR$;"ate0n0r0v1q0";CR$; 220 GOSUB900:IFP$<>"ok"THEN208 -225 GET#5,A$:IFA$<>""THEN225 +225 FORI=1TO100:NEXT +228 GET#5,A$:IFA$>""THEN228 230 PRINT#5,"ate0v1x1f3q0s40=248i4";CR$;CHR$(19); 235 GOSUB900:VR=VAL(P$):IFVR<2.0THENPRINT"Zimodem init failed: ";P$:STOP 240 GOSUB900:IFP$<>"ok"THEN203 @@ -152,13 +150,15 @@ 2280 INPUT#1,E:IFE>0THENPRINT"{reverse on}{red}Unable to open "+OF$+": "+STR$(E)+"{reverse off}";CO$:STOP 2290 REM 2300 GOSUB930:IFP0<>PTHEN2300 -2310 IFP1=0ORP0=0THEN2300 +2310 IF(PEEK(DD)AND16)=0THENPRINT:PRINT"{reverse on}{red}LOST CONNECTION{reverse off}":GOTO2385 +2320 IFP1=0ORP0=0THEN2300 2330 TL=TL+LEN(P$) 2340 IFLEN(P$)>0THENPRINT#8,P$; 2350 TP=INT(TL/CL*100.0) 2360 PRINT" {left*15}"+STR$(TL)+" ("+STR$(TP)+"% )" 2370 PRINT"{up}";:IFTL lvl: + print(s) +def keypressed(key): + if hasattr(key, 'char'): + tmps[0] += key.char + py_print(key.char, end='') + sys.stdout.flush() + elif key == keyboard.Key.enter: + s[0] = tmps[0] + tmps[0] = '' + print("\n\r") + +def keyreleased(key): + pass + +def terminal(): + listener = Listener(on_press=keypressed, on_release=keyreleased) + listener.start() + print('Enter your commands below.\r\nInsert "exit" to leave the application.') + + py_print(">>", end='') + while True : + # get keyboard input + cmd = s[0] + if len(cmd)==0: + time.sleep(0.01) + pass + else: + s[0] = '' + if cmd == 'exit': + ser[0].close() + return True + else: + ser[0].write(bytes(cmd + '\r\n','utf-8')) + out = '' + time.sleep(0.1) + while ser[0].inWaiting() > 0: + while ser[0].inWaiting() > 0: + out += ser[0].read(1).decode('utf-8') + time.sleep(0.1) + if out != '': + print(out) + py_print("\n\r>>", end='') + +def sock_write(b): + if isinstance(b, int): + vprint("socout: "+str(b),0) + sock_conn[0].sendall(bytes([b])) + elif isinstance(b, str): + vprint("socout: "+b,0) + sock_conn[0].sendall(bytes(b, 'utf-8')) + else: + vprint("socout: "+str(len(b))+" bytes",0) + if verbosity > 3: + print("socout buffer:") + print_buf(b) + sock_conn[0].sendall(b) + +def serial_write(b): + if isinstance(b, int): + vprint("serout: "+str(b),0) + ser[0].write(bytes([b])) + elif isinstance(b, str): + vprint("serout: "+b,0) + ser[0].write(bytes(b, 'utf-8')) + else: + global verbosity + vprint("serout: "+str(len(b))+" bytes",0) + if verbosity > 3: + print("serout buffer:") + print_buf(b) + ser[0].write(b) + ser[0].flush() + +def serial_writeln(s): + vprint("serout: "+s,0) + global verbosity + v = verbosity + verbosity = 0 + ser[0].write(bytes(s + "\r\n", 'utf-8')) + ser[0].flush() + verbosity = v + +def serial_transact(cmd, sec=1.0): + flush_serial() + global verbosity + v = verbosity + verbosity = 0 + serial_writeln(cmd) + serial_wait(sec / 0.1) + res = serial_inln() + verbosity = v + vprint("sercmd: "+cmd+": "+str(res),0) + return res + +def serial_wait(max_ct=10): + ct = 0 + while ser[0].inWaiting() == 0 and ct < max_ct: + time.sleep(0.1) + ct = ct + 1 + +# waits for a line of text. if none arrives in time, preserve what it has gotten so far +def serial_inln(): + serial_wait(60) + while ser[0].inWaiting() > 0: + while ser[0].inWaiting() > 0: + c = ser[0].read(1).decode('utf-8') + if c == '\n' or c == '\r': + s = serin[0] + serin[0] = '' + vprint("ser_in: "+s,0) + return s + else: + serin[0] += c + serial_wait(60) + return None + +# returns all bytes it can reasonably wait for from the serial port +def serial_in(expect=0): + global verbosity + serial_wait(100) + bin = [] + while ser[0].inWaiting() > 0: + while ser[0].inWaiting() > 0: + c = ser[0].read(1) + bin.extend(c) + if expect > 0 and len(bin) >= expect: + break + serial_wait(60) + vprint("ser_in: "+str(len(bin))+" bytes",0) + ba = bytearray(bin) + if verbosity > 3: + print("serin buffer:") + print_buf(ba) + return ba + +def flush_serial(): + serin[0] = '' + serial_wait(10) + while ser[0].inWaiting() > 0: + while ser[0].inWaiting() > 0: + ser[0].read(1) + serial_wait(10) + +def sock_wait(max_ct=10): + ct = 0 + while len(sockin[0]) == 0 and ct < max_ct: + time.sleep(0.1) + ct = ct + 1 + +def flush_sock(): + sock_wait(1) + while len(sockin[0]) >0: + sockin[0] = [] # reset sockin buff + sock_wait(1) + +def sock_in_silent(expect=0): + sock_wait(50) + sin = [] + # print("sock_in_len: "+str(len(sockin[0]))) + while len(sockin[0]) >0: + socklock[0].acquire() + sin.extend(sockin[0]) + # print("sin: "+str(len(sin))+"/"+str(sin[0])) + sockin[0] = [] # reset sockin buff + socklock[0].release() + if expect > 0 and len(sin) >= expect: + break + sock_wait(100) + ba = bytearray(sin) + return ba + +def sock_in(expect=0): + global verbosity + ba = sock_in_silent(expect) + vprint("sockin: "+str(len(ba))+" bytes",0) + if verbosity > 3: + print("sockin buffer:") + print_buf(ba) + return ba + +def thread_socket_listener(): + try: + hostname = socket.gethostname() + ipaddr = socket.gethostbyname(hostname) + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind((str(ipaddr), 8089)) + serversocket.listen(5) + print("Listener ("+str(ipaddr)+") started") + while True: + sock_conn[0] = None + connection, address = serversocket.accept() + sock_conn[0] = connection + try: + while True: + time.sleep(0.1) + buf = connection.recv(64) + if len(buf) > 0: + socklock[0].acquire() + sockin[0].extend(buf) + socklock[0].release() + except BaseException as e: + pass + finally: + print("Listener closed") + +def compare_bytearrays(a, b, startswith=False): + la = list(a) + lb = list(b) + if(len(la) < len(lb)): + return False + if not startswith: + if len(la) != len(lb): + return False + for i in range(0,len(lb)): + if la[i] != lb[i]: + return False + return True + +def print_buf(b): + for i in range(0,len(b)): + py_print(str(b[i])+" ",end="") + if (i+1) % 8 == 0: + print("") + if (len(b) + 1) & 8 != 0: + print("") + +def serial_sock_echo(b): + global verbosity + i=0; + vprint("serout: "+str(len(b))+" bytes",0) + if verbosity > 3: + print("serout buffer:") + print_buf(b) + i = 0 + while i < len(b): + bl = i+256 + if bl > len(b): + bl = len(b) + ser[0].write(b[i:bl]) + ser[0].flush() + rb = sock_in_silent(bl-i) + if not compare_bytearrays(rb, b[i:bl]): + vprint("sockin: "+str(i)+" bytes",0) + if verbosity > 3: + print("sockin buffer:") + print_buf(b[:i]) + return i + i += bl-i + vprint("sockin: "+str(i)+" bytes",0) + return i + +def test_atd(baud=1200): + global verbosity + if sock_conn[0] != None: + return errprint("ATD","Previous socked connected") + ipaddr = socket.gethostbyname(socket.gethostname()) + s = serial_transact('atd"'+str(ipaddr)+':8089"', sec=10) + if s is None or s[0:8] != 'CONNECT ': + return errprint("ATD","ATD ("+s+")") + connum = s[8:] + if sock_conn[0] is None: + return errprint("ATD","No Connection") + b = bytearray(os.urandom(1024)) + # send from socket->modem, this will be slow to receive all. + sock_write(b) + sb = serial_in() + if not compare_bytearrays(b, sb): + return errprint("ATD","sock->ser "+str(len(b))) + # send from modem->socket, this will be slow to send. + flush_serial() + flush_sock() + if serial_sock_echo(b) != len(b): + return errprint("ATD","ser->sock "+str(len(b))) + flush_serial() + flush_sock() + ud_tests = [[128, 50], [256, 50], [1024, 15 * (baud / 1200)], [baud * 20, 1]] + for ud_test in ud_tests: + packet_size = round(ud_test[0]) + rounds = round(ud_test[1]) + # download test + for rnd in range(0,rounds): + b = bytearray(os.urandom(packet_size)) + sock_write(b) + rb = serial_in(len(b)) + if not compare_bytearrays(b, rb): + return errprint("ATD","sock->ser "+str(len(b))+" round "+str(rnd)) + serial_write(13) + rb = sock_in(1) + if not compare_bytearrays(bytes([13]), rb): + return errprint("ATD","sock->ser ack round "+str(rnd)) + # upload test + for rnd in range(0,rounds): + b = bytearray(os.urandom(packet_size)) + if serial_sock_echo(b) != len(b): + return errprint("ATD","ser->sock "+str(len(b))+" round "+str(rnd)) + sock_write(13) + rb = serial_in(1) + if not compare_bytearrays(bytes([13]), rb): + return errprint("ATD","sock->ser ack round "+str(rnd)) + + print("ATD : Passed") + sock_conn[0].close() + sock_conn[0] = None + return True + +def test_atc(baud=1200): + if sock_conn[0] != None: + return errprint("ATC","Previous socked connected") + ipaddr = socket.gethostbyname(socket.gethostname()) + s = serial_transact('atc"'+str(ipaddr)+':8089"', sec=10) + if s is None or s[0:8] != 'CONNECT ': + return errprint("ATC","ATC") + connum = s[8:] + if sock_conn[0] is None: + return errprint("ATC","No Connection") + if serial_transact('att"testing!"') != 'OK': + return errprint("ATC","ATT 1") + if sock_in(10).decode() != 'testing!\r\n': + return errprint("ATC","ATT2 "+str(s)) + # should return a conn message, and THEN ok + if serial_transact('atc0') == 'OK': + return errprint("ATC","ATC0:MSG") + if serial_inln() != 'OK': + return errprint("ATC","ATC0:OK") + if serial_transact('ath'+connum) != 'OK': + return errprint("ATC","ATHx") + if serial_transact('atc0') != 'OK': + return errprint("ATC","ATC0 #2" ) + print("ATC : Passed") + sock_conn[0].close() + sock_conn[0] = None + return True + + +def tester(baud): + print("Starting Zimodem test") + if not test_atc(baud): + return False + if not test_atd(baud): + return False + # TODO + return True + +def initialize(port, baud): + global verbosity + ser[0] = serial.Serial( + port=port, + baudrate=1200, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS + ) + + if not ser[0].isOpen(): + print("Unable to open serial port") + return None + serial_writeln('ath0z0r0f4e0&o1&p0b'+str(baud)) + flush_serial() + serial_wait(200) # sometimes z takes a long time + if baud != 1200: + ser[0].close() + ser[0] = serial.Serial( + port=port, + baudrate=baud, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS + ) + cmd = "i4" + if verbosity > 1: + cmd = "&o1i4" + s = serial_transact("at"+cmd) + if s is None or (s[0:2] != '3.' and s[0:2] != '4.'): + print("Unable to initialize") + return None + flush_serial() + print("Modem Initialized "+s) + server_thread = threading.Thread(target=thread_socket_listener, daemon=True) + server_thread.start() + time.sleep(3) # wait for listener to kick off + return ser[0] + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + prog='Zimodem Tester', + description='Runs various tests on a Zimodem. Must always start at 1200bps!') + parser.add_argument('port') + parser.add_argument('baud', default=1200) + parser.add_argument('-t', '--term', default=False) + parser.add_argument('-v', '--verbosity', default=0) + args = parser.parse_args() + verbosity = int(args.verbosity) + baud = int(args.baud) + ser[0] = initialize(args.port, baud) + if ser[0] is None: + sys.exit(-1) + + result = False + try: + if args.term: + terminal() + sys.exit(0) + # configure the serial connections (the parameters differs on the device you are connecting to) + result = tester(baud) + finally: + if ser[0].isOpen(): + time.sleep(1); + serial_write('+++') + time.sleep(1) + if verbosity > 1: + flush_serial() + serial_writeln("at&o0") + time.sleep(1) + v=verbosity + verbosity=0 + buf = serial_in() + print("Log:") + print(buf.decode('utf-8')) + verbosity=v + serial_writeln('atb1200') + ser[0].close() + if result: + print("Test Completed Successfully") + else: + print("Test Failed.") diff --git a/zimodem/connSettings.h b/zimodem/connSettings.h index 4e922b4..915c76e 100644 --- a/zimodem/connSettings.h +++ b/zimodem/connSettings.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,19 +27,19 @@ enum ConnFlag class ConnSettings { public: - boolean petscii = false; - boolean telnet = false; - boolean echo = false; - boolean xonxoff = false; - boolean rtscts = false; - boolean secure = false; + bool petscii = false; + bool telnet = false; + bool echo = false; + bool xonxoff = false; + bool rtscts = false; + bool secure = false; ConnSettings(int flagBitmap); ConnSettings(const char *dmodifiers); ConnSettings(String modifiers); int getBitmap(); int getBitmap(FlowControlType forceCheck); - void setFlag(ConnFlag flagMask, boolean newVal); + void setFlag(ConnFlag flagMask, bool newVal); String getFlagString(); static void IPtoStr(IPAddress *ip, String &str); diff --git a/zimodem/connSettings.ino b/zimodem/connSettings.ino index 0163833..9ef16a7 100644 --- a/zimodem/connSettings.ino +++ b/zimodem/connSettings.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ String ConnSettings::getFlagString() return lastOptions; } -void ConnSettings::setFlag(ConnFlag flagMask, boolean newVal) +void ConnSettings::setFlag(ConnFlag flagMask, bool newVal) { switch(flagMask) { diff --git a/zimodem/filelog.h b/zimodem/filelog.h index 0706c31..b675651 100644 --- a/zimodem/filelog.h +++ b/zimodem/filelog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ enum LogOutputState static unsigned long expectedSerialTime = 1000; -static boolean logFileOpen = false; -static bool logFileDebug= false; +static bool logFileOpen = false; +static bool logFile2Uart= false; static File logFile; static void logSerialOut(const uint8_t c); @@ -37,6 +37,7 @@ 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 void logFileLoop(); static char *TOHEX(const char *s, char *hex, const size_t len); static char *TOHEX(long a); static char *TOHEX(int a); diff --git a/zimodem/filelog.ino b/zimodem/filelog.ino index 0e36994..f9b2b25 100644 --- a/zimodem/filelog.ino +++ b/zimodem/filelog.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ static unsigned long logStartTime = millis(); static unsigned long lastLogTime = millis(); static unsigned long logCurCount = 0; static LogOutputState logOutputState = LOS_NADA; +static char LBUF[256]; +static int pinLook[8]; static uint8_t FROMHEXDIGIT(uint8_t a1) { @@ -124,35 +126,103 @@ static char *TOHEX(long a) return TOHEX((unsigned long)a); } +static bool isChangedBit() +{ + int val; + bool changed=false; + for(int i=0;i<7;i++) + { + switch(i) + { + case 0: val = pinCache[pinDCD]; break; + case 1: val = digitalRead(pinCTS); break; + case 2: val = pinCache[pinRTS]; break; + case 3: val = pinCache[pinDSR]; break; + case 4: val = digitalRead(pinDTR); break; + case 5: val = digitalRead(pinOTH); break; + case 6: val = pinCache[pinRI]; break; + } + if(pinLook[i] != val) + { + pinLook[i] = val; + changed=true; + } + } + return changed; +} + +static void logFileLoop() +{ + if(logFileOpen && logFile2Uart) + { + if(isChangedBit()) + { + char bits[8]; + for(int i=0;i<7;i++) + bits[i] = pinLook[i]?'h':'l'; + bits[7]=0; + logPrintfln("Signals: DCRSTOI (%s)",bits); + } + } +} + + static void logInternalOut(const LogOutputState m, const uint8_t c) { if(logFileOpen) { + unsigned long diff = (millis()-lastLogTime); if((m != logOutputState) ||(++logCurCount > DBG_BYT_CTR) - ||((millis()-lastLogTime)>expectedSerialTime)) + ||(diff>expectedSerialTime)) { - logCurCount=0; - - logOutputState = m; - rawLogPrintln(""); - switch(m) + if((diff<=expectedSerialTime) + &&(logCurCount<= DBG_BYT_CTR-3) + &&(logOutputState != LOS_NADA) + &&(m != LOS_NADA)) + { + switch(m) + { + case LOS_NADA: + break; + case LOS_SocketIn: + rawLogPrintf(", SocI: "); + break; + case LOS_SocketOut: + rawLogPrintf(", SocO: "); + break; + case LOS_SerialIn: + rawLogPrintf(", SerI: "); + break; + case LOS_SerialOut: + rawLogPrintf(", SerO: "); + break; + } + logCurCount+=4; + } + else { - 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; + 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; + } + logCurCount=0; } + logOutputState = m; } lastLogTime=millis(); rawLogPrint(TOHEX(c)); @@ -191,18 +261,17 @@ static void logSocketIn(const uint8_t *c, int n) static void rawLogPrintf(const char* format, ...) { - int ret; va_list arglist; va_start(arglist, format); - vsnprintf(FBUF,sizeof(FBUF), format, arglist); - rawLogPrint(FBUF); + vsnprintf(LBUF,sizeof(LBUF), format, arglist); + rawLogPrint(LBUF); va_end(arglist); - + } static void rawLogPrint(const char* str) { - if(logFileDebug) + if(logFile2Uart) debugPrintf(str); else logFile.print(str); @@ -210,7 +279,7 @@ static void rawLogPrint(const char* str) static void rawLogPrintln(const char* str) { - if(logFileDebug) + if(logFile2Uart) { debugPrintf(str); debugPrintf("\n"); @@ -228,11 +297,10 @@ static void logPrintfln(const char* format, ...) rawLogPrintln(""); logOutputState = LOS_NADA; } - int ret; va_list arglist; va_start(arglist, format); - vsnprintf(FBUF,sizeof(FBUF), format, arglist); - rawLogPrintln(FBUF); + vsnprintf(LBUF,sizeof(LBUF), format, arglist); + rawLogPrintln(LBUF); va_end(arglist); } } @@ -246,11 +314,10 @@ static void logPrintf(const char* format, ...) rawLogPrintln(""); logOutputState = LOS_NADA; } - int ret; va_list arglist; va_start(arglist, format); - vsnprintf(FBUF, sizeof(FBUF), format, arglist); - rawLogPrint(FBUF); + vsnprintf(LBUF, sizeof(LBUF), format, arglist); + rawLogPrint(LBUF); va_end(arglist); } } diff --git a/zimodem/pet2asc.h b/zimodem/pet2asc.h index 9b53fcf..a604140 100644 --- a/zimodem/pet2asc.h +++ b/zimodem/pet2asc.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,7 +22,18 @@ # include "SD.h" # include "SPI.h" # include "driver/uart.h" - static HardwareSerial HWSerial(UART_NUM_2); +# if defined(ARDUINO_ESP32C3_DEV) +# define MAIN_UART_NUM UART_NUM_0 +# define DEBUG_UART_NUM -1 +# elif defined(ARDUINO_ESP32S3_DEV) || (!defined(UART_NUM_2)) +# define MAIN_UART_NUM UART_NUM_1 +# define DEBUG_UART_NUM UART_NUM_0 +# else +# define MAIN_UART_NUM UART_NUM_2 +# define DEBUG_UART_NUM UART_NUM_0 +# endif + static HardwareSerial HWSerial(MAIN_UART_NUM); + static HardwareSerial DBSerial(DEBUG_UART_NUM); #else # include "ESP8266WiFi.h" # define HWSerial Serial diff --git a/zimodem/pet2asc.ino b/zimodem/pet2asc.ino index b2db343..e371353 100644 --- a/zimodem/pet2asc.ino +++ b/zimodem/pet2asc.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ unsigned char petToAscTable[256] PROGMEM = { }; unsigned char ascToPetTable[256] PROGMEM = { -0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x14,0x20,0x0a,0x11,0x93,0x0d,0x0e,0x0f, +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x14,0xa0,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, diff --git a/zimodem/phonebook.h b/zimodem/phonebook.h index c3471c0..84d3eaa 100644 --- a/zimodem/phonebook.h +++ b/zimodem/phonebook.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/zimodem/phonebook.ino b/zimodem/phonebook.ino index 57c22ef..b0e7b69 100644 --- a/zimodem/phonebook.ino +++ b/zimodem/phonebook.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,14 +25,28 @@ PhoneBookEntry::PhoneBookEntry(unsigned long phnum, const char *addr, const char strcpy((char *)notes,note); if(phonebook == null) + { phonebook = this; - else + return; + } + PhoneBookEntry *last = phonebook; + if(last->number > number) { - PhoneBookEntry *last = phonebook; - while(last->next != null) - last = last->next; - last->next = this; + 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() @@ -91,9 +105,10 @@ bool PhoneBookEntry::checkPhonebookEntry(String cmd) bool error = false; for(char *cptr=(char *)vbuf;*cptr!=0;cptr++) { - if(strchr("0123456789",*cptr) < 0) + if(strchr("0123456789",*cptr) == 0) { - error =true; + error = true; + break; } } if(error || (strlen((char *)vbuf)>9)) diff --git a/zimodem/proto_comet64.h b/zimodem/proto_comet64.h new file mode 100644 index 0000000..edc2d77 --- /dev/null +++ b/zimodem/proto_comet64.h @@ -0,0 +1,47 @@ +#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_COMET64 +/* + Copyright 2024-2025 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. +*/ +/* Reverse engineered by Bo Zimmerman */ +#include + +# define COM64_BUFSIZ 204 + +class Comet64 +{ +private: + ZSerial cserial; + uint8_t inbuf[COM64_BUFSIZ+1]; + int idex = 0; + + bool aborted = false; + SDFS *cFS = &SD; + bool browsePetscii = false; + + bool checkPlusPlusPlusExpire(); + void printFilename(const char* name); + void printDiskHeader(const char* name); + void printPetscii(const char* name); + String getNormalExistingFilename(String name); + +public: + void receiveLoop(); + bool isAborted(); + Comet64(SDFS *fs, FlowControlType fcType); + ~Comet64(); +}; +#endif +#endif diff --git a/zimodem/proto_comet64.ino b/zimodem/proto_comet64.ino new file mode 100644 index 0000000..a1c7281 --- /dev/null +++ b/zimodem/proto_comet64.ino @@ -0,0 +1,439 @@ +#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_COMET64 +/* + Copyright 2024-2025 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. +*/ +/* Reverse engineered by Bo Zimmerman */ +Comet64::Comet64(SDFS *fs, FlowControlType fcType) +{ + cFS = fs; + cserial.setPetsciiMode(false); + if(fcType == FCT_RTSCTS) + cserial.setFlowControlType(FCT_RTSCTS); + else + cserial.setFlowControlType(FCT_DISABLED); + browseMode.init(); + browsePetscii = browseMode.serial.isPetsciiMode(); + browseMode.serial.setPetsciiMode(true); +} + +Comet64::~Comet64() +{ + browseMode.serial.setPetsciiMode(browsePetscii); + //closeAllFiles(); +} + +bool Comet64::checkPlusPlusPlusExpire() +{ + if(aborted) + return true; + if(checkPlusPlusPlusEscape()) + { + aborted = true; + return true; + } + return false; +} + +bool Comet64::isAborted() +{ + return aborted; +} + +void Comet64::printDiskHeader(const char* name) +{ + int i=0; + cserial.print("\""); + for(;i<16 && name[i] != 0;i++) + cserial.printf("%c",ascToPetcii(name[i])); + for(;i<16;i++) + cserial.print(" "); + cserial.print("\""); +} + +void Comet64::printFilename(const char* name) +{ + int i=0; + cserial.print("\""); + for(;i<16 && name[i] != 0;i++) + cserial.printf("%c",ascToPetcii(name[i])); + cserial.print("\""); + for(;i<16;i++) + cserial.print(" "); +} + +String Comet64::getNormalExistingFilename(String name) +{ + File f = cFS->open(name,FILE_READ); + if(f) + { + bool isDir = f.isDirectory(); + f.close(); + if(!isDir) + return name; + } + if((!name.endsWith(".prg")) + &&(!name.endsWith(".PRG")) + &&(!name.endsWith(".seq")) + &&(!name.endsWith(".SEQ"))) + { + String chk; + chk = getNormalExistingFilename(name + ".prg"); + if(chk.length() > 0) + return chk; + chk = getNormalExistingFilename(name + ".PRG"); + if(chk.length() > 0) + return chk; + chk = getNormalExistingFilename(name + ".seq"); + if(chk.length() > 0) + return chk; + chk = getNormalExistingFilename(name + ".SEQ"); + if(chk.length() > 0) + return chk; + } + return ""; +} + +void Comet64::printPetscii(const char* name) +{ + int i=0; + for(int i=0;name[i] != 0;i++) + cserial.printf("%c",ascToPetcii(name[i])); +} + +void Comet64::receiveLoop() +{ + serialOutDeque(); + if(checkPlusPlusPlusExpire()) + return; + int c; + while(cserial.available() > 0) + { + c=cserial.read(); + if(idex0)||(c!=' ')) + inbuf[idex++]=c; + } + processPlusPlusPlus(c); + yield(); + if(c==13) + { + inbuf[idex-1]=c; // we do this to signal the rest of the method + break; + } + } + + if((idex==0) + ||(inbuf[idex-1]!=13)) + { + serialOutDeque(); + return; + } + inbuf[--idex]=0; + while((idex>0)&&(inbuf[idex-1]==' ')) + idex--; + if(idex==0) + { + serialOutDeque(); + return; + } + + //since de-petsciify breaks save, and we needs lb,hb in the args. :( + char *sp = strchr((char *)inbuf,' '); + char *args = (sp+1); + char *cmd = (char *)inbuf; + if(sp == 0) + args=(char *)(inbuf-1); + else + *sp=0; + uint8_t lbhb[2]; + char *lbhbc = strchr((char *)args,','); + if(lbhbc > args) + { + lbhb[0] = lbhbc[1]; + lbhb[1] = lbhbc[2]; + } + + + // ok, for most commands, petscii translation helps... + for(int i=0;i0) + { + File f=SD.open(p, FILE_READ); + unsigned char hb = round(floor(f.size()/256)); + unsigned char lb = (f.size()-(hb*256)); + cserial.write(lb); + cserial.write(hb); + for(int i=0;i0) + { + int c = cserial.read(); + f.write(c); + last=millis(); + i++; + } + else + if((millis()-last)>3000) + { + fail=true; + break; + } + } + f.close(); + if(fail) + { + cFS->remove(p); + printPetscii("?25 - read error\r\n"); + } + else + printPetscii("00 - ok\r\n"); + } + else + printPetscii("?63 - file exists\r\n"); + } + else + printPetscii("?500 - internal error\r\n"); + cserial.write(4); + } + else + if((strcmp(cmd,"$")==0) + ||(strcmp(cmd,"FIND")==0)) + { + // header line + bool find = (strcmp(cmd,"FIND")==0); + if(find) + { + char *comma = strchr(args,','); + if(comma != 0) + *comma = 0; + } + cserial.printf("0 %c",18); // rvs on + char *lastSlash = strrchr(browseMode.path.c_str(),'/'); + if(lastSlash == 0) + lastSlash = (char *)browseMode.path.c_str(); + else + lastSlash++; + printDiskHeader((const char *)lastSlash); // prints the dir name in petscii + cserial.printf(" 2%c%c\r\n",193,146); //2A[rvs off] + File root = cFS->open(browseMode.path.c_str()); + if(root && root.isDirectory()) + { + File file = root.openNextFile(); + while(file) + { + if((!find)||(browseMode.matches(file.name(),args))) + { + String fname = file.name(); + String ext = " prg"; + String blks; + if(file.isDirectory()) + { + ext = " dir"; + blks="0 "; + } + else + { + if(fname.endsWith(".prg")||fname.endsWith(".PRG")) + fname = fname.substring(0,fname.length()-4); + else + if(fname.endsWith(".seq")||fname.endsWith(".SEQ")) + { + fname = fname.substring(0,fname.length()-4); + ext = " seq"; + } + int numBlocks = round(ceil(file.size()/254.0)); + blks = numBlocks; + while(blks.length()<4) + blks += " "; + } + cserial.printf(blks.c_str()); + printFilename(fname.c_str()); + printPetscii(ext.c_str()); + cserial.printf("\r\n"); + } + file = root.openNextFile(); + } + } + uint64_t free = cFS->totalBytes() - cFS->usedBytes(); + uint64_t adjFree= round(ceil(free/254.0)); + int blksFree = (adjFree < 65535) ? adjFree : 65535; + cserial.printf("%u",blksFree); + printPetscii(" blocks free.\r\n"); + cserial.write(4); + } + else + { + printPetscii("?500 - unknown command\r\n"); + cserial.write(4); + } + idex=0; // we are ready for the next packet! + serialOutDeque(); +} +#endif +#endif diff --git a/zimodem/proto_ftp.h b/zimodem/proto_ftp.h index 3ac14f9..3bb601e 100644 --- a/zimodem/proto_ftp.h +++ b/zimodem/proto_ftp.h @@ -1,8 +1,8 @@ -#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_FTP #ifndef ZIMODEM_PROTO_FTP #define ZIMODEM_PROTO_FTP /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,8 +17,18 @@ limitations under the License. */ +class FTPClientPair +{ +public: + WiFiClient *cmdClient = 0; + WiFiClient *dataClient = 0; + ~FTPClientPair(); +}; + class FTPHost { +private: + FTPClientPair *streams = 0; public: char *hostIp = 0; int port = 0; @@ -32,6 +42,7 @@ class FTPHost bool doPut(File &f, const char *remotepath); bool doLS(ZSerial *serial, const char *remotepath); bool parseUrl(uint8_t *vbuf, char **req); + WiFiClient *doGetStream(const char *remotepath, uint32_t *responseSize); void fixPath(const char *remotepath); }; @@ -40,5 +51,6 @@ bool parseFTPUrl(uint8_t *vbuf, char **hostIp, char **req, int *port, bool *doSS bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const char *remotepath, const char *username, const char *pw, const bool doSSL); bool doFTPPut(File &f, const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, const bool doSSL); bool doFTPLS(ZSerial *serial, const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, const bool doSSL); +FTPClientPair *doFTPGetStream(const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, bool doSSL, uint32_t *responseSize); #endif #endif diff --git a/zimodem/proto_ftp.ino b/zimodem/proto_ftp.ino index e1ac741..b5762c1 100644 --- a/zimodem/proto_ftp.ino +++ b/zimodem/proto_ftp.ino @@ -1,5 +1,5 @@ /* - Copyright 2018-2019 Bo Zimmerman + Copyright 2018-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#ifdef INCLUDE_SD_SHELL +#ifdef INCLUDE_FTP FTPHost::~FTPHost() { @@ -22,6 +22,11 @@ FTPHost::~FTPHost() freeCharArray(&pw); freeCharArray(&path); freeCharArray(&file); + if(streams != 0) + { + delete streams; + streams=0; + } } bool FTPHost::doGet(FS *fs, const char *localpath, const char *remotepath) @@ -67,7 +72,7 @@ void FTPHost::fixPath(const char *remotepath) if(remotepath[0] == '/') { strcpy(fbuf, remotepath); - if(end > 0) + if(end != 0) { *end = 0; sprintf(pbuf,"%s/",remotepath); @@ -78,7 +83,7 @@ void FTPHost::fixPath(const char *remotepath) else { sprintf(fbuf,"%s%s",path,remotepath); - if(end > 0) + if(end != 0) { *end = 0; sprintf(pbuf,"%s%s/",path,remotepath); @@ -178,7 +183,7 @@ bool parseFTPUrl(uint8_t *vbuf, char **hostIp, char **req, int *port, bool *doSS *portB = 0; portB++; *port = atoi(portB); - if(port <= 0) + if(*port <= 0) return ZERROR; } return true; @@ -236,8 +241,13 @@ static int getFTPResponseCode(WiFiClient *c, char *buf) if(resp.length()==0) return -1; } - if((buf != NULL)&&(resp.length()<132)) - strcpy(buf,resp.substring(4).c_str()); + if(buf != NULL) + { + int start = 4; + if(resp.length()>=132) + start=4+(resp.length()-132); + strcpy(buf,resp.substring(start).c_str()); + } return atoi(resp.substring(0,3).c_str()); } @@ -255,9 +265,23 @@ void readBytesToSilence(WiFiClient *cc) } } -bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const char *remotepath, const char *username, const char *pw, const bool doSSL) +FTPClientPair::~FTPClientPair() +{ + if(cmdClient != 0) + { + doFTPQuit(&cmdClient); + cmdClient = 0; + } + if(dataClient != 0) + { + dataClient->stop(); + delete dataClient; + dataClient = 0; + } +} + +static bool doFTPLogin(WiFiClient *cc, const char *hostIp, int port, const char *username, const char *pw) { - WiFiClient *cc = createWiFiClient(doSSL); if(WiFi.status() != WL_CONNECTED) return false; if(!cc->connect(hostIp, port)) @@ -279,21 +303,28 @@ bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const if(respCode != 230) return doFTPQuit(&cc); readBytesToSilence(cc); - cc->printf("TYPE I\r\n"); - respCode = getFTPResponseCode(cc, NULL); - if(respCode < 0) - return doFTPQuit(&cc); + return true; +} + +static WiFiClient *doFTPPassive(WiFiClient *cc, bool doSSL) +{ char ipbuf[129]; cc->printf("PASV\r\n"); - respCode = getFTPResponseCode(cc, ipbuf); + int respCode = getFTPResponseCode(cc, ipbuf); if(respCode != 227) - return doFTPQuit(&cc); + { + doFTPQuit(&cc); + return 0; + } // 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); + { + doFTPQuit(&cc); + return 0; + } int digitCount=0; int digits[10]; char *commaPtr=strchr(ipptr+1,','); @@ -307,23 +338,102 @@ bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const commaPtr=strchr(ipptr+1,')'); } if(digitCount < 6) - return doFTPQuit(&cc); + { + doFTPQuit(&cc); + return 0; + } 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); + { + doFTPQuit(&cc); + return 0; + } WiFiClient *c = createWiFiClient(doSSL); if(!c->connect(ipbuf, dataPort)) { doFTPQuit(&c); - return doFTPQuit(&cc); + doFTPQuit(&cc); + return 0; } c->setNoDelay(DEFAULT_NO_DELAY); + return c; +} + +FTPClientPair *doFTPGetStream(const char *hostIp, int port, const char *remotepath, const char *username, const char *pw, bool doSSL, uint32_t *responseSize) +{ + *responseSize = 0; + WiFiClient *cc = createWiFiClient(doSSL); + if(!doFTPLogin(cc, hostIp, port, username, pw)) + return 0; + cc->printf("TYPE I\r\n"); + int respCode = getFTPResponseCode(cc, NULL); + if(respCode < 0) + { + doFTPQuit(&cc); + return 0; + } + WiFiClient *c = doFTPPassive(cc, doSSL); + if(c == 0) + return 0; + cc->printf("RETR %s\r\n",remotepath); + char lbuf[133]; + respCode = getFTPResponseCode(cc, lbuf); + if((respCode < 0)||(respCode > 400)) + { + c->stop(); + delete c; + doFTPQuit(&cc); + return 0; + } + if(respCode == 150) + { + char *eob=strstr(lbuf," bytes"); + if(eob) + { + *eob=0; + char *sob=eob-1; + while(strchr("0123456789",*sob) && (sob > lbuf)) + sob--; + if((sob<(eob-1))&&(sob>lbuf)) + *responseSize=atoi(sob+1); + } + } + FTPClientPair *pair = (FTPClientPair *)malloc(sizeof(FTPClientPair)); + pair->cmdClient = cc; + pair->dataClient = c; + return pair; +} + +WiFiClient *FTPHost::doGetStream(const char *remotepath, uint32_t *responseSize) +{ + streams = doFTPGetStream(hostIp,port,remotepath,username,pw,doSSL,responseSize); + if(streams != 0) + return streams->dataClient; + return 0; +} + +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(!doFTPLogin(cc, hostIp, port, username, pw)) + return false; + cc->printf("TYPE I\r\n"); + int respCode = getFTPResponseCode(cc, NULL); + if(respCode < 0) + return doFTPQuit(&cc); + WiFiClient *c = doFTPPassive(cc, doSSL); + if(c == 0) + return false; cc->printf("RETR %s\r\n",remotepath); respCode = getFTPResponseCode(cc, NULL); if((respCode < 0)||(respCode > 400)) + { + c->stop(); + delete c; return doFTPQuit(&cc); + } File f = fs->open(localpath, "w"); unsigned long now=millis(); while((c->connected()||(c->available()>0)) @@ -350,77 +460,24 @@ bool doFTPGet(FS *fs, const char *hostIp, int port, const char *localpath, const 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) + if(!doFTPLogin(cc, hostIp, port, username, pw)) 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); + int 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); + WiFiClient *c = doFTPPassive(cc, doSSL); + if(c == 0) + return false; cc->printf("STOR %s\r\n",remotepath); respCode = getFTPResponseCode(cc, NULL); if((respCode < 0)||(respCode > 400)) + { + c->stop(); + delete c; 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 { @@ -434,7 +491,6 @@ bool doFTPPut(File &f, const char *hostIp, int port, const char *remotepath, con else yield(); } - debugPrintf("FPUT: Done\r\n"); c->flush(); c->stop(); delete c; @@ -445,29 +501,10 @@ bool doFTPPut(File &f, const char *hostIp, int port, const char *remotepath, con 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) + if(!doFTPLogin(cc, hostIp, port, username, pw)) 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); + int respCode = getFTPResponseCode(cc, NULL); if(respCode < 0) return doFTPQuit(&cc); if((remotepath != NULL)&& (*remotepath != NULL)) @@ -478,47 +515,17 @@ bool doFTPLS(ZSerial *serial, const char *hostIp, int port, const char *remotepa 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); + WiFiClient *c = doFTPPassive(cc, doSSL); + if(c == 0) + return false; cc->printf("LIST\r\n",remotepath); respCode = getFTPResponseCode(cc, NULL); if((respCode < 0)||(respCode > 400)) + { + c->stop(); + delete c; return doFTPQuit(&cc); + } unsigned long now=millis(); while((c->connected()||(c->available()>0)) && ((millis()-now) < 30000)) // loop for data, with nice long timeout @@ -538,4 +545,4 @@ bool doFTPLS(ZSerial *serial, const char *hostIp, int port, const char *remotepa doFTPQuit(&cc); return true; } -#endif \ No newline at end of file +#endif diff --git a/zimodem/proto_hostcm.h b/zimodem/proto_hostcm.h index 3ace46c..2041fa0 100644 --- a/zimodem/proto_hostcm.h +++ b/zimodem/proto_hostcm.h @@ -42,17 +42,13 @@ class HostCM 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); + bool checkPlusPlusPlusExpire(); void sendNAK(); void sendACK(); void sendError(const char* format, ...); diff --git a/zimodem/proto_hostcm.ino b/zimodem/proto_hostcm.ino index 8935c3c..e3d92e5 100644 --- a/zimodem/proto_hostcm.ino +++ b/zimodem/proto_hostcm.ino @@ -101,35 +101,13 @@ int HostCM::numOpenFiles() return n; } -void HostCM::checkDoPlusPlusPlus(const int c, const unsigned long tm) -{ - if(c == '+') - { - if((plussesInARow>0)||((tm-lastNonPlusTm)>800)) - { - plussesInARow++; - if(plussesInARow > 2) - plusTimeExpire = tm + 800; - } - } - else - { - plusTimeExpire = 0; - lastNonPlusTm = tm; - plussesInARow = 0; - } -} - -bool HostCM::checkPlusPlusPlusExpire(const unsigned long tm) +bool HostCM::checkPlusPlusPlusExpire() { if(aborted) return true; - if((plusTimeExpire>0)&&(tm>plusTimeExpire)&&(plussesInARow>2)) + if(checkPlusPlusPlusEscape()) { aborted = true; - plusTimeExpire = 0; - lastNonPlusTm = tm; - plussesInARow = 0; return true; } return false; @@ -626,8 +604,7 @@ void HostCM::sendACK() void HostCM::receiveLoop() { serialOutDeque(); - unsigned long tm = millis(); - if(checkPlusPlusPlusExpire(tm)) + if(checkPlusPlusPlusExpire()) return; int c; while(hserial.available() > 0) @@ -635,9 +612,7 @@ void HostCM::receiveLoop() c=hserial.read(); if(idexstop(); - delete wifi; - } + wifi->stop(); + delete wifi; } + } - virtual int read() + virtual int read() + { + if(available()==0) + return -1; + int returnC = -1; + char c=wifi->read(); + bool gotC = false; + int errors = 0; + while((!gotC) && (errors < 5000)) { - if(available()==0) - return -1; - char c=wifi->read(); - bool gotC = false; - int errors = 0; - while((!gotC) && (errors < 5000)) + switch(state) { - 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: + case 0: + if(c=='0') + { + eof=true; + return -1; + } + if((c>='0')&&(c<='9')) + { + chunkSize = (c - '0'); + state=1; + } + else + if((c>='a')&&(c<='f')) { - 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; + chunkSize = 10 + (c-'a'); + state=1; } - 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; + 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; } - while((!gotC) && (errors < 5000)) + case 2: + if(c == '\n') + { + state = 3; + errors = 0; + chunkCount=0; + } + else + state = 0; + break; + case 3: + if(chunkCount < chunkSize) + { + gotC = true; + chunkCount++; + returnC = c; + } + 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) { - if(available()>0) - { - c=wifi->read(); - break; - } - else - if(++errors > 5000) - break; - else - delay(1); + c=wifi->read(); + break; } + else + if(++errors > 5000) + break; + else + delay(1); } - return c; - } - virtual int peek() - { - return wifi->peek(); } + return returnC; + } + 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;i size) + num=size; + for(int i=0;igetNoDelay(); - } - void setNoDelay(bool nodelay) + bool getNoDelay() + { + return wifi->getNoDelay(); + } + 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() || (!eof); + } +}; + +class FileWiFiStream : public WiFiClient +{ +private: + File f; + uint8_t state = 0; //0 + + void closeFile() + { + if(state == 0) { - wifi->setNoDelay(nodelay); + String name = "/"; + name += f.name(); + f.close(); + SD.remove(name.c_str()); + state=1; } + } + +public: + + FileWiFiStream(const char *file) + { + f = SD.open(file,FILE_READ); + } + + ~FileWiFiStream() + { + closeFile(); + } + + virtual int read() + { + return f.read(); + } + + virtual int peek() + { + return f.peek(); + } + + virtual int read(uint8_t *buf, size_t size) + { + return f.read(buf,size); + } - virtual int available() + bool getNoDelay() + { + return true; + } + void setNoDelay(bool nodelay) + { + } + + virtual int available() + { + return f.available(); + } + + virtual void stop() + { + closeFile(); + } + virtual uint8_t connected() + { + return (state == 0); + } +}; +#endif + +WiFiClient *doGopherGetStream(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 = 70; + if(!c->connect(hostIp, port)) + { + c->stop(); + delete c; + return null; + } + c->setNoDelay(DEFAULT_NO_DELAY); + const char *root = ""; + if(req == NULL) + req=root; + c->printf("%s\r\n",req); + c->flush(); + *responseSize = 0; +# ifdef INCLUDE_SD_SHELL + if(SD.cardType() != CARD_NONE) + { + char tempWebName[20]; + sprintf(tempWebName,"/.tmp_web_%u",random(9999)); + if(SD.exists(tempWebName)) + SD.remove(tempWebName); + File tempF = SD.open(tempWebName,FILE_WRITE); + if(!tempF) { - return wifi->available(); + *responseSize = 0; + return c; } - - virtual void stop() + size_t written = 0; + int b = c->read(); + int errors = 0; + while((b < 0)&&(++errors<2000)) { - wifi->stop(); + delay(1); + b = c->read(); } - virtual uint8_t connected() + while((b >= 0)&&(c->connected()||(c->available()>0))) { - return wifi->connected(); + written++; + tempF.write(b); + b = c->read(); + while((b < 0)&&(++errors<2000)&&(c->connected()||(c->available()>0))) + { + delay(1); + b = c->read(); + } } -}; - */ + delete c; + tempF.close(); + *responseSize = written; + return new FileWiFiStream(tempWebName); + } +# endif + return c; +} + WiFiClient *doWebGetStream(const char *hostIp, int port, const char *req, bool doSSL, uint32_t *responseSize) { *responseSize = 0; @@ -315,6 +461,11 @@ WiFiClient *doWebGetStream(const char *hostIp, int port, const char *req, bool d } } +# ifdef INCLUDE_SD_SHELL + if((chunked) + &&(SD.cardType() != CARD_NONE)) + respLength = 1; +#endif *responseSize = respLength; if(((!c->connected())&&(c->available()==0)) ||(respCode != 200) @@ -324,21 +475,58 @@ WiFiClient *doWebGetStream(const char *hostIp, int port, const char *req, bool d delete c; return null; } +# ifdef INCLUDE_SD_SHELL + if((chunked) + &&(SD.cardType() != CARD_NONE)) + { + ChunkedStream *ch = new ChunkedStream(c); + char tempWebName[20]; + sprintf(tempWebName,"/.tmp_web_%u",random(9999)); + if(SD.exists(tempWebName)) + SD.remove(tempWebName); + File tempF = SD.open(tempWebName,FILE_WRITE); + if(!tempF) + { + delete ch; + *responseSize = 0; + return c; + } + size_t written = 0; + int b = ch->read(); + int errors = 0; + while((b < 0)&&(++errors<2000)) + { + delay(1); + b = ch->read(); + } + while((b >= 0)&&(ch->connected())) + { + written++; + tempF.write(b); + b = ch->read(); + while((b < 0)&&(++errors<2000)&&(ch->connected())) + { + delay(1); + b = ch->read(); + } + } + delete ch; + tempF.close(); + *responseSize = written; + return new FileWiFiStream(tempWebName); + } +# endif //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) +bool doStreamGet(WiFiClient *c, uint32_t respLength, FS *fs, const char *filename) { - 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 + while(((bytesRead < respLength) || (respLength==0)) && (c->connected()||(c->available()>0)) && ((millis()-now)<10000)) { @@ -360,13 +548,27 @@ bool doWebGet(const char *hostIp, int port, FS *fs, const char *filename, const return (respLength == 0) || (bytesRead == respLength); } -bool doWebGetBytes(const char *hostIp, int port, const char *req, const bool doSSL, uint8_t *buf, int *bufSize) +bool doWebGet(const char *hostIp, int port, FS *fs, const char *filename, const char *req, const bool doSSL) { - *bufSize = -1; - uint32_t respLength=0; + uint32_t respLength = 0; WiFiClient *c = doWebGetStream(hostIp, port, req, doSSL, &respLength); - if(c==null) + if(c == NULL) return false; + return doStreamGet(c,respLength,fs,filename); +} + +bool doGopherGet(const char *hostIp, int port, FS *fs, const char *filename, const char *req, const bool doSSL) +{ + uint32_t respLength = 0; + WiFiClient *c = doGopherGetStream(hostIp, port, req, doSSL, &respLength); + if(c == NULL) + return false; + return doStreamGet(c,respLength,fs,filename); +} + +bool doStreamGetBytes(WiFiClient *c, uint32_t respLength, uint8_t *buf, int *bufSize) +{ + *bufSize = -1; if(((!c->connected())&&(c->available()==0)) ||(respLength > *bufSize)) { @@ -396,3 +598,23 @@ bool doWebGetBytes(const char *hostIp, int port, const char *req, const bool doS delete c; return (respLength == 0) || (index == 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; + return doStreamGetBytes(c,respLength,buf,bufSize); +} + +bool doGopherGetBytes(const char *hostIp, int port, const char *req, const bool doSSL, uint8_t *buf, int *bufSize) +{ + *bufSize = -1; + uint32_t respLength=0; + WiFiClient *c = doGopherGetStream(hostIp, port, req, doSSL, &respLength); + if(c==null) + return false; + return doStreamGetBytes(c,respLength,buf,bufSize); +} diff --git a/zimodem/proto_kermit.h b/zimodem/proto_kermit.h index 6dd2bab..822aa42 100644 --- a/zimodem/proto_kermit.h +++ b/zimodem/proto_kermit.h @@ -96,7 +96,6 @@ static int kReceiveSerial(ZSerial *ser, int del) if(ser->available() > 0) { int c=ser->read(); - logSerialIn(c); return c; } yield(); @@ -134,7 +133,7 @@ static bool kDDataHandler(File *kfp, unsigned long number, char *buf, int sz) return true; } -static boolean kDownload(FlowControlType commandFlow, FS &fs, String **fileList, int fileCount, String &errors) +static bool kDownload(FlowControlType commandFlow, FS &fs, String **fileList, int fileCount, String &errors) { KModem kmo(commandFlow, kReceiveSerial, kSendSerial, kDDataHandler, errors); kmo.kfileSystem = &fs; @@ -143,7 +142,7 @@ static boolean kDownload(FlowControlType commandFlow, FS &fs, String **fileList, return result; } -static boolean kUpload(FlowControlType commandFlow, FS &fs, String rootPath, String &errors) +static bool kUpload(FlowControlType commandFlow, FS &fs, String rootPath, String &errors) { KModem kmo(commandFlow, kReceiveSerial, kSendSerial, kUDataHandler, errors); kmo.kfileSystem = &fs; diff --git a/zimodem/proto_kermit.ino b/zimodem/proto_kermit.ino index fb010b6..4f26171 100644 --- a/zimodem/proto_kermit.ino +++ b/zimodem/proto_kermit.ino @@ -44,7 +44,7 @@ bool KModem::receive() while(TRUE) { if (debug) - debugPrintf(" recsw state: %c\n",state); + debugPrintf(" recsw state: %c\r\n",state); switch(state) /* Do until done */ { case 'R': @@ -80,7 +80,7 @@ bool KModem::transmit() while(ZTRUE) /* Do this as long as necessary */ { if (debug) - debugPrintf("sendsw state: %c\n",state); + debugPrintf("sendsw state: %c\r\n",state); switch(state) { case 'S': @@ -178,7 +178,7 @@ char KModem::sfile() if (kfpClosed) /* If not already open, */ { if (debug) - debugPrintf(" Opening %s for sending.\n",filnam); + debugPrintf(" Opening %s for sending.\r\n",filnam); kfp = kfileSystem->open(filnam,"r"); /* open the file to be sent */ if (!kfp) /* If bad file pointer, give up */ { @@ -291,11 +291,11 @@ char KModem::seof() kfp.close(); /* Close the input file */ kfpClosed = true; /* Set flag indicating no file open */ if (debug) - debugPrintf("looking for next file...\n"); + debugPrintf("looking for next file...\r\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); + debugPrintf(" New file is %s\r\n",filnam); return('F'); /* More files, switch state to F */ case 'E': /* Error packet received */ prerrpkt(recpkt); /* Print it out and */ @@ -358,7 +358,7 @@ char KModem::rinit() char rs=rpack(&len,&num,packet); if (debug) - debugPrintf(" recsw-rinit state: %c\n",rs); + debugPrintf(" recsw-rinit state: %c\r\n",rs); switch(rs) /* Get a packet */ { case 'S': /* Send-Init */ @@ -395,7 +395,7 @@ char KModem::rfile() char rs = rpack(&len,&num,packet); if (debug) - debugPrintf(" recsw-rfile state: %c\n",rs); + debugPrintf(" recsw-rfile state: %c\r\n",rs); switch(rs) /* Get a packet */ { case 'S': /* Send-Init, maybe our ACK lost */ @@ -448,7 +448,7 @@ char KModem::rfile() } else /* OK, give message */ { - debugPrintf("Receiving %s as %s\n",packet,filnam1); + debugPrintf("Receiving %s as %s\r\n",packet,filnam1); kfpClosed=false; } } @@ -488,7 +488,7 @@ char KModem::rdata() char rs=rpack(&len,&num,packet); if (debug) - debugPrintf(" recsw-rdata state: %c\n",rs); + debugPrintf(" recsw-rdata state: %c\r\n",rs); switch(rs) /* Get packet */ { case 'D': /* Got Data packet */ @@ -558,11 +558,11 @@ int KModem::spack(char type, int num, int len, char *data) { 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); + debugPrintf("\n spack type: %c\r\n",type); + debugPrintf(" num: %d\r\n",num); + debugPrintf(" len: %d\r\n",len); if (data != NULL) - debugPrintf(" data: \"%s\"\n",data); + debugPrintf(" data: \"%s\"\r\n",data); } bufp = buffer; /* Set up buffer pointer */ @@ -674,11 +674,11 @@ int KModem::rpack(int *len, int *num, char *data) { 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); + debugPrintf("\n rpack type: %c\r\n",type); + debugPrintf(" num: %d\r\n",*num); + debugPrintf(" len: %d\r\n",*len); if (data != NULL) - debugPrintf(" data: \"%s\"\n",data); + debugPrintf(" data: \"%s\"\r\n",data); } /* Fold in bits 7,8 to compute */ cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */ @@ -760,7 +760,7 @@ int KModem::gnxtfl() filnam = (char *)filelist[filenum++]->c_str(); filnamo = filnam; if (debug) - debugPrintf(" gnxtfl: filelist = \"%s\"\n",filnam); + debugPrintf(" gnxtfl: filelist = \"%s\"\r\n",filnam); return TRUE; /* else succeed */ } @@ -809,9 +809,9 @@ void KModem::prerrpkt(char *msg) if(errStr != 0) { (*errStr)+=msg; - debugPrintf("kermit: %s\n",msg); + debugPrintf("kermit: %s\r\n",msg); } } -#endif \ No newline at end of file +#endif diff --git a/zimodem/proto_ping.h b/zimodem/proto_ping.h new file mode 100644 index 0000000..186d438 --- /dev/null +++ b/zimodem/proto_ping.h @@ -0,0 +1,21 @@ +#ifndef ZIMODEM_PROTO_PING +#define ZIMODEM_PROTO_PING +#ifdef INCLUDE_PING +/* + Copyright 2023-2025 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/zimodem/proto_ping.ino b/zimodem/proto_ping.ino new file mode 100644 index 0000000..253bae9 --- /dev/null +++ b/zimodem/proto_ping.ino @@ -0,0 +1,137 @@ +#ifdef INCLUDE_PING +/* + Copyright 2023-2025 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" + + +#ifndef ZIMODEM_ESP32 +# include "lwip/raw.h" + static uint8_t ping_recv(void *pingtm, struct raw_pcb *pcb, pbuf *packet, const ip_addr_t *addr) + { + unsigned long *tm = (unsigned long *)pingtm; + *tm = millis(); + return false; + } +#endif + +static int ping(char *host) +{ + IPAddress hostIp((uint32_t)0); +#ifdef ZIMODEM_ESP32 + 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; +#else + if(!WiFi.hostByName(host, hostIp)) + return -1; + int icmp_len = sizeof(struct icmp_echo_hdr); + struct pbuf * packet = pbuf_alloc(PBUF_IP, 32 + icmp_len, PBUF_RAM); + if(packet == nullptr) + return 1; + struct icmp_echo_hdr * pingRequest = (struct icmp_echo_hdr *)packet->payload; + ICMPH_TYPE_SET(pingRequest, ICMP_ECHO); + ICMPH_CODE_SET(pingRequest, 0); + pingRequest->chksum = 0; + pingRequest->id = 0x0100; + pingRequest->seqno = htons(0); + char dataByte = 'a'; + for(size_t i=0; i<32; i++) + { + ((char*)pingRequest)[icmp_len + i] = dataByte; + if(++dataByte > 'w') + dataByte = 'a'; + } + pingRequest->chksum = inet_chksum(pingRequest,32+icmp_len); + ip_addr_t dest_addr; + dest_addr.addr = hostIp; + struct raw_pcb *ping_pcb = raw_new(IP_PROTO_ICMP); + + unsigned long startTm = millis(); + unsigned long tm = 0; + raw_recv(ping_pcb, ping_recv, (void *)&tm); + raw_bind(ping_pcb, IP_ADDR_ANY); + + raw_sendto(ping_pcb, packet, &dest_addr); + while((millis()-startTm < 1500) && (tm == 0)) + delay(1); + pbuf_free(packet); + raw_remove(ping_pcb); + return (tm > 0)? (int)(millis()-tm) : -1; +#endif +} +#endif diff --git a/zimodem/proto_punter.h b/zimodem/proto_punter.h new file mode 100644 index 0000000..1c79f80 --- /dev/null +++ b/zimodem/proto_punter.h @@ -0,0 +1,134 @@ +/* + Copyright 2024-2025 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 + +typedef int (*RecvChar)(ZSerial *ser, int); +typedef void (*SendChar)(ZSerial *ser, char); +typedef bool (*DataHandler)(File *pfile, unsigned long, char*, int); + +enum PunterCode +{ + PUNTER_GOO, + PUNTER_BAD, + PUNTER_ACK, + PUNTER_SYN, + PUNTER_SNB, + PUNTER_TIMEOUT +}; + +static int PUNTER_HDR_SIZE = 7; +static int PUNTER_HDR_CHK1LB = 0; +static int PUNTER_HDR_CHK1HB = 1; +static int PUNTER_HDR_CHK2LB = 2; +static int PUNTER_HDR_CHK2HB = 3; +static int PUNTER_HDR_NBLKSZ = 4; +static int PUNTER_HDR_BLKNLB = 5; +static int PUNTER_HDR_BLKNHB = 6; + +class Punter +{ + private: + ZSerial pserial; + uint8_t buf[300]; + int bufSize = 0; + + int (*recvChar)(ZSerial *ser, int); + void (*sendChar)(ZSerial *ser, char); + bool (*dataHandler)(File *pfile, unsigned long number, char *buffer, int len); + + int serialRead(int delay); + void serialWrite(char symbol); + + PunterCode readPunterCode(int waitTime); + bool readPunterCode(PunterCode code, int waitTime); + void sendPunterCode(PunterCode code); + bool exchangePunterCodes(PunterCode sendCode, PunterCode expectCode, int tries); + + void calcBufChecksums(uint16_t* chks); + bool sendBufBlock(unsigned int blkNum, unsigned int nextBlockSize, int tries); + bool receiveBlock(int size, int tries); + + protected: + FS *fileSystem = null; + File *pfile = null; + + public: + static const int receiveFrameDelay=4000; + static const int receiveByteDelay=2000; + static const int retryLimit = 10; + + + Punter(File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler); + bool receive(); + bool transmit(); + bool receiveBlock(int size); +}; + +static int pReceiveSerial(ZSerial *ser, int del) +{ + unsigned long end=micros() + (del * 1000L); + while(micros() < end) + { + serialOutDeque(); + if(ser->available() > 0) + { + int c=ser->read(); + return c; + } + yield(); + } + return -1; +} + +static void pSendSerial(ZSerial *ser, char c) +{ + ser->write((uint8_t)c); + ser->flush(); +} + +static bool pUDataHandler(File *pfile, unsigned long number, char *buf, int sz) +{ + for(int i=0;iwrite((uint8_t)buf[i]); + return true; +} + +static bool pDDataHandler(File *pfile, unsigned long number, char *buf, int sz) +{ + for(int i=0;iread(); + if(c<0) + return false; + else + buf[i] = (uint8_t)c; + } + return true; +} + +static bool pDownload(FlowControlType commandFlow, File &f, String &errors) +{ + Punter c1(f, commandFlow, pReceiveSerial, pSendSerial, pDDataHandler); + bool result = c1.transmit(); + return result; +} + +static bool pUpload(FlowControlType commandFlow, File &f, String &errors) +{ + Punter c1(f, commandFlow, pReceiveSerial, pSendSerial, pUDataHandler); + bool result = c1.receive(); + return result; +} diff --git a/zimodem/proto_punter.ino b/zimodem/proto_punter.ino new file mode 100644 index 0000000..b35d3f7 --- /dev/null +++ b/zimodem/proto_punter.ino @@ -0,0 +1,365 @@ +/* + Copyright 2024-2025 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 + +Punter::Punter(File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler) +{ + this->pfile = &f; + this->sendChar = sendChar; + this->recvChar = recvChar; + this->dataHandler = dataHandler; + this->pserial.setFlowControlType(FCT_DISABLED); + if(commandFlow==FCT_RTSCTS) + this->pserial.setFlowControlType(FCT_RTSCTS); + this->pserial.setPetsciiMode(false); + this->pserial.setXON(true); +} + +PunterCode Punter::readPunterCode(int waitTime) +{ + unsigned long start = millis(); + char nxt = '\0'; + int num = 0; + int shortWait = Punter::receiveByteDelay; + if(waitTime >= Punter::receiveFrameDelay) + shortWait = waitTime; + while((millis()-start)= 2) + return PUNTER_GOO; + break; + case 'A': nxt = 'D'; break; + case 'C': nxt = 'K'; break; + case 'D': return PUNTER_BAD; + case 'K': return PUNTER_ACK; + case 'N': return PUNTER_SYN; + case 'B': return PUNTER_SNB; + default: + num = -1; // nope, skip it + break; + } + num++; + } + } + return PUNTER_TIMEOUT; +} + +bool Punter::readPunterCode(PunterCode code, int waitTime) +{ + unsigned long start = millis(); + while((millis()-start)<(waitTime*5)) + { + yield(); + PunterCode c = this->readPunterCode(waitTime); + if(c == PUNTER_TIMEOUT) + return false; + if(c == code) + return true; + } + return false; +} + +int Punter::serialRead(int delay) +{ + return this->recvChar(&pserial,delay); +} + +void Punter::serialWrite(char symbol) +{ + this->pserial.write(symbol); +} + +void Punter::sendPunterCode(PunterCode code) +{ + char *str; + switch(code) + { + case PUNTER_GOO: str = "GOO"; break; + case PUNTER_BAD: str = "BAD"; break; + case PUNTER_ACK: str = "ACK"; break; + case PUNTER_SYN: str = "SYN"; break; + case PUNTER_SNB: str = "S/B"; break; + default: return; + } + for(int i=0;i<3;i++) + serialWrite(str[i]); + pserial.flush(); +} + +void Punter::calcBufChecksums(uint16_t* chks) +{ + chks[0] = 0; + chks[1] = 0; + for(int i=PUNTER_HDR_NBLKSZ;ibufSize;i++) + { + chks[0] += buf[i]; + chks[1] ^= buf[i]; + chks[1] = (chks[1] << 1) | (chks[1] >> 15); + } +} + +bool Punter::transmit() +{ + bool success = exchangePunterCodes(PUNTER_GOO, PUNTER_GOO, Punter::retryLimit*3); + if(!success) // abort! + return false; + this->sendPunterCode(PUNTER_ACK); + if(!this->readPunterCode(PUNTER_SNB, Punter::receiveFrameDelay * 2)) + return false; + + // send filetype packet + this->buf[0] = 1; // prg=1, seq=2 + this->bufSize = 1; + if(!this->sendBufBlock(65535, 4, Punter::retryLimit)) + return false; + + this->sendPunterCode(PUNTER_SYN); + if(!this->readPunterCode(PUNTER_SYN, Punter::receiveFrameDelay)) + return false; + this->sendPunterCode(PUNTER_SNB); + delay(1000); + this->sendPunterCode(PUNTER_SNB); + delay(1000); + this->sendPunterCode(PUNTER_SNB); + delay(1000); + if(!this->readPunterCode(PUNTER_GOO, Punter::receiveFrameDelay*3)) + return false; + + this->sendPunterCode(PUNTER_ACK); + if(!this->readPunterCode(PUNTER_SNB, Punter::receiveByteDelay)) + return false; + + unsigned int bytesRemain = (unsigned int)this->pfile->size(); + int nextBlockSize = (bytesRemain > 248) ? 255 : bytesRemain + PUNTER_HDR_SIZE; + this->bufSize = 0; + if(!this->sendBufBlock(0, nextBlockSize, Punter::retryLimit)) + return false; + int blockNum = 1; + while(bytesRemain > 0) + { + this->bufSize = 0; + int bytesToRead = (bytesRemain > 248) ? 248 : bytesRemain; + if(this->dataHandler != NULL) + { + if(!this->dataHandler(this->pfile, blockNum, (char *)this->buf, bytesToRead)) + return false; + this->bufSize += bytesToRead; + } + int sendBlockNo = ((bytesRemain <= 248)?65280:0) + blockNum; + bytesRemain -= bytesToRead; + nextBlockSize = (bytesRemain > 248) ? 255 : bytesRemain + PUNTER_HDR_SIZE; + if(!this->sendBufBlock(sendBlockNo,nextBlockSize,Punter::retryLimit)) + return false; + blockNum++; + } + this->sendPunterCode(PUNTER_SYN); + if(!this->readPunterCode(PUNTER_SYN, Punter::receiveFrameDelay)) + return false; + this->sendPunterCode(PUNTER_SNB); + return true; +} + +bool Punter::sendBufBlock(unsigned int blkNum, unsigned int nextBlockSize, int tries) +{ + // make room for the header, safely. Do Not Use memcpy! + for(int i=this->bufSize-1;i>=0;i--) + this->buf[i+PUNTER_HDR_SIZE] = this->buf[i]; + buf[PUNTER_HDR_NBLKSZ] = (uint8_t)nextBlockSize; + buf[PUNTER_HDR_BLKNLB] = (uint8_t)(blkNum & 0xff); + buf[PUNTER_HDR_BLKNHB] = (uint8_t)((blkNum & 0xffff) >> 8); + this->bufSize += PUNTER_HDR_SIZE; + uint16_t chksums[2]; + this->calcBufChecksums(chksums); + buf[PUNTER_HDR_CHK1LB] = (uint8_t)(chksums[0] & 0xff); + buf[PUNTER_HDR_CHK1HB] = (uint8_t)((chksums[0] & 0xffff) >> 8); + buf[PUNTER_HDR_CHK2LB] = (uint8_t)(chksums[1] & 0xff); + buf[PUNTER_HDR_CHK2HB] = (uint8_t)((chksums[1] & 0xffff) >> 8); + for(int tri=0;tribufSize;i++) + this->serialWrite(this->buf[i]); + delay(1); + PunterCode pc = this->readPunterCode(Punter::receiveFrameDelay * 2); + if(pc == PUNTER_GOO) + { + this->sendPunterCode(PUNTER_ACK); + pc = this->readPunterCode(Punter::receiveByteDelay); + return pc == PUNTER_SNB; + } + if (pc == PUNTER_BAD) + { + this->sendPunterCode(PUNTER_ACK); + this->readPunterCode(Punter::receiveByteDelay); // better be s/b + } + } + return false; +} + +bool Punter::receiveBlock(int size, int tries) +{ + for(int tri = 0; tri < tries; tri++) + { + this->bufSize = 0; + this->sendPunterCode(PUNTER_SNB); + int c = this->serialRead(Punter::receiveFrameDelay); + if(c >= 0) + { + this->buf[this->bufSize++] = c; + while(this->bufSize < size) + { + yield(); + int c = this->serialRead(Punter::receiveByteDelay); + if(c < 0) + break; + this->buf[this->bufSize++] = c; + } + if(this->bufSize == size) + { + uint16_t chksums[2]; + this->calcBufChecksums(chksums); + uint16_t chksum1 = chksums[0]; + uint16_t chksum2 = chksums[1]; + if(((chksum1&0xff) == buf[PUNTER_HDR_CHK1LB]) + &&((chksum1>>8) == buf[PUNTER_HDR_CHK1HB]) + &&((chksum2&0xff) == buf[PUNTER_HDR_CHK2LB]) + &&((chksum2>>8) == buf[PUNTER_HDR_CHK2HB])) + { + if(this->exchangePunterCodes(PUNTER_GOO, PUNTER_ACK, Punter::retryLimit)) + return true; + } + } + } + if(tries < 2) + return false; + this->sendPunterCode(PUNTER_BAD); + } + return false; +} + +bool Punter::exchangePunterCodes(PunterCode sendCode, PunterCode expectCode, int tries) +{ + for(int i=0;isendPunterCode(sendCode); + if(readPunterCode(expectCode,Punter::receiveByteDelay)) + return true; + } + return false; +} + +bool Punter::receive() +{ + // first say hello -- thirty goos, thirty chances; + bool success = exchangePunterCodes(PUNTER_GOO, PUNTER_ACK, Punter::retryLimit*3); + if(!success) // abort! + return false; + + // next do filetype block + success=this->receiveBlock(PUNTER_HDR_SIZE+1, Punter::retryLimit); // 7 regular + 1 payload + if(!success) // abort! + return false; + + this->sendPunterCode(PUNTER_SNB); + if(!this->readPunterCode(PUNTER_SYN, Punter::receiveFrameDelay)) + return false; + this->sendPunterCode(PUNTER_SYN); + if(!this->readPunterCode(PUNTER_SNB, Punter::receiveFrameDelay)) + return false; + if(!this->readPunterCode(PUNTER_SNB, Punter::receiveFrameDelay)) + return false; + //if(!this->readPunterCode(PUNTER_SNB, Punter::receiveFrameDelay)) + // return false; + if(!exchangePunterCodes(PUNTER_GOO, PUNTER_ACK, Punter::retryLimit)) + return false; + + uint8_t blockSize = buf[PUNTER_HDR_NBLKSZ]; // from the filetype! + // next do useless block + success=this->receiveBlock(PUNTER_HDR_SIZE,Punter::retryLimit); // + if(!success) // abort! + return false; + + blockSize = buf[PUNTER_HDR_NBLKSZ]; // from the useless! + while(true) + { + // receive a for-real block + success=this->receiveBlock(blockSize,Punter::retryLimit); + if(!success) + return false; + unsigned long blockNum = buf[PUNTER_HDR_BLKNLB] + (256 * buf[PUNTER_HDR_BLKNHB]); + // write the buffer data to disk + if(this->dataHandler != NULL) + { + char *dataBuf = (char *)(this->buf + PUNTER_HDR_SIZE); + int dataSize = this->bufSize - PUNTER_HDR_SIZE; + if(!this->dataHandler(this->pfile, blockNum, dataBuf, dataSize)) + return false; + } + if(blockNum >= 65280) // are we actually done? Check for a magic number! + { + if(!exchangePunterCodes(PUNTER_SNB,PUNTER_SYN, Punter::retryLimit)) + return false; + if(!exchangePunterCodes(PUNTER_SYN,PUNTER_SNB, Punter::retryLimit)) + return false; + return true; + } + // now get the size of the NEXT block and move along + blockSize = buf[PUNTER_HDR_NBLKSZ]; + } + return false; +} + +#endif diff --git a/zimodem/proto_xmodem.h b/zimodem/proto_xmodem.h index 3529828..40dbd9a 100644 --- a/zimodem/proto_xmodem.h +++ b/zimodem/proto_xmodem.h @@ -1,5 +1,5 @@ /* - Copyright 2018-2019 Bo Zimmerman + Copyright 2018-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,6 +15,10 @@ */ #include +typedef int (*RecvChar)(ZSerial *ser, int); +typedef void (*SendChar)(ZSerial *ser, char); +typedef bool (*DataHandler)(File *xfile, unsigned long, char*, int); + class XModem { typedef enum @@ -24,6 +28,7 @@ class XModem } transfer_t; private: + size_t fileSizeLimit = 10000000; //holds readed byte (due to dataAvail()) int byte; //expected block number @@ -33,51 +38,66 @@ class XModem //retry counter for NACK int retries; //buffer - char buffer[128]; + char buffer[1024]; //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 serialAvail(int delay); + int serialRead(int delay); + void serialWrite(char symbol); bool receiveFrameNo(void); - bool receiveData(void); - bool checkCrc(void); + bool receiveData(int blkSize); + bool checkCrc(int blkSize); bool checkChkSum(void); bool receiveFrames(transfer_t transfer); bool sendNack(void); void init(void); + bool transmitFrame(transfer_t transfer, int blkSize); bool transmitFrames(transfer_t); unsigned char generateChkSum(void); + + protected: + FS *fileSystem = null; + File yfile; + File *xfile = null; + File *dfile = null; + int blockSize = 128; + bool send0block = false; public: static const unsigned char XMO_NACK = 21; static const unsigned char XMO_ACK = 6; + static const unsigned char XMO_CRC = 'C'; static const unsigned char XMO_SOH = 1; + static const unsigned char XMO_STX = 2; static const unsigned char XMO_EOT = 4; static const unsigned char XMO_CAN = 0x18; - static const int receiveDelay=7000; + static const int receiveFrameDelay=7000; + static const int receiveByteDelay=3000; 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)); + size_t fileSize = 0; + + XModem(File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler); bool receive(); bool transmit(); }; +class YModem : public XModem +{ + public: + YModem(FS *fileSystem, File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler); +}; + static int xReceiveSerial(ZSerial *ser, int del) { unsigned long end=micros() + (del * 1000L); @@ -87,7 +107,6 @@ static int xReceiveSerial(ZSerial *ser, int del) if(ser->available() > 0) { int c=ser->read(); - logSerialIn(c); return c; } yield(); @@ -125,16 +144,31 @@ static bool xDDataHandler(File *xfile, unsigned long number, char *buf, int sz) return true; } -static boolean xDownload(FlowControlType commandFlow, File &f, String &errors) +static bool xDownload(FlowControlType commandFlow, File &f, String &errors) { - XModem xmo(f,commandFlow, xReceiveSerial, xSendSerial, xDDataHandler); + XModem xmo(f, commandFlow, xReceiveSerial, xSendSerial, xDDataHandler); bool result = xmo.transmit(); return result; } -static boolean xUpload(FlowControlType commandFlow, File &f, String &errors) +static bool xUpload(FlowControlType commandFlow, File &f, String &errors) { - XModem xmo(f,commandFlow, xReceiveSerial, xSendSerial, xUDataHandler); + XModem xmo(f, commandFlow, xReceiveSerial, xSendSerial, xUDataHandler); bool result = xmo.receive(); return result; } + +static bool yDownload(FS *fileSystem, FlowControlType commandFlow, File &f, String &errors) +{ + YModem ymo(fileSystem, f, commandFlow, xReceiveSerial, xSendSerial, xDDataHandler); + ymo.fileSize = f.size(); // multi-upload will need this + bool result = ymo.transmit(); + return result; +} + +static bool yUpload(FS *fileSystem, FlowControlType commandFlow, File &dirF, String &errors) +{ + YModem ymo(fileSystem, dirF, commandFlow, xReceiveSerial, xSendSerial, xUDataHandler); + bool result = ymo.receive(); + return result; +} diff --git a/zimodem/proto_xmodem.ino b/zimodem/proto_xmodem.ino index 3369ac7..30d51b8 100644 --- a/zimodem/proto_xmodem.ino +++ b/zimodem/proto_xmodem.ino @@ -1,5 +1,5 @@ /* - Copyright 2018-2019 Bo Zimmerman + Copyright 2018-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,11 +15,7 @@ */ #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)) +XModem::XModem(File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler) { this->xfile = &f; this->sendChar = sendChar; @@ -32,7 +28,21 @@ XModem::XModem(File &f, this->xserial.setXON(true); } -bool XModem::dataAvail(int delay) +YModem::YModem(FS *fileSystem, File &f, FlowControlType commandFlow, RecvChar recvChar, SendChar sendChar, DataHandler dataHandler) + : XModem(f,commandFlow,recvChar,sendChar,dataHandler) +{ + this->fileSystem = fileSystem; + this->dfile = 0; + if(f.isDirectory()) + { + this->dfile = &f; + this->xfile = 0; + } + this->blockSize = 1024; + this->send0block = true; +} + +bool XModem::serialAvail(int delay) { if (this->byte != -1) return true; @@ -42,7 +52,7 @@ bool XModem::dataAvail(int delay) return false; } -int XModem::dataRead(int delay) +int XModem::serialRead(int delay) { int b; if(this->byte != -1) @@ -54,15 +64,15 @@ int XModem::dataRead(int delay) return this->recvChar(&xserial,delay); } -void XModem::dataWrite(char symbol) +void XModem::serialWrite(char symbol) { - this->sendChar(&xserial,symbol); + this->xserial.write(symbol); } bool XModem::receiveFrameNo() { - unsigned char num = (unsigned char)this->dataRead(XModem::receiveDelay); - unsigned char invnum = (unsigned char)this-> dataRead(XModem::receiveDelay); + unsigned char num = (unsigned char)this->serialRead(XModem::receiveFrameDelay); + unsigned char invnum = (unsigned char)this-> serialRead(XModem::receiveByteDelay); this->repeatedBlock = false; //check for repeated block if (invnum == (255-num) && num == this->blockNo-1) { @@ -76,10 +86,10 @@ bool XModem::receiveFrameNo() return true; } -bool XModem::receiveData() +bool XModem::receiveData(int blkSize) { - for(int i = 0; i < 128; i++) { - int byte = this->dataRead(XModem::receiveDelay); + for(int i = 0; i < blkSize; i++) { + int byte = this->serialRead(XModem::receiveByteDelay); if(byte != -1) this->buffer[i] = (unsigned char)byte; else @@ -88,13 +98,13 @@ bool XModem::receiveData() return true; } -bool XModem::checkCrc() +bool XModem::checkCrc(int blkSize) { - unsigned short frame_crc = ((unsigned char)this->dataRead(XModem::receiveDelay)) << 8; + unsigned short frame_crc = ((unsigned char)this->serialRead(XModem::receiveByteDelay)) << 8; - frame_crc |= (unsigned char)this->dataRead(XModem::receiveDelay); + frame_crc |= (unsigned char)this->serialRead(XModem::receiveByteDelay); //now calculate crc on data - unsigned short crc = this->crc16_ccitt(this->buffer, 128); + unsigned short crc = this->crc16_ccitt(this->buffer, blkSize); if(frame_crc != crc) return false; @@ -104,10 +114,10 @@ bool XModem::checkCrc() bool XModem::checkChkSum() { - unsigned char frame_chksum = (unsigned char)this->dataRead(XModem::receiveDelay); + unsigned char frame_chksum = (unsigned char)this->serialRead(XModem::receiveByteDelay); //calculate chksum unsigned char chksum = 0; - for(int i = 0; i< 128; i++) { + for(int i = 0; i< blockSize; i++) { chksum += this->buffer[i]; } if(frame_chksum == chksum) @@ -118,7 +128,7 @@ bool XModem::checkChkSum() bool XModem::sendNack() { - this->dataWrite(XModem::XMO_NACK); + this->serialWrite(XModem::XMO_NACK); this->retries++; if(this->retries < XModem::rcvRetryLimit) return true; @@ -128,27 +138,35 @@ bool XModem::sendNack() bool XModem::receiveFrames(transfer_t transfer) { - this->blockNo = 1; - this->blockNoExt = 1; + this->blockNo = send0block ? 0 : 1; + this->blockNoExt = send0block ? 0 : 1; this->retries = 0; - while (1) { - char cmd = this->dataRead(1000); - switch(cmd){ + + while (1) + { + xserial.flush(); + char cmd = this->serialRead(XModem::receiveFrameDelay); + this->blockSize = 128; + switch(cmd) + { + case XModem::XMO_STX: + this->blockSize = 1024; case XModem::XMO_SOH: + { if (!this->receiveFrameNo()) { if (this->sendNack()) break; else return false; } - if (!this->receiveData()) { + if (!this->receiveData(this->blockSize)) { if (this->sendNack()) break; else - return false; + return false; }; if (transfer == Crc) { - if (!this->checkCrc()) { + if (!this->checkCrc(this->blockSize)) { if (this->sendNack()) break; else @@ -163,42 +181,115 @@ bool XModem::receiveFrames(transfer_t transfer) } } //callback - if(this->dataHandler != NULL && this->repeatedBlock == false) - if(!this->dataHandler(xfile,this->blockNoExt, this->buffer, 128)) { + if(this->send0block && (this->blockNo == 0) && (this->dfile != 0) && (this->fileSystem != 0)) + { + if(xfile != 0) + xfile->close(); + xfile = 0; + char *fname = buffer; + char *fsize = 0; + if(this->buffer[0] == 0) + { + this->serialWrite(XModem::XMO_ACK); + xserial.flush(); + return true; // received no file (or last file)! + } + int i=0; + for(;i<128;i++) + { + if(this->buffer[i]==0) + { + if(fsize == 0) + fsize = buffer + i + 1; + else + break; + } + } + if((fsize == 0)||(i>=128)) + { + debugPrintf("Ymodem fail: no args.\r\n"); + return false; + } + this->fileSize = atoi(fsize); + if((this->fileSize == 0)||(this->fileSize > fileSizeLimit)) + { + debugPrintf("Ymodem fail: no size.\r\n"); return false; } + if(fname[0] == '/') + fname++; + String path = this->dfile->name(); + if(path.endsWith("/")) + path = path.substring(0,path.length()-1); + path += "/"; + path += fname; + debugPrintf("YModem opened %s for %u bytes.\r\n",path.c_str(),this->fileSize); + if(fileSystem->exists(path)) + fileSystem->remove(path); + yfile = fileSystem->open(path, FILE_WRITE); + if(!yfile) + { + debugPrintf("Ymodem fail: no file.\r\n"); + return false; + } + this->xfile = &yfile; + this->repeatedBlock = false; + } + else + { + if(this->dataHandler != NULL && this->repeatedBlock == false) + { + int dataSize = this->blockSize; + if(this->send0block && (this->fileSize < this->blockSize)) + dataSize = this->fileSize; + if(!this->dataHandler(this->xfile,this->blockNoExt, this->buffer, dataSize)) { + return false; + } + this->fileSize -= dataSize; + } + } //ack - this->dataWrite(XModem::XMO_ACK); + this->serialWrite(XModem::XMO_ACK); + if(this->blockNo == 0) + this->serialWrite(XModem::XMO_CRC); if(this->repeatedBlock == false) { this->blockNo++; this->blockNoExt++; } break; + } case XModem::XMO_EOT: - this->dataWrite(XModem::XMO_ACK); + this->serialWrite(XModem::XMO_ACK); + if(send0block) + { + this->serialWrite(XModem::XMO_CRC); + this->blockNo=0; + this->blockNoExt=0; + this->retries = 0; + break; + } return true; case XModem::XMO_CAN: //wait second CAN - if(this->dataRead(XModem::receiveDelay) ==XModem::XMO_CAN) + if(this->serialRead(XModem::receiveByteDelay) ==XModem::XMO_CAN) { - this->dataWrite(XModem::XMO_ACK); + this->serialWrite(XModem::XMO_ACK); //this->flushInput(); return false; } //something wrong - this->dataWrite(XModem::XMO_CAN); - this->dataWrite(XModem::XMO_CAN); - this->dataWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); return false; default: //something wrong - this->dataWrite(XModem::XMO_CAN); - this->dataWrite(XModem::XMO_CAN); - this->dataWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); return false; } - } } @@ -214,19 +305,18 @@ bool XModem::receive() for (int i =0; i < 16; i++) { - this->dataWrite('C'); - if (this->dataAvail(1500)) + this->serialWrite(XModem::XMO_CRC); + if (this->serialAvail(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)) + this->serialWrite(XModem::XMO_NACK); + if (this->serialAvail(1500)) { bool ok = receiveFrames(ChkSum); xserial.flushAlways(); @@ -254,78 +344,106 @@ unsigned char XModem::generateChkSum(void) { //calculate chksum unsigned char chksum = 0; - for(int i = 0; i< 128; i++) { + for(int i = 0; i< blockSize; i++) { chksum += this->buffer[i]; } return chksum; } +bool XModem::transmitFrame(transfer_t transfer, int blkSize) +{ + //send SOH + if(blkSize == 1024) + this->serialWrite(XModem::XMO_STX); + else + this->serialWrite(XModem::XMO_SOH); + //send frame number + this->serialWrite(this->blockNo); + //send inv frame number + this->serialWrite((unsigned char)(255-(this->blockNo))); + //send data + for(int i = 0; i serialWrite(this->buffer[i]); + //send checksum or crc + if (transfer == ChkSum) + { + this->serialWrite(this->generateChkSum()); + } + else + { + unsigned short crc; + crc = this->crc16_ccitt(this->buffer, blkSize); + this->serialWrite((unsigned char)(crc >> 8)); + this->serialWrite((unsigned char)(crc)); + } + xserial.flush(); + return true; +} + 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) + //memset(this->buffer, 'A', blockSize); + //get data + while(true) // keep going until all frames done { - //get data if (this->dataHandler != NULL) { - if( false == this->dataHandler(xfile,this->blockNoExt, this->buffer, 128)) + if( false == this->dataHandler(xfile,this->blockNoExt, this->buffer, blockSize)) { //end of transfer - this->sendChar(&xserial,XModem::XMO_EOT); + this->serialWrite(XModem::XMO_EOT); //wait ACK - if (this->dataRead(XModem::receiveDelay) == XModem::XMO_ACK) + if (this->serialRead(XModem::receiveFrameDelay) == 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); + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); //wait ACK - if (this->dataRead(XModem::receiveDelay) == XModem::XMO_ACK) + if (this->serialRead(XModem::receiveFrameDelay) == 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) + int retries = 0; + while(true) { - 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 + transmitFrame(transfer, blockSize); + int ret = this->serialRead(XModem::receiveFrameDelay); + if(ret == XModem::XMO_ACK) //data is ok - go to next chunk + { + this->blockNo++; + this->blockNoExt++; + break; + } + else + if(ret == XModem::XMO_NACK) //resend data + { + } + else + if(ret == XModem::XMO_CAN) //abort transmision + { + return false; + } + else + { + } + if(++retries > 10) + { + //cancel transfer due to FAIL - send CAN twice + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); return false; - } + } + } } return false; } @@ -334,20 +452,67 @@ bool XModem::transmit() { int retry = 0; int sym; + bool test = true; this->init(); //wait for CRC transfer while(retry < 32) { - if(this->dataAvail(1000)) + if(this->serialAvail(1000)) { - sym = this->dataRead(1); //data is here - no delay - if(sym == 'C') + sym = this->serialRead(1); //data is here - no delay + if(sym == XModem::XMO_CRC) { + if(send0block) // if YModem, send the header packet + { + strcpy((char *)this->buffer,this->xfile->name()); + int ct = strlen((char *)buffer); + sprintf((char *)(buffer + ct + 1),"%u",(unsigned int)this->fileSize); + ct = ct + strlen((char *)(buffer + ct + 1)) + 1; + for(;ct<128;ct++) + this->buffer[ct]=0; + while(retry < 32) + { + this->blockNo = 0; + this->transmitFrame(Crc, 128); + sym = this->serialRead(XModem::receiveFrameDelay); + if(sym == XModem::XMO_ACK) //data is ok - go to next chunk) + { + sym = this->serialRead(XModem::receiveByteDelay); // this should be the unnecc CRC + if(sym == XModem::XMO_CRC) + break; + retry++; + } + else + if((sym == XModem::XMO_CRC)&&(test)) + { + test=false; + retry++; + } + else + if((sym != XModem::XMO_NACK) || (test)) + { + retry=32; + break; + } + } + } + if(retry >= 32) + break; + retry = 0; bool ok = this->transmitFrames(Crc); xserial.flushAlways(); + if(ok && send0block) + { + for(int i=0;i<128;i++) + this->buffer[i]=0; + this->blockNo = 0; + this->transmitFrame(Crc, 128); + //TODO: YModem: don't leave just yet, but move to next file + } return ok; } + else if(sym == XModem::XMO_NACK) { bool ok = this->transmitFrames(ChkSum); @@ -356,7 +521,9 @@ bool XModem::transmit() } } retry++; - } + } + this->serialWrite(XModem::XMO_CAN); + this->serialWrite(XModem::XMO_CAN); return false; } diff --git a/zimodem/proto_zmodem.h b/zimodem/proto_zmodem.h index f99d519..59d4601 100644 --- a/zimodem/proto_zmodem.h +++ b/zimodem/proto_zmodem.h @@ -402,7 +402,7 @@ static ZModem *initZSerial(FS &fs, FlowControlType commandFlow) return modem; } -static boolean zDownload(FlowControlType flow, FS &fs, String filePath, String &errors) +static bool zDownload(FlowControlType flow, FS &fs, String filePath, String &errors) { time_t starttime = 0; uint64_t bytes_sent=0; @@ -427,7 +427,7 @@ static boolean zDownload(FlowControlType flow, FS &fs, String filePath, String & return (success==ZTRUE) && (modem->zm->cancelled==ZFALSE); } -static boolean zUpload(FlowControlType flow, FS &fs, String dirPath, String &errors) +static bool zUpload(FlowControlType flow, FS &fs, String dirPath, String &errors) { BOOL success=ZFALSE; int i; diff --git a/zimodem/proto_zmodem.ino b/zimodem/proto_zmodem.ino index bfde252..556a9ec 100644 --- a/zimodem/proto_zmodem.ino +++ b/zimodem/proto_zmodem.ino @@ -84,7 +84,7 @@ ZModem::ZModem(FS *zfs, void* 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. + debugPrintf("%s\r\n",str); return ZTRUE; } @@ -153,7 +153,7 @@ int ZModem::data_waiting(unsigned timeout) */ void ZModem::progress(void* cbdata, int64_t current_pos) { - //debugPrintf("POGRESS %lld\n",current_pos); + //debugPrintf("POGRESS %lld\r\n",current_pos); // do nothing? } @@ -2393,7 +2393,7 @@ int ZModem::recv_file_frame(File* fp) 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); + lprintf(LOG_INFO,"packet len %d type %d\r\n",n,type); if (type == ENDOFFRAME || type == FRAMEOK) { @@ -2419,14 +2419,14 @@ int ZModem::recv_file_frame(File* fp) const char* ZModem::source(void) { - return(__FILE__); + return (__FILE__); } char* ZModem::ver(char *buf) { sscanf("$Revision: 1.120 $", "%*s %s", buf); - return(buf); + return (buf); } #endif diff --git a/zimodem/rt_clock.h b/zimodem/rt_clock.h index e612bb5..294ae1f 100644 --- a/zimodem/rt_clock.h +++ b/zimodem/rt_clock.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/zimodem/rt_clock.ino b/zimodem/rt_clock.ino index f74a644..1a6c2e3 100644 --- a/zimodem/rt_clock.ino +++ b/zimodem/rt_clock.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -109,7 +109,6 @@ void RealTimeClock::tick() // subtract seventy years: uint32_t epoch = secsSince1900 - seventyYears; lastMillis = millis(); - debugPrintf("Received NTP: %d/%d/%d %d:%d:%d\n\r",(int)getMonth(),(int)getDay(),(int)getYear(),(int)getHour(),(int)getMinute(),(int)getSecond()); // now to apply the timezone. Ugh; setByUnixEpoch(epoch); String tz=""; @@ -140,6 +139,7 @@ void RealTimeClock::tick() 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 } diff --git a/zimodem/serout.h b/zimodem/serout.h index 56d65cd..3457d76 100644 --- a/zimodem/serout.h +++ b/zimodem/serout.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,10 +37,10 @@ static bool enableRtsCts = true; # 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 char FBUF[256]; static void serialDirectWrite(uint8_t c); static void serialOutDeque(); diff --git a/zimodem/serout.ino b/zimodem/serout.ino index ee7b372..fda1487 100644 --- a/zimodem/serout.ino +++ b/zimodem/serout.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -115,40 +115,49 @@ void ZSerial::setFlowControlType(FlowControlType type) #ifdef ZIMODEM_ESP32 if(flowControlType == FCT_RTSCTS) { - uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_DISABLE,0); + uart_set_hw_flow_ctrl(MAIN_UART_NUM,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); + uart_set_pin(MAIN_UART_NUM, 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) - invertMask = invertMask | UART_INVERSE_CTS; +# 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); + uart_set_pin(MAIN_UART_NUM, 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) - invertMask = invertMask | UART_INVERSE_RTS; +# 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); + //debugPrintf("invert = %d magic values = %d %d, RTS_HIGH=%d, RTS_LOW=%d HIGHHIGH=%d LOWLOW=%d\r\n", + // invertMask,ctsActive,rtsActive, DEFAULT_RTS_ACTIVE, DEFAULT_RTS_INACTIVE, HIGH, LOW); if(invertMask != 0) - uart_set_line_inverse(UART_NUM_2, invertMask); + uart_set_line_inverse(MAIN_UART_NUM, 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); + uart_set_hw_flow_ctrl(MAIN_UART_NUM,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); else - uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); + uart_set_hw_flow_ctrl(MAIN_UART_NUM,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); } else if(pinSupport[pinCTS]) - uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); + uart_set_hw_flow_ctrl(MAIN_UART_NUM,UART_HW_FLOWCTRL_CTS_RTS,CUTOFF); } else - uart_set_hw_flow_ctrl(UART_NUM_2,UART_HW_FLOWCTRL_DISABLE,0); + uart_set_hw_flow_ctrl(MAIN_UART_NUM,UART_HW_FLOWCTRL_DISABLE,0); #endif } @@ -184,7 +193,7 @@ bool ZSerial::isSerialOut() case FCT_RTSCTS: if(pinSupport[pinCTS]) { - //debugPrintf("CTS: pin %d (%d == %d)\n",pinCTS,digitalRead(pinCTS),ctsActive); + //debugPrintf("CTS: pin %d (%d == %d)\r\n",pinCTS,digitalRead(pinCTS),ctsActive); return (digitalRead(pinCTS) == ctsActive); } return true; @@ -206,7 +215,7 @@ bool ZSerial::isSerialCancelled() { if(pinSupport[pinCTS]) { - //debugPrintf("CTS: pin %d (%d == %d)\n",pinCTS,digitalRead(pinCTS),ctsActive); + //debugPrintf("CTS: pin %d (%d == %d)\r\n",pinCTS,digitalRead(pinCTS),ctsActive); return (digitalRead(pinCTS) == ctsInactive); } } @@ -261,7 +270,7 @@ void ZSerial::enqueByte(uint8_t c) } } // the car jam of blocked bytes stops HERE - //debugPrintf("%d\n",serialOutBufferBytesRemaining()); + //debugPrintf("%d\r\n",serialOutBufferBytesRemaining()); while(serialOutBufferBytesRemaining()<1) { if(!isSerialOut()) @@ -345,7 +354,6 @@ void ZSerial::prints(String str) void ZSerial::printf(const char* format, ...) { - int ret; va_list arglist; va_start(arglist, format); vsnprintf(FBUF, sizeof(FBUF), format, arglist); diff --git a/zimodem/src/libssh2/agent.h b/zimodem/src/libssh2/agent.h new file mode 100644 index 0000000..8dfaa03 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/bcrypt_pbkdf.c b/zimodem/src/libssh2/bcrypt_pbkdf.c new file mode 100644 index 0000000..e05a362 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/blf.h b/zimodem/src/libssh2/blf.h new file mode 100644 index 0000000..46fc422 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/blowfish.c b/zimodem/src/libssh2/blowfish.c new file mode 100644 index 0000000..a27105d --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/channel.c b/zimodem/src/libssh2/channel.c new file mode 100644 index 0000000..d0fba10 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/channel.h b/zimodem/src/libssh2/channel.h new file mode 100644 index 0000000..403a260 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/comp.c b/zimodem/src/libssh2/comp.c new file mode 100644 index 0000000..e82b8c9 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/comp.h b/zimodem/src/libssh2/comp.h new file mode 100644 index 0000000..e1bc97a --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/crypt.c b/zimodem/src/libssh2/crypt.c new file mode 100644 index 0000000..9be925f --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/crypto.h b/zimodem/src/libssh2/crypto.h new file mode 100644 index 0000000..a5893f1 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/global.c b/zimodem/src/libssh2/global.c new file mode 100644 index 0000000..9aec87b --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/hostkey.c b/zimodem/src/libssh2/hostkey.c new file mode 100644 index 0000000..290c426 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/keepalive.c b/zimodem/src/libssh2/keepalive.c new file mode 100644 index 0000000..dd733f7 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/kex.c b/zimodem/src/libssh2/kex.c new file mode 100644 index 0000000..2a5881f --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libgcrypt.h b/zimodem/src/libssh2/libgcrypt.h new file mode 100644 index 0000000..32fe004 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libssh2.h b/zimodem/src/libssh2/libssh2.h new file mode 100644 index 0000000..f36da35 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libssh2_config.h b/zimodem/src/libssh2/libssh2_config.h new file mode 100644 index 0000000..1f4faab --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libssh2_priv.h b/zimodem/src/libssh2/libssh2_priv.h new file mode 100644 index 0000000..b3030b3 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libssh2_publickey.h b/zimodem/src/libssh2/libssh2_publickey.h new file mode 100644 index 0000000..4c68eaf --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/libssh2_sftp.h b/zimodem/src/libssh2/libssh2_sftp.h new file mode 100644 index 0000000..32970be --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/mac.c b/zimodem/src/libssh2/mac.c new file mode 100644 index 0000000..22327fa --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/mac.h b/zimodem/src/libssh2/mac.h new file mode 100644 index 0000000..3441ad0 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/mbedtls.c b/zimodem/src/libssh2/mbedtls.c new file mode 100644 index 0000000..27e75fc --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/mbedtls.h b/zimodem/src/libssh2/mbedtls.h new file mode 100644 index 0000000..fbef109 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/misc.c b/zimodem/src/libssh2/misc.c new file mode 100644 index 0000000..8dd5010 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/misc.h b/zimodem/src/libssh2/misc.h new file mode 100644 index 0000000..4d6e96f --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/openssl.h b/zimodem/src/libssh2/openssl.h new file mode 100644 index 0000000..490ccfe --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/packet.c b/zimodem/src/libssh2/packet.c new file mode 100644 index 0000000..da80412 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/packet.h b/zimodem/src/libssh2/packet.h new file mode 100644 index 0000000..009dc7b --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/pem.c b/zimodem/src/libssh2/pem.c new file mode 100644 index 0000000..4f2ce48 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/session.c b/zimodem/src/libssh2/session.c new file mode 100644 index 0000000..d76105b --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/session.h b/zimodem/src/libssh2/session.h new file mode 100644 index 0000000..ff875b4 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/sftp.h b/zimodem/src/libssh2/sftp.h new file mode 100644 index 0000000..49b57fc --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/transport.c b/zimodem/src/libssh2/transport.c new file mode 100644 index 0000000..722d3b8 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/transport.h b/zimodem/src/libssh2/transport.h new file mode 100644 index 0000000..6ff9f59 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/userauth.c b/zimodem/src/libssh2/userauth.c new file mode 100644 index 0000000..4525295 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/userauth.h b/zimodem/src/libssh2/userauth.h new file mode 100644 index 0000000..c493353 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/userauth_kbd_packet.c b/zimodem/src/libssh2/userauth_kbd_packet.c new file mode 100644 index 0000000..9997886 --- /dev/null +++ b/zimodem/src/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/zimodem/src/libssh2/userauth_kbd_packet.h b/zimodem/src/libssh2/userauth_kbd_packet.h new file mode 100644 index 0000000..2435743 --- /dev/null +++ b/zimodem/src/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/zimodem/stringstream.h b/zimodem/stringstream.h index ecdf342..6c8d44c 100644 --- a/zimodem/stringstream.h +++ b/zimodem/stringstream.h @@ -1,5 +1,20 @@ #ifndef _STRING_STREAM_H_INCLUDED_ #define _STRING_STREAM_H_INCLUDED_ +/* + Copyright 2016-2025 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 StringStream : public Stream { diff --git a/zimodem/wificlientnode.h b/zimodem/wificlientnode.h index b5ff7b5..51aa322 100644 --- a/zimodem/wificlientnode.h +++ b/zimodem/wificlientnode.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,15 +17,25 @@ #define PACKET_BUF_SIZE 256 #ifdef ZIMODEM_ESP32 -#include +# include +#endif +#ifdef INCLUDE_SSH +# include "wifisshclient.h" #endif static WiFiClient *createWiFiClient(bool SSL) { #ifdef ZIMODEM_ESP32 if(SSL) - return new WiFiClientSecure(); + { + WiFiClientSecure *c = new WiFiClientSecure(); + c->setInsecure(); + return c; + } else +#else + //WiFiClientSecure *c = new WiFiClientSecure(); + //c->setInsecure(); #endif return new WiFiClient(); } @@ -49,6 +59,9 @@ class WiFiClientNode : public Stream 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; @@ -70,6 +83,7 @@ class WiFiClientNode : public Stream 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(); @@ -103,7 +117,7 @@ class WiFiClientNode : public Stream String readLine(unsigned int timeout); static int getNumOpenWiFiConnections(); - static int checkForAutoDisconnections(); + static void checkForAutoDisconnections(); }; diff --git a/zimodem/wificlientnode.ino b/zimodem/wificlientnode.ino index 224df37..481cf89 100644 --- a/zimodem/wificlientnode.ino +++ b/zimodem/wificlientnode.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,53 +27,90 @@ void WiFiClientNode::finishConnectionLink() last->next = this; } checkOpenConnections(); + if(host != 0) + debugPrintf("\r\nConnected to %s:%d\r\n",host,port); } -WiFiClientNode::WiFiClientNode(char *hostIp, int newport, int flagsBitmap) +void WiFiClientNode::constructNode() { - port=newport; - host=new char[strlen(hostIp)+1]; - strcpy(host,hostIp); id=++WiFiNextClientId; - this->flagsBitmap = flagsBitmap; - clientPtr = createWiFiClient((flagsBitmap&FLAG_SECURE)==FLAG_SECURE); - client = *clientPtr; setCharArray(&delimiters,""); setCharArray(&maskOuts,""); setCharArray(&stateMachine,""); machineState = stateMachine; - if(!client.connect(hostIp, port)) +} + +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)) + { + debugPrintf("\r\nFailed to Connected to %s:%d\r\n",hostIp,newport); // deleted when it returns and is deleted } else { - client.setNoDelay(DEFAULT_NO_DELAY); + 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; + clientPtr=null; // why is this so important?! newClient.setNoDelay(DEFAULT_NO_DELAY); port=newClient.localPort(); - setCharArray(&delimiters,""); - setCharArray(&maskOuts,""); - setCharArray(&stateMachine,""); - machineState = stateMachine; String remoteIPStr = newClient.remoteIP().toString(); const char *remoteIP=remoteIPStr.c_str(); host=new char[remoteIPStr.length()+1]; strcpy(host,remoteIP); - id=++WiFiNextClientId; client = newClient; answered=(ringDelay == 0); if(ringDelay > 0) @@ -91,7 +128,11 @@ WiFiClientNode::~WiFiClientNode() lastPacket[1].len=0; if(host!=null) { - client.stop(); + debugPrintf("\r\nDisconnected from %s:%d\r\n",host,port); + if(clientPtr != null) + clientPtr->stop(); + else + client.stop(); delete host; host=null; } @@ -125,7 +166,14 @@ WiFiClientNode::~WiFiClientNode() bool WiFiClientNode::isConnected() { - return (host != null) && client.connected(); + if(host != null) + { + if(clientPtr != null) + return clientPtr->connected(); + else + return client.connected(); + } + return false; } bool WiFiClientNode::isPETSCII() @@ -194,7 +242,11 @@ int WiFiClientNode::read() return b; } */ - int c = client.read(); + int c; + if(clientPtr != null) + c = clientPtr->read(); + else + c= client.read(); //fillUnderflowBuf(); return c; } @@ -208,13 +260,25 @@ int WiFiClientNode::peek() if(underflowBuf.len > 0) return underflowBuf.buf[0]; */ - return client.peek(); + if(clientPtr != null) + return clientPtr->peek(); + else + return client.peek(); } void WiFiClientNode::flush() { - if((host != null)&&(client.available()==0)) - flushAlways(); + if(host != null) + { + if(clientPtr != null) + { + if(clientPtr->available()==0) + flushAlways(); + } + else + if(client.available()==0) + flushAlways(); + } } void WiFiClientNode::flushAlways() @@ -222,7 +286,10 @@ void WiFiClientNode::flushAlways() if(host != null) { flushOverflowBuffer(); - client.flush(); + if(clientPtr != null) + clientPtr->flush(); + else + client.flush(); } } @@ -230,7 +297,10 @@ int WiFiClientNode::available() { if((host == null)||(!answered)) return 0; - return client.available(); // +underflowBuf.len; + if(clientPtr != null) + return clientPtr->available(); + else + return client.available(); // +underflowBuf.len; } int WiFiClientNode::read(uint8_t *buf, size_t size) @@ -265,7 +335,11 @@ int WiFiClientNode::read(uint8_t *buf, size_t size) return previouslyRead; */ - int bytesRead = client.read(buf,size); + int bytesRead; + if(clientPtr != null) + bytesRead = clientPtr->read(buf,size); + else + bytesRead = client.read(buf,size); //fillUnderflowBuf(); return bytesRead;// + previouslyRead; } @@ -328,7 +402,10 @@ size_t WiFiClientNode::write(const uint8_t *buf, size_t size) return written; } */ - written += client.write(buf, size); + if(clientPtr != null) + written += clientPtr->write(buf, size); + else + written += client.write(buf, size); /* if(written < size) { @@ -397,6 +474,7 @@ size_t WiFiClientNode::write(uint8_t c) { const uint8_t one[] = {c}; write(one,1); + return 1; } int WiFiClientNode::getNumOpenWiFiConnections() @@ -433,7 +511,7 @@ bool WiFiClientNode::isMarkedForDisconnect() return nextDisconnect != 0; } -int WiFiClientNode::checkForAutoDisconnections() +void WiFiClientNode::checkForAutoDisconnections() { WiFiClientNode *conn = conns; while(conn != null) diff --git a/zimodem/wifiservernode.h b/zimodem/wifiservernode.h index b332e91..86aeeff 100644 --- a/zimodem/wifiservernode.h +++ b/zimodem/wifiservernode.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/zimodem/wifiservernode.ino b/zimodem/wifiservernode.ino index 925c8c0..ffe9c74 100644 --- a/zimodem/wifiservernode.ino +++ b/zimodem/wifiservernode.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ WiFiServerNode::WiFiServerNode(int newport, int flagsBitmap) id=++WiFiNextClientId; port=newport; this->flagsBitmap = flagsBitmap; - server = new WiFiServer(newport); + server = new WiFiServer((uint16_t)newport); //BZ:server->setNoDelay(DEFAULT_NO_DELAY); server->begin(); if(servs==null) @@ -261,7 +261,7 @@ void WiFiServerNode::RestoreWiFiServers() WiFiServerSpec snode; if(!ReadWiFiServer(f,snode)) { - debugPrintf("Server: FAIL\n"); + debugPrintf("Server: Read: FAIL\r\n"); fail=true; break; } @@ -278,10 +278,10 @@ void WiFiServerNode::RestoreWiFiServers() 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); + debugPrintf("Server: %d, %d: '%s' '%s'\r\n",node->port,node->flagsBitmap,node->delimiters,node->maskOuts); } else - debugPrintf("Server: DUP %d\n",snode.port); + debugPrintf("Server: DUP %d\r\n",snode.port); } f.close(); } diff --git a/zimodem/wifisshclient.h b/zimodem/wifisshclient.h new file mode 100644 index 0000000..b5f5b27 --- /dev/null +++ b/zimodem/wifisshclient.h @@ -0,0 +1,79 @@ +#ifdef INCLUDE_SSH +/* + Copyright 2023-2025 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/zimodem/wifisshclient.ino b/zimodem/wifisshclient.ino new file mode 100644 index 0000000..3148a1b --- /dev/null +++ b/zimodem/wifisshclient.ino @@ -0,0 +1,301 @@ +#ifdef INCLUDE_SSH +/* + Copyright 2024-2025 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/zimodem/zbrowser.h b/zimodem/zbrowser.h index e512014..b727581 100644 --- a/zimodem/zbrowser.h +++ b/zimodem/zbrowser.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +17,10 @@ #ifdef INCLUDE_SD_SHELL class ZBrowser : public ZMode { +# ifdef INCLUDE_COMET64 + friend class Comet64; +# endif + private: enum ZBrowseState { @@ -31,17 +35,45 @@ class ZBrowser : public ZMode String stripDir(String path); String stripFilename(String path); String stripArgs(String line, String &argLetters); + int argNum(String argLetters, char argLetter, int def); 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); + bool 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); + bool copyFiles(String source, String mask, String target, bool recurse, bool overwrite); + bool doHelpCommand(String &line, bool showShellOutput); + bool doDirCommand(String &line, bool showShellOutput); + bool doMkDirCommand(String &line, bool showShellOutput); + bool doRmDirCommand(String &line, bool showShellOutput); + bool doCdDirCommand(String &line, bool showShellOutput); + bool doCatCommand(String &line, bool showShellOutput); + bool doRmCommand(String &line, bool showShellOutput); + bool doCpCommand(String &line, bool showShellOutput); + bool doRenCommand(String &line, bool showShellOutput); + bool doMoveCommand(String &line, bool showShellOutput); + bool doXGetCommand(String &line, bool showShellOutput); + bool doXPutCommand(String &line, bool showShellOutput); + bool doYGetCommand(String &line, bool showShellOutput); + bool doYPutCommand(String &line, bool showShellOutput); + bool doKGetCommand(String &line, bool showShellOutput); + bool doKPutCommand(String &line, bool showShellOutput); + bool doZGetCommand(String &line, bool showShellOutput); + bool doZPutCommand(String &line, bool showShellOutput); + bool doPGetCommand(String &line, bool showShellOutput); + bool doPPutCommand(String &line, bool showShellOutput); + bool doWGetCommand(String &line, bool showShellOutput); + +#ifdef INCLUDE_FTP + bool doFGetCommand(String &line, bool showShellOutput); + bool doFPutCommand(String &line, bool showShellOutput); + bool doFDirCommand(String &line, bool showShellOutput); FTPHost *ftpHost = 0; +#endif bool showMenu; bool savedEcho; String path="/"; @@ -49,6 +81,7 @@ class ZBrowser : public ZMode char EOLNC[5]; unsigned long lastNumber; String lastString; + bool quiet = false; public: ~ZBrowser(); @@ -56,6 +89,6 @@ class ZBrowser : public ZMode void serialIncoming(); void loop(); void init(); - void doModeCommand(String &line); + bool doModeCommand(String &line, bool showShellOutput); }; #endif diff --git a/zimodem/zbrowser.ino b/zimodem/zbrowser.ino index 1a84927..7423eee 100644 --- a/zimodem/zbrowser.ino +++ b/zimodem/zbrowser.ino @@ -1,1179 +1,1725 @@ -/* - 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()) - { - if(SD.cardType() != CARD_NONE) - browseEnabled = true; - } -} - -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() -{} +/* + Copyright 2016-2025 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()) + { + debugPrintf("External SD card initialized.\r\n"); + } +} + +ZBrowser::~ZBrowser() +{ +#ifdef INCLUDE_FTP + if(ftpHost != 0) + { + delete ftpHost; + ftpHost = 0; + } +#endif +} + +void ZBrowser::switchTo() +{ + debugPrintf("\n\rMode:Browse\r\n"); + 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(); + if(crReceived) + { + if(commandMode.doEcho) + serial.prints(EOLN); + String line =commandMode.getNextSerialCommand(); + doModeCommand(line,true); + } +} + +void ZBrowser::switchBackToCommandMode() +{ + debugPrintf("\r\nMode:Command\r\n"); + + 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;i= 0) + { + x++; + String cnStr = ""; + while((x < argLetters.length())&&(strchr("0123456789",argLetters[x])!=null)) + cnStr += argLetters[x++]; + num = atoi(argLetters.c_str()); + } + return num; +} + +String ZBrowser::cleanOneArg(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; +} + +bool ZBrowser::copyFiles(String source, String mask, String target, bool recurse, bool overwrite) +{ + File root = SD.open(source); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",source.c_str(),EOLNC); + return false; + } + + 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()) + { + if(!quiet) + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return false; + } + } + for(File file = root.openNextFile(); file != null; file = root.openNextFile()) + { + if(matches(file.name(), mask)) + { + debugPrintf("file matched:%s\r\n",file.name()); + String tpath = target; + if(file.isDirectory()) + { + if(!recurse) + { + if(!quiet) + serial.printf("Skipping: %s%s",file.name(),EOLNC); + } + else + { + if(!tpath.endsWith("/")) + tpath += "/"; + tpath += file.name(); + } + } + copyFiles(source + "/" + 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 += root.name(); + debugPrintf("file xform to file in dir:%s\r\n",tpath.c_str()); + } + DD.close(); + } + if(SD.exists(tpath)) + { + File DD=SD.open(tpath); + if(strcmp(DD.name(),root.name())==0) + { + if(!quiet) + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return false; + } + else + if(!overwrite) // cp f a, cp f e + { + if(!quiet) + serial.printf("File exists: %s%s",DD.name(),EOLNC); + DD.close(); + return false; + } + 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) +{ + File root = SD.open(p); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + } + else + if(root.isDirectory()) + { + File file = root.openNextFile(); + while(file) + { + if(matches(file.name(), mask)) + { + debugPrintf("file matched:%s\r\n",file.name()); + if(file.isDirectory()) + { + serial.printf("%sd %s%s",prefix.c_str(),file.name(),EOLNC); + if(recurse) + { + String newPrefix = prefix + " "; + showDirectory(p + "/" + file.name(), mask, newPrefix, recurse); + } + } + else + serial.printf("%s %s %lu%s",prefix.c_str(),file.name(),file.size(),EOLNC); + } + else + debugPrintf("file unmatched:%s (%s)\r\n",file.name(),mask.c_str()); + file = root.openNextFile(); + } + } + else + serial.printf(" %s %lu%s",root.name(),root.size(),EOLNC); +} + +bool ZBrowser::doDirCommand(String &line, bool showShellOutput) +{ + 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); + return true; +} + +bool ZBrowser::doMkDirCommand(String &line, bool showShellOutput) +{ + String p = makePath(cleanOneArg(line)); + debugPrintf("md:%s\r\n",p.c_str()); + if((p.length() < 2) || isMask(p) || !SD.mkdir(p)) + { + if(!quiet) + serial.printf("Illegal path: %s%s",p.c_str(),EOLNC); + return false; + } + return true; +} + +bool ZBrowser::doCdDirCommand(String &line, bool showShellOutput) +{ + bool success = true; + String p = makePath(cleanOneArg(line)); + debugPrintf("cd:%s\r\n",p.c_str()); + if(p.length()==0) + { + if(!quiet) + 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) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + if(!root.isDirectory()) + { + if(!quiet) + serial.printf("Illegal path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + path = p + "/"; + } + return success; +} + +bool ZBrowser::doRmDirCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p = makePath(cleanOneArg(line)); + debugPrintf("rd:%s\r\n",p.c_str()); + File root = SD.open(p); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + if(!root.isDirectory()) + { + if(!quiet) + serial.printf("Not a directory: %s%s",p.c_str(),EOLNC); + } + else + if(!SD.rmdir(p)) + { + if(!quiet) + serial.printf("Failed to remove directory: %s%s",p.c_str(),EOLNC); + success = false; + } + return success; +} + +bool ZBrowser::doCatCommand(String &line, bool showShellOutput) +{ + bool success = true; + String argLetters = ""; + line = stripArgs(line,argLetters); + argLetters.toLowerCase(); + bool dohex=argLetters.indexOf('h')>=0; + String p = makePath(cleanOneArg(line)); + debugPrintf("cat:%s\r\n",p.c_str()); + File root = SD.open(p); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + if(root.isDirectory()) + { + if(!quiet) + serial.printf("Is a directory: %s%s",p.c_str(),EOLNC); + success = false; + } + else + { + root.close(); + File f=SD.open(p, FILE_READ); + int hexct = 0; + for(int i=0;i 15) + { + serial.print(EOLNC); + hexct=0; + } + else + serial.write(' '); + } + else + serial.write(f.read()); + } + serial.print(EOLNC); + f.close(); + } + return success; +} + +bool ZBrowser::doXGetCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p = makePath(cleanOneArg(line)); + debugPrintf("xget:%s\r\n",p.c_str()); + File root = SD.open(p); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + if(root.isDirectory()) + { + if(!quiet) + serial.printf("Is a directory: %s%s",p.c_str(),EOLNC); + root.close(); + success = false; + } + else + { + root.close(); + File rfile = SD.open(p, FILE_READ); + String errors=""; + if(!quiet) + serial.printf("Go to XModem download.%s",EOLNC); + serial.flushAlways(); + if(xDownload(commandMode.getFlowControlType(), rfile,errors)) + { + rfile.close(); + delay(2000); + if(!quiet) + serial.printf("Download completed successfully.%s",EOLNC); + } + else + { + rfile.close(); + delay(2000); + if(!quiet) + serial.printf("Download failed (%s).%s",errors.c_str(),EOLNC); + success = false; + } + } + return success; +} + +bool ZBrowser::doXPutCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p = makePath(cleanOneArg(line)); + debugPrintf("xput:%s\r\n",p.c_str()); + File root = SD.open(p); + if(root) + { + if(!quiet) + serial.printf("File exists: %s%s",root.name(),EOLNC); + root.close(); + success = false; + } + else + { + String dirNm=stripDir(p); + File rootDir=SD.open(dirNm); + if((!rootDir)||(!rootDir.isDirectory())) + { + if(!quiet) + serial.printf("Path doesn't exist: %s%s",dirNm.c_str(),EOLNC); + if(rootDir) + rootDir.close(); + success = false; + } + else + { + File rfile = SD.open(p, FILE_WRITE); + String errors=""; + if(!quiet) + serial.printf("Go to XModem upload.%s",EOLNC); + serial.flushAlways(); + if(xUpload(commandMode.getFlowControlType(), rfile,errors)) + { + rfile.close(); + delay(2000); + if(!quiet) + serial.printf("Upload completed successfully.%s",EOLNC); + } + else + { + rfile.close(); + delay(2000); + if(!quiet) + serial.printf("Upload failed (%s).%s",errors.c_str(),EOLNC); + success = false; + } + } + } + return success; +} + +bool ZBrowser::doYGetCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p = makePath(cleanOneArg(line)); + debugPrintf("yget:%s\r\n",p.c_str()); + File root = SD.open(p); + if(!root) + { + if(!quiet) + serial.printf("Unknown path: %s%s",p.c_str(),EOLNC); + success = false; + } + else + if(root.isDirectory()) + { + if(!quiet) + serial.printf("Is not a directory: %s%s",p.c_str(),EOLNC); + root.close(); + success = false; + } + else + { + String errors=""; + if(!quiet) + serial.printf("Go to YModem download.%s",EOLNC); + serial.flushAlways(); + if(yDownload(&SD, commandMode.getFlowControlType(), root,errors)) + { + delay(2000); + if(!quiet) + serial.printf("Download completed successfully.%s",EOLNC); + } + else + { + delay(2000); + if(!quiet) + serial.printf("Download failed (%s).%s",errors.c_str(),EOLNC); + success = false; + } + } + return success; +} + +bool ZBrowser::doYPutCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p = makePath(cleanOneArg(line)); + debugPrintf("yput:%s\r\n",p.c_str()); + File rootDir=SD.open(p); + if((!rootDir)||(!rootDir.isDirectory())) + { + if(!quiet) + serial.printf("Path doesn't exist: %s%s",p.c_str(),EOLNC); + if(rootDir) + rootDir.close(); + success = false; + } + else + { + String errors=""; + if(!quiet) + serial.printf("Go to YModem upload.%s",EOLNC); + serial.flushAlways(); + if(yUpload(&SD, commandMode.getFlowControlType(), rootDir, errors)) + { + rootDir.close(); + delay(2000); + if(!quiet) + serial.printf("Upload completed successfully.%s",EOLNC); + } + else + { + rootDir.close(); + delay(2000); + if(!quiet) + serial.printf("Upload failed (%s).%s",errors.c_str(),EOLNC); + success = false; + } + } + return success; +} + +bool ZBrowser::doKGetCommand(String &line, bool showShellOutput) +{ + bool success=true; + String rawPath = makePath(cleanOneArg(line)); + String p=stripDir(rawPath); + String mask=stripFilename(rawPath); + String errors=""; + String **fileList=(String**)malloc(sizeof(String *)); + int numFiles=0; + makeFileList(&fileList,&numFiles,p,mask,true); + if(!quiet) + serial.printf("Go to Kermit download.%s",EOLNC); + serial.flushAlways(); + if(kDownload(commandMode.getFlowControlType(),SD,fileList,numFiles,errors)) + { + delay(2000); + if(!quiet) + serial.printf("Download completed successfully.%s",EOLNC); + serial.flushAlways(); + } + else + { + delay(2000); + if(!quiet) + serial.printf("Download failed (%s).%s",errors.c_str(),EOLNC); + serial.flushAlways(); + success = false; + } + for(int i=0;i=0; + String rawPath = makePath(cleanOneArg(line)); + String p=stripDir(rawPath); + String mask=stripFilename(rawPath); + debugPrintf("rm:%s (%s)\r\n",p.c_str(),mask.c_str()); + deleteFile(p,mask,recurse); + return success; +} + +bool ZBrowser::doCpCommand(String &line, bool showShellOutput) +{ + bool success=true; + 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\r\n",p1.c_str(),mask.c_str(), p2.c_str()); + success = copyFiles(p1,mask,p2,recurse,overwrite); + return success; +} + +bool ZBrowser::doRenCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p1=makePath(cleanFirstArg(line)); + String p2=makePath(cleanRemainArg(line)); + debugPrintf("ren:%s -> %s\r\n",p1.c_str(), p2.c_str()); + if(p1 == p2) + { + if(!quiet) + serial.printf("File exists: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + if(SD.exists(p2)) + { + if(!quiet) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + success = false; + } + else + { + if(!SD.rename(p1,p2)) + { + if(!quiet) + serial.printf("Failed to rename: %s%s",p1.c_str(),EOLNC); + success = false; + } + } + return success; +} + +bool ZBrowser::doWGetCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p1=cleanFirstArg(line); + String p2=makePath(cleanRemainArg(line)); + debugPrintf("wget:%s -> %s\r\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) + && (strcmp(p1.substring(0,11).c_str(),"gophers://") != 0) + && (strcmp(p1.substring(0,10).c_str(),"gopher://") != 0))) + { + if(!quiet) + serial.printf("Not a url: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + if(SD.exists(p2)) + { + if(!quiet) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + success = false; + } + else + { + char buf[p1.length()+1]; + strcpy(buf,p1.c_str()); + bool gopher = p1.startsWith("gopher"); + char *hostIp; + char *req; + int port; + bool doSSL; + if(!parseWebUrl((uint8_t *)buf,&hostIp,&req,&port,&doSSL)) + { + if(!quiet) + serial.printf("Invalid url: %s",p1.c_str()); + success = false; + } + else + if(gopher) + { + if(!doGopherGet(hostIp, port, &SD, p2.c_str(), req, doSSL)) + { + if(!quiet) + serial.printf("Wget failed: %s to file %s",p1.c_str(),p2.c_str()); + success = false; + } + } + else + if(!doWebGet(hostIp, port, &SD, p2.c_str(), req, doSSL)) + { + if(!quiet) + serial.printf("Wget failed: %s to file %s",p1.c_str(),p2.c_str()); + success = false; + } + } + return success; +} + +#ifdef INCLUDE_FTP + +bool ZBrowser::doFGetCommand(String &line, bool showShellOutput) +{ + bool success=true; + 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\r\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)) + { + if(!quiet) + serial.printf("Url required: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + if(SD.exists(p2)) + { + if(!quiet) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + success = false; + } + else + { + uint8_t buf[p1.length()+1]; + strcpy((char *)buf,p1.c_str()); + char *req; + ftpHost = makeFTPHost(isUrl,ftpHost,buf,&req); + if(req == 0) + { + if(!quiet) + serial.printf("Invalid url: %s",p1.c_str()); + success = false; + } + else + if(!ftpHost->doGet(&SD, p2.c_str(), req)) + { + if(!quiet) + serial.printf("Fget failed: %s to file %s",p1.c_str(),p2.c_str()); + success = false; + } + } + return success; +} + +bool ZBrowser::doFPutCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p1=makePath(cleanFirstArg(line)); + String p2=cleanRemainArg(line); + debugPrintf("fput:%s -> %s\r\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)) + { + if(!quiet) + serial.printf("Url required: %s%s",p2.c_str(),EOLNC); + success = false; + } + else + if(!SD.exists(p1)) + { + if(!quiet) + serial.printf("File not found: %s%s",p1.c_str(),EOLNC); + success = false; + } + 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) + { + if(!quiet) + serial.printf("Invalid url: %s",p2.c_str()); + success = false; + } + else + if(!file) + { + if(!quiet) + serial.printf("File not found: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + { + if(!ftpHost->doPut(file, req)) + { + if(!quiet) + serial.printf("Fput failed: %s from file %s",p2.c_str(),p1.c_str()); + success = false; + } + file.close(); + } + } + return success; +} + +bool ZBrowser::doFDirCommand(String &line, bool showShellOutput) +{ + bool success=true; + String p1=cleanOneArg(line); + debugPrintf("fls:%s\r\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)) + { + if(!quiet) + serial.printf("Url required: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + { + uint8_t buf[p1.length()+1]; + strcpy((char *)buf,p1.c_str()); + char *req; + ftpHost = makeFTPHost(isUrl,ftpHost,buf,&req); + if(req == 0) + { + if(!quiet) + serial.printf("Invalid url: %s",p1.c_str()); + success = false; + } + else + if(!ftpHost->doLS(&serial, req)) + { + if(!quiet) + serial.printf("Fls failed: %s",p1.c_str()); + success = false; + } + } + return success; +} + +#endif + +bool ZBrowser::doMoveCommand(String &line, bool showShellOutput) +{ + bool success=true; + 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\r\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\r\n",p1.c_str(),p2.c_str()); + } + root.close(); + if(p1 == p2) + { + if(!quiet) + serial.printf("File exists: %s%s",p1.c_str(),EOLNC); + success = false; + } + else + if(SD.exists(p2) && (!overwrite)) + { + if(!quiet) + serial.printf("File exists: %s%s",p2.c_str(),EOLNC); + success = false; + } + else + { + if(SD.exists(p2)) + SD.remove(p2); + if(!SD.rename(p1,p2)) + { + if(!quiet) + serial.printf("Failed to move: %s%s",p1.c_str(),EOLNC); + success = false; + } + } + } + else + { + success = copyFiles(p1,mask,p2,false,overwrite); + if(success) + success = deleteFile(p1,mask,false); + } + return success; +} + +bool ZBrowser::doHelpCommand(String &line, bool showShellOutput) +{ + bool success=true; + if(!quiet) + { + 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 [-h] [/][path]filename - View a file(s)%s",EOLNC); + serial.printf("df/free/info - Show space remaining%s",EOLNC); + serial.printf("xget/zget/kget/pget [/][path]filename - Download a file%s",EOLNC); + serial.printf("xput/zput/kput/pput [/][path]filename - Upload a file%s",EOLNC); + serial.printf("wget [http://url] [/][path]filename - Download url to file%s",EOLNC); +#ifdef INCLUDE_FTP + 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); +#endif + serial.printf("exit/quit/x/endshell - Quit to command mode%s",EOLNC); + serial.printf("%s",EOLNC); + } + return success; +} + +bool ZBrowser::doModeCommand(String &line, bool showShellOutput) +{ + char c='?'; + bool success = true; + for(int i=0;i 0) + { + cmd=line.substring(0,sp); + cmd.trim(); + line = line.substring(sp+1); + line.trim(); + } + else + line = ""; + quiet = !showShellOutput; + 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(); + quiet = false; + return success; + } + else + if(cmd.equalsIgnoreCase("ls")||cmd.equalsIgnoreCase("dir")||cmd.equalsIgnoreCase("$")||cmd.equalsIgnoreCase("list")) + success = doDirCommand(line,showShellOutput); + else + if(cmd.equals("?")||cmd.equals("help")) + success = doHelpCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("mv")||cmd.equalsIgnoreCase("move")) + success = doMoveCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("rm")||cmd.equalsIgnoreCase("del")||cmd.equalsIgnoreCase("delete")) + success = doRmCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("cp")||cmd.equalsIgnoreCase("copy")) + success = doCpCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("df")||cmd.equalsIgnoreCase("free")||cmd.equalsIgnoreCase("info")) + { + if(!quiet) + serial.printf("%llu free of %llu total%s",(SD.totalBytes()-SD.usedBytes()),SD.totalBytes(),EOLNC); + } + else + if(cmd.equalsIgnoreCase("ren")||cmd.equalsIgnoreCase("rename")) + success = doRenCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("md")||cmd.equalsIgnoreCase("mkdir")||cmd.equalsIgnoreCase("makedir")) + success = doMkDirCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("cd")) + success = doCdDirCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("rd")||cmd.equalsIgnoreCase("rmdir")||cmd.equalsIgnoreCase("deletedir")) + success = doRmDirCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("cat")||cmd.equalsIgnoreCase("type")) + success = doCatCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("xget")) + success = doXGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("xput")) + success = doXPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("yget")) + success = doYGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("yput")) + success = doYPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("zget")||cmd.equalsIgnoreCase("rz")||cmd.equalsIgnoreCase("rz.exe")) + success = doZGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("zput")||cmd.equalsIgnoreCase("sz")) + success = doYPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("kget")||cmd.equalsIgnoreCase("rk")) + success = doKGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("kput")||cmd.equalsIgnoreCase("sk")) + success = doKPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("pget")) + success = doPGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("pput")) + success = doPPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("wget")) + success = doWGetCommand(line,showShellOutput); + else +#ifdef INCLUDE_FTP + if(cmd.equalsIgnoreCase("fget")) + success = doFGetCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("fput")) + success = doFPutCommand(line,showShellOutput); + else + if(cmd.equalsIgnoreCase("fls") || cmd.equalsIgnoreCase("fdir")) + success = doFDirCommand(line,showShellOutput); + else +#endif + if(!quiet) + serial.printf("Unknown command: '%s'. Try '?'.%s",cmd.c_str(),EOLNC); + showMenu=true; + break; + } + } + quiet = !showShellOutput; + return success; +} + +void ZBrowser::loop() +{ + if(showMenu) + { + showMenu=false; + switch(currState) + { + case ZBROW_MAIN: + { + if(!quiet) + serial.printf("%s%s> ",EOLNC,path.c_str()); + break; + } + } + } + if(checkPlusPlusPlusEscape()) + { + switchBackToCommandMode(); + } + else + if(serial.isSerialOut()) + { + serialOutDeque(); + } + logFileLoop(); +} +#else +static void initSDShell() +{} #endif diff --git a/zimodem/zcomet64mode.h b/zimodem/zcomet64mode.h new file mode 100644 index 0000000..18a03e3 --- /dev/null +++ b/zimodem/zcomet64mode.h @@ -0,0 +1,34 @@ +/* + Copyright 2024-2025 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_COMET64 +#include "proto_comet64.h" + +class ZComet64Mode : public ZMode +{ + private: + void switchBackToCommandMode(); + Comet64 *proto = 0; + + public: + void switchTo(); + void serialIncoming(); + void loop(); +}; + +#endif +#endif diff --git a/zimodem/zcomet64mode.ino b/zimodem/zcomet64mode.ino new file mode 100644 index 0000000..4778a96 --- /dev/null +++ b/zimodem/zcomet64mode.ino @@ -0,0 +1,57 @@ +/* + Copyright 2016-2025 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_COMET64 + +void ZComet64Mode::switchBackToCommandMode() +{ + debugPrintf("\r\nMode:Command\r\n"); + if(proto != 0) + delete proto; + proto = 0; + currMode = &commandMode; + if(baudState == BS_SWITCHED_TEMP) + baudState = BS_SWITCH_NORMAL_NEXT; +} + +void ZComet64Mode::switchTo() +{ + debugPrintf("\r\nMode:Comet64\r\n"); + currMode=&comet64Mode; + if(proto == 0) + proto = new Comet64(&SD,commandMode.serial.getFlowControlType()); + if((tempBaud > 0) && (baudState == BS_NORMAL)) + baudState = BS_SWITCH_TEMP_NEXT; + checkBaudChange(); +} + +void ZComet64Mode::serialIncoming() +{ + if(proto != 0) + proto->receiveLoop(); +} + +void ZComet64Mode::loop() +{ + serialOutDeque(); + if((proto != 0) && (proto->isAborted())) + switchBackToCommandMode(); + logFileLoop(); + checkBaudChange(); +} + +#endif +#endif diff --git a/zimodem/zcommand.h b/zimodem/zcommand.h index 2bc4cc9..e8f0f21 100644 --- a/zimodem/zcommand.h +++ b/zimodem/zcommand.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,6 +21,9 @@ static const char *CONFIG_FILE = "/zconfig_v2.txt"; #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, @@ -73,22 +76,34 @@ enum ConfigOptions CFG_STATIC_SN=36, CFG_BUSYMSG=37, CFG_S62_TELNET=38, - CFG_LAST=38 + CFG_S63_HANGUP=39, + CFG_ALTOPMODE=40, + CFG_LAST=40 }; -const ConfigOptions v2HexCfgs[] = { CFG_WIFISSI, CFG_WIFIPW, CFG_TIMEZONE, CFG_TIMEFMT, CFG_TIMEURL, - CFG_PRINTSPEC, CFG_BUSYMSG, CFG_HOSTNAME, CFG_TERMTYPE, (ConfigOptions)255 }; +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 + 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 +}; + +enum OpModes +{ + OPMODE_NONE, + OPMODE_1650, + OPMODE_1660, + OPMODE_1670 }; class ZCommand : public ZMode @@ -99,66 +114,79 @@ class ZCommand : public ZMode #ifdef INCLUDE_IRCC friend class ZIRCMode; #endif +#ifdef INCLUDE_COMET64 + friend class ZComet64Mode; +#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; + char CRLF[4]; + char LFCR[4]; + char LF[2]; + char CR[2]; + char BS = 8; + + ZSerial serial; + + WiFiClientNode *current = null; + WiFiClientNode *nextConn = null; + bool packetXOn = true; + bool busyMode = false; + char ringCounter = 1; + BinType binType = BTYPE_NORMAL; + uint8_t nbuf[MAX_COMMAND_SIZE]; + char hbuf[MAX_COMMAND_SIZE]; + int eon = 0; + int lastServerClientId = 0; + bool autoStreamMode = false; + bool telnetSupport = false; + bool preserveListeners = false; + char *tempDelimiters = NULL; + char *delimiters = NULL; + char *tempMaskOuts = NULL; + char *maskOuts = NULL; + char *tempStateMachine = NULL; + char *stateMachine = NULL; + char *machineState = NULL; + String machineQue = ""; + String previousCommand = ""; + int lastPacketId = -1; + + unsigned long lastPulseTimeMs = 0; + int lastPulseState = DEFAULT_OTH_INACTIVE; + unsigned int pulseWork = 0; + String pulseBuf = ""; byte CRC8(const byte *data, byte len); void showInitMessage(); bool readSerialStream(); - bool clearPlusProgress(); - bool checkPlusEscape(); + bool checkPlusPlusPlusDisconnect(); String getNextSerialCommand(); ZResult doSerialCommand(); void setConfigDefaults(); void parseConfigOptions(String configArguments[]); void setOptionsFromSavedConfig(String configArguments[]); - void reSaveConfig(); + bool reSaveConfig(int retries); + void setAltOpModeAdjustments(); + int pinStatusDecoder(int pinActive, int pinInactive); + int getStatusRegister(const int snum, int crc8); + ZResult setStatusRegister(const int snum, const int sval, int *crc8, const ZResult oldRes); + void packetOut(uint8_t id, uint8_t *cbuf, uint16_t bufLen, uint8_t num); void reSendLastPacket(WiFiClientNode *conn, uint8_t which); - void acceptNewConnection(); + bool 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(); + void checkPulseDial(); 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 doResetCommand(bool resetOpMode); + ZResult doNoListenCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber); 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); @@ -171,18 +199,20 @@ class ZCommand : public ZMode 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); +#ifdef INCLUDE_OTH_UPDATES + ZResult doUpdateFirmware(int vval, uint8_t *vbuf, int vlen, bool isNumber); +#endif public: - int packetSize = 127; - bool suppressResponses; - bool numericResponses; - bool longResponses; - boolean doEcho; - String EOLN; - char EC='+'; - char ECS[32]; + int packetSize = 127; + bool suppressResponses; + bool numericResponses; + bool longResponses; + bool doEcho; + String EOLN; + char EC = '+'; + char ECS[32]; ZCommand(); void loadConfig(); diff --git a/zimodem/zcommand.ino b/zimodem/zcommand.ino index 0b36654..2af2f2b 100644 --- a/zimodem/zcommand.ino +++ b/zimodem/zcommand.ino @@ -1,3691 +1,4311 @@ -/* - 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(); - -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; -} - -void ZCommand::reset() -{ - doResetCommand(); - showInitMessage(); -} - - -byte ZCommand::CRC8(const byte *data, byte len) -{ - byte crc = 0x00; - //logPrint("CRC8: "); - int c=0; - while (len--) - { - byte extract = *data++; - /* save for later debugging - if(logFileOpen) - { - logFile.print(TOHEX(extract)); - if((++c)>20) - { - 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,autoStreamMode,ringCounter,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) -{ - 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; - int baudChk=atoi((char *)vbuf); - if((baudChk<128)||(baudChk>115200)) - return ZERROR; - if((conStr[0]<'5')||(conStr[0]>'8')) - return ZERROR; - if((conStr[2]!='1')&&(conStr[2]!='2')) - return ZERROR; - char *parPtr=strchr("oemn",lc(conStr[1])); - if(parPtr==NULL) - return ZERROR; - char parity=*parPtr; - uint32_t configChk=0; - 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; - } - if(conStr[2]=='1') - configChk = configChk | UART_NB_STOP_BIT_1; - else - if(conStr[2]=='2') - configChk = configChk | UART_NB_STOP_BIT_2; - switch(parity) - { - case 'o': - configChk = configChk | UART_PARITY_ODD; - break; - case 'e': - configChk = configChk | UART_PARITY_EVEN; - break; - case 'm': - configChk = configChk | UART_PARITY_MASK; - break; - case 'n': - configChk = configChk | UART_PARITY_NONE; - break; - } - serialConfig=(SerialConfig)configChk; - baudRate=baudChk; - } - else - { - baudRate=vval; - } - hwSerialFlush(); - changeBaudRate(baudRate); - changeSerialConfig(serialConfig); - return ZOK; -} - -ZResult ZCommand::doConnectCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber, const char *dmodifiers) -{ - 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((WiFi.status() != WL_CONNECTED) - &&(vval== 1) - &&(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(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 *colon=strstr((char *)vbuf,":"); - int port=23; - if(colon != null) - { - (*colon)=0; - port=atoi((char *)(++colon)); - } - int flagsBitmap=0; - { - ConnSettings flags(dmodifiers); - 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"); - 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); -} - -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/guru"; -#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((vlen==0)||(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 *colon=strstr((char *)vbuf,":"); - int port=23; - if(colon != null) - { - (*colon)=0; - port=atoi((char *)(++colon)); - } - WiFiClientNode *c = new WiFiClientNode((char *)vbuf,port,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(); - streamMode.switchTo(c); - lastServerClientId=0; - if(ringCounter == 0) - { - c->answer(); - sendConnectionNotice(c->id); - checkOpenConnections(); - return ZIGNORE; - } - break; - } - c=c->next; - } - return ZOK; // not really doing anything important... - } - else - { - 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; - } - current = null; - nextConn = null; - return ZOK; - } - else - if(isNumber && (vval == 0)) - { - if(current != 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; - 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; - } -} - -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); - else - logPrintfln("Proc: %c %lu ''",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 - { - streamMode.switchTo(current); - result = ZOK; - } - } - 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)&&(browseEnabled)) - { - 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 - 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(); -#ifdef INCLUDE_SD_SHELL - serial.prints("GuruModem WiFi Firmware v"); -#else - serial.prints("Zimodem32 Firmware v"); -#endif - -#else - FSInfo info; - SPIFFS.info(info); - int totalSPIFFSSize = info.totalBytes; -#ifdef RS232_INVERTED - serial.prints("C64Net WiFi Firmware v"); -#else - serial.prints("Zimodem Firmware v"); -#endif -#endif - 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); - } -} - -bool 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 * 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 <= 0) - { - s_pinWrite(pinRI,riInactive); - if(ringCounter > 0) - { - preEOLN(EOLN); - serial.prints(numericResponses?"2":"RING"); - serial.prints(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; - } - if(checkOpenConnections()==0) - { - s_pinWrite(pinRI,riInactive); - } -} - -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(); -} +/* + Copyright 2016-2025 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() +{ + altOpMode = OPMODE_NONE; + doEcho=true; + busyMode=false; + autoStreamMode=false; // should prob have been true all along, ats0 takes care of this. + telnetSupport=true; + preserveListeners=false; + ringCounter=1; + streamMode.setHangupType(HANGUP_NONE); + serial.setFlowControlType(DEFAULT_FCT); + serial.setXON(true); + packetXOn = true; +# ifdef INCLUDE_CMDRX16 + packetXOn = false; +# endif + serial.setPetsciiMode(false); + binType=BTYPE_NORMAL; + serialDelayMs=0; + dcdActive=DEFAULT_DCD_ACTIVE; + dcdInactive=DEFAULT_DCD_INACTIVE; +# ifdef HARD_DCD_HIGH + dcdInactive=DEFAULT_DCD_ACTIVE; +# elif defined(HARD_DCD_LOW) + dcdActive=DEFAULT_DCD_INACTIVE; +# endif + ctsActive=DEFAULT_CTS_ACTIVE; + ctsInactive=DEFAULT_CTS_INACTIVE; + rtsActive=DEFAULT_RTS_ACTIVE; + rtsInactive=DEFAULT_RTS_INACTIVE; + riActive = DEFAULT_RTS_ACTIVE; + riInactive = DEFAULT_RTS_INACTIVE; + dtrActive = DEFAULT_RTS_ACTIVE; + dtrInactive = DEFAULT_RTS_INACTIVE; + dsrActive = DEFAULT_RTS_ACTIVE; + dsrInactive = DEFAULT_RTS_INACTIVE; + othActive = DEFAULT_OTH_ACTIVE; + othInactive = DEFAULT_OTH_INACTIVE; + pinDCD = DEFAULT_PIN_DCD; + pinCTS = DEFAULT_PIN_CTS; + pinRTS = DEFAULT_PIN_RTS; + pinDTR = DEFAULT_PIN_DTR; + pinOTH = DEFAULT_PIN_OTH; + 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[pinOTH]) + pinMode(pinOTH,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(bool resetOpMode) +{ + while(conns != null) + { + WiFiClientNode *c=conns; + delete c; + } + current = null; + nextConn = null; + WiFiServerNode::DestroyAllServers(); + OpModes oldMode = altOpMode; + setConfigDefaults(); + String argv[CFG_LAST+1]; + parseConfigOptions(argv); + eon=0; + serial.setXON(true); + packetXOn = true; +# ifdef INCLUDE_CMDRX16 + packetXOn = false; +# endif + serial.setPetsciiMode(false); + serialDelayMs=0; + binType=BTYPE_NORMAL; + serial.setFlowControlType(DEFAULT_FCT); + if(!resetOpMode) + argv[CFG_ALTOPMODE] = String(oldMode); + setOptionsFromSavedConfig(argv); + memset(nbuf,0,MAX_COMMAND_SIZE); + busyMode=false; + return ZOK; +} + +ZResult ZCommand::doNoListenCommand(int vval, uint8_t *vbuf, int vlen, bool isNumber) +{ + if(vval>0 && isNumber) + { + WiFiServerNode *s=servs; + while(s!=null) + { + if(vval == s->id) + { + delete s; + updateAutoAnswer(); + return ZOK; + } + s=s->next; + } + return ZERROR; + } + 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); + } +} + +bool ZCommand::reSaveConfig(int retries) +{ + 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_ACTIVE); + int ctsMode = pinModeCoder(ctsActive, ctsInactive, DEFAULT_CTS_ACTIVE); + int rtsMode = pinModeCoder(rtsActive, rtsInactive, DEFAULT_RTS_ACTIVE); + int riMode = pinModeCoder(riActive, riInactive, DEFAULT_RTS_ACTIVE); + int dtrMode = pinModeCoder(dtrActive, dtrInactive, DEFAULT_DTR_ACTIVE); + int dsrMode = pinModeCoder(dsrActive, dsrInactive, DEFAULT_DSR_ACTIVE); + 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,",wifiSSIhex.c_str(), wifiPWhex.c_str(), baudRate, eoln); + f.printf("%d,%d,%d,%d,",serial.getFlowControlType(), doEcho, suppressResponses, numericResponses); + f.printf("%d,%d,%d,%d,%d,",longResponses, serial.isPetsciiMode(), dcdMode, serialConfig, ctsMode); + f.printf("%d,%d,%d,%d,%d,%d,%d,",rtsMode,pinDCD,pinCTS,pinRTS,ringCounter,autoStreamMode,preserveListeners); + f.printf("%d,%d,%d,%d,%d,%d,",riMode,dtrMode,dsrMode,pinRI,pinDTR,pinDSR); + f.printf("%d,",zclock.isDisabled()?999:zclock.getTimeZoneCode()); + f.printf("%s,%s,%s,",zclockFormathex.c_str(),zclockHosthex.c_str(),hostnamehex.c_str()); + f.printf("%d,%s,%s,",printMode.getTimeoutDelayMs(),printSpechex.c_str(),termTypehex.c_str()); + f.printf("%s,%s,%s,%s,",staticIPstr.c_str(),staticDNSstr.c_str(),staticGWstr.c_str(),staticSNstr.c_str()); + f.printf("%s,%d,%d,%d",busyMsghex.c_str(),telnetSupport,streamMode.getHangupType(),altOpMode); + 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\r\n",str.c_str()); + for(int i=0;i0) + { + debugPrintf("Error saving config: Trying again.\r\n"); + delay(100); + yield(); + return reSaveConfig(retries-1); + } + return false; +} + +int ZCommand::getConfigFlagBitmap() +{ + return serial.getConfigFlagBitmap() | (doEcho ? FLAG_ECHO : 0); +} + +void ZCommand::setOptionsFromSavedConfig(String configArguments[]) +{ + if(configArguments[CFG_EOLN].length()>0) + { + 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_ACTIVE,DEFAULT_DCD_INACTIVE); + pinModeDecoder(configArguments[CFG_CTSMODE],&ctsActive,&ctsInactive,DEFAULT_CTS_ACTIVE,DEFAULT_CTS_INACTIVE); + pinModeDecoder(configArguments[CFG_RTSMODE],&rtsActive,&rtsInactive,DEFAULT_RTS_ACTIVE,DEFAULT_RTS_INACTIVE); + pinModeDecoder(configArguments[CFG_RIMODE],&riActive,&riInactive,DEFAULT_RI_ACTIVE,DEFAULT_RI_INACTIVE); + pinModeDecoder(configArguments[CFG_DTRMODE],&dtrActive,&dtrInactive,DEFAULT_DTR_ACTIVE,DEFAULT_DTR_INACTIVE); + pinModeDecoder(configArguments[CFG_DSRMODE],&dsrActive,&dsrInactive,DEFAULT_DSR_ACTIVE,DEFAULT_DSR_INACTIVE); + 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_S63_HANGUP].length()>0) + streamMode.setHangupType((HangupType)atoi(configArguments[CFG_S63_HANGUP].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]; + if(configArguments[CFG_ALTOPMODE].length()>0) + { + altOpMode = (OpModes)atoi(configArguments[CFG_ALTOPMODE].c_str()); + setAltOpModeAdjustments(); + } + 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\r\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\r\n",wifiSSI.c_str()); + connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN); + nextReconnectDelay = DEFAULT_RECONNECT_DELAY; + } + debugPrintf("Resetting...\r\n"); + doResetCommand(true); + if(altOpMode == OPMODE_NONE) + showInitMessage(); + debugPrintf("Init complete.\r\n"); +} + +void ZCommand::setAltOpModeAdjustments() +{ + switch(altOpMode) + { + case OPMODE_1650: + { + suppressResponses=true; + doEcho=false; + busyMode=false; + autoStreamMode=true; + telnetSupport=false; + streamMode.setDefaultEcho(false); + streamMode.setHangupType(HANGUP_PDP); + ringCounter=1; + serial.setFlowControlType(FCT_DISABLED); + if(baudRate != 300) + { + baudRate=300; + changeBaudRate(baudRate); + } + riActive = HIGH; // remember, this are inverted, so active LOW + riInactive = LOW; + s_pinWrite(pinRI,riInactive); // drive it high + othActive = DEFAULT_OTH_ACTIVE; + othInactive = DEFAULT_OTH_INACTIVE; + dcdActive = HIGH; + dcdInactive = LOW; + checkOpenConnections(); + break; + } + case OPMODE_1660: + { + suppressResponses=true; + doEcho=false; + busyMode=false; + autoStreamMode=true; + telnetSupport=false; + streamMode.setDefaultEcho(false); + streamMode.setHangupType(HANGUP_PDP); + ringCounter=1; + serial.setFlowControlType(FCT_DISABLED); + if(baudRate != 300) + { + baudRate=300; + changeBaudRate(baudRate); + } + riActive = HIGH; // remember, this are inverted, so active LOW + riInactive = LOW; + s_pinWrite(pinRI,riInactive); // drive it high + othActive = DEFAULT_OTH_INACTIVE; + othInactive = DEFAULT_OTH_ACTIVE; + checkOpenConnections(); + break; + } + case OPMODE_1670: + { + busyMode=false; + autoStreamMode=true; + telnetSupport=false; + streamMode.setDefaultEcho(false); + streamMode.setHangupType(HANGUP_PPPHARD); + serial.setPetsciiMode(true); + serial.setXON(true); + packetXOn = true; + if(baudRate != 1200) + { + baudRate=1200; + changeBaudRate(baudRate); + } + dcdActive = HIGH; + dcdInactive = LOW; + checkOpenConnections(); + break; + } + default: + break; + } +} + +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); +# ifdef INCLUDE_CMDRX16 + serial.prints("X16:"); +# endif + switch(altOpMode) + { + case OPMODE_NONE: + break; + case OPMODE_1650: + serial.prints("1650:"); + break; + case OPMODE_1660: + serial.prints("1660:"); + break; + case OPMODE_1670: + serial.prints("1670:"); + break; + } + 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(busyMode) + serial.prints("H1"); + 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_ACTIVE)||(dcdInactive == DEFAULT_DCD_ACTIVE)||(showAll)) + serial.printf("S46=%d",pinModeCoder(dcdActive,dcdInactive,DEFAULT_DCD_ACTIVE)); + if((pinDCD != DEFAULT_PIN_DCD)||(showAll)) + serial.printf("S47=%d",pinDCD); + if((ctsActive != DEFAULT_CTS_ACTIVE)||(ctsInactive == DEFAULT_CTS_ACTIVE)||(showAll)) + serial.printf("S48=%d",pinModeCoder(ctsActive,ctsInactive,DEFAULT_CTS_ACTIVE)); + if((pinCTS != DEFAULT_PIN_CTS)||(showAll)) + serial.printf("S49=%d",pinCTS); + if((rtsActive != DEFAULT_RTS_ACTIVE)||(rtsInactive == DEFAULT_RTS_ACTIVE)||(showAll)) + serial.printf("S50=%d",pinModeCoder(rtsActive,rtsInactive,DEFAULT_RTS_ACTIVE)); + if((pinRTS != DEFAULT_PIN_RTS)||(showAll)) + serial.printf("S51=%d",pinRTS); + if((riActive != DEFAULT_RI_ACTIVE)||(riInactive == DEFAULT_RI_ACTIVE)||(showAll)) + serial.printf("S52=%d",pinModeCoder(riActive,riInactive,DEFAULT_RI_ACTIVE)); + if((pinRI != DEFAULT_PIN_RI)||(showAll)) + serial.printf("S53=%d",pinRI); + if((dtrActive != DEFAULT_DTR_ACTIVE)||(dtrInactive == DEFAULT_DTR_ACTIVE)||(showAll)) + serial.printf("S54=%d",pinModeCoder(dtrActive,dtrInactive,DEFAULT_DTR_ACTIVE)); + if((pinDTR != DEFAULT_PIN_DTR)||(showAll)) + serial.printf("S55=%d",pinDTR); + if((dsrActive != DEFAULT_DSR_ACTIVE)||(dsrInactive == DEFAULT_DSR_ACTIVE)||(showAll)) + serial.printf("S56=%d",pinModeCoder(dsrActive,dsrInactive,DEFAULT_DSR_ACTIVE)); + 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((streamMode.getHangupType()!=HANGUP_NONE) ||(showAll)) + { + switch(streamMode.getHangupType()) + { + case HANGUP_PPPHARD: + serial.prints("S63=1"); + break; + case HANGUP_DTR: + serial.prints("S63=2"); + break; + case HANGUP_PDP: + serial.prints("S63=3"); + break; + default: + serial.prints("S63=0"); + break; + } + } + if((serial.isPetsciiMode())||(showAll)) + serial.prints(serial.isPetsciiMode() ? "&P1" : "&P0"); + if((logFileOpen) || showAll) + serial.prints((logFileOpen && !logFile2Uart) ? "&O1" : ((logFileOpen && logFile2Uart) ? "&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>921600)) + 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\r\n",wifiSSI.c_str()); + bool doconn = connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN); + debugPrintf("Done attempting connect to %s\r\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; + bool gopher = false; +#ifdef INCLUDE_FTP + FTPHost *ftpHost = 0; + if((strstr((char *)vbuf,"ftp:")==(char *)vbuf) + ||(strstr((char *)vbuf,"ftps:")==(char *)vbuf)) + { + ftpHost = makeFTPHost(true,ftpHost,vbuf,&req); + if((req == 0)||(ftpHost==0)) + return ZERROR; + } + else +#endif + if((strstr((char *)vbuf,"gopher:")==(char *)vbuf) + ||(strstr((char *)vbuf,"gophers:")==(char *)vbuf)) + gopher=true; + if(!parseWebUrl(vbuf,&hostIp,&req,&port,&doSSL)) + return ZERROR; + + if(cache) + { + if(!SPIFFS.exists(filename)) + { +#ifdef INCLUDE_FTP + if(ftpHost != 0) + { + if(!ftpHost->doGet(&SPIFFS,filename,req)) + { + delete ftpHost; + return ZERROR; + } + } + else +#endif + if(gopher) + { + if(!doGopherGet(hostIp, port, &SPIFFS, filename, req, doSSL)) + return ZERROR; + } + else + 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; + +#ifdef INCLUDE_FTP + if(ftpHost != 0) + { + c=ftpHost->doGetStream(req, &respLength); + if(c==null) + delete ftpHost; + } + else +#endif + if(gopher) + c = doGopherGetStream(hostIp, port, req, doSSL, &respLength); + else + 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); +#ifdef INCLUDE_FTP + if(ftpHost != 0) + delete ftpHost; + else +#endif + { + c->stop(); + delete c; + } + serial.prints(EOLN); + return res; + } + else + { + if(SPIFFS.exists(filename)) + SPIFFS.remove(filename); +#ifdef INCLUDE_FTP + if(ftpHost != 0) + { + if(!ftpHost->doGet(&SPIFFS,filename,req)) + { + delete ftpHost; + return ZERROR; + } + } + else +#endif + if(gopher) + { + if(!doGopherGet(hostIp, port, &SPIFFS, filename, req, doSSL)) + return ZERROR; + } + 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)||(in->available()>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; +} + +#ifdef INCLUDE_OTH_UPDATES +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 +# ifdef INCLUDE_CMDRX16 + char *updaterHost = "www.zimmers.net"; // changeme! +# else + char *updaterHost = "www.zimmers.net"; +# endif + int updaterPort = 80; +#endif +#ifdef INCLUDE_CMDRX16 + char *updaterPrefix = "/otherprojs/x16"; +#else +# ifdef ZIMODEM_ESP32 + char *updaterPrefix = "/otherprojs/guru2"; +# else + char *updaterPrefix = "/otherprojs/c64net2"; +# endif +#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; +} +#endif + +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)||((isNumber && (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=PhoneBookEntry::findPhonebookEntry(vval); + if(phb != null) + { + 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; + } + /* + 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); + if(strchr(dmodifiers,';')==0) + 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); + if(strchr(dmodifiers,';')==0) + 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(); + busyMode=false; + return ZIGNORE; + } + else + { + busyMode=false; + streamMode.switchTo(c); + checkOpenConnections(); + return ZOK; + } + break; + } + c=c->next; + } + busyMode=false; + 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(); + busyMode=false; + 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; + busyMode=false; + return ZOK; + } + else + if(isNumber && (vval == 0)) + { + if(current != 0) + { + if(lastServerClientId==current->id) + lastServerClientId=0; + delete current; + current = conns; + nextConn = conns; + busyMode=false; + return ZOK; + } + if(busyMode) + { + busyMode=false; + 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; + } + if(vval == 1) // enter busy mode ATH1 in command mode... + { + busyMode=true; + return ZOK; + } + return ZERROR; + } + return ZERROR; +} + +int ZCommand::getStatusRegister(const int snum, int crc8) +{ + switch(snum) + { + case 0: + return ringCounter; + case 2: + return (int)ECS[0]; + case 3: + return (int)CR[0]; + case 4: + return (int)LF[0]; + case 5: + return (int)BS; + case 40: + return packetSize; + case 41: + return autoStreamMode?1:0; + case 42: + return crc8; + case 43: + return tempBaud; + case 44: + return serialDelayMs; + case 45: + return binType; + case 46: + return pinModeCoder(dcdActive,dcdInactive,DEFAULT_DCD_ACTIVE); + case 47: + return pinDCD; + case 48: + return pinModeCoder(ctsActive,ctsInactive,DEFAULT_CTS_ACTIVE); + case 49: + return pinCTS; + case 50: + return pinModeCoder(rtsActive,rtsInactive,DEFAULT_RTS_ACTIVE); + case 51: + return pinRTS; + case 52: + return pinModeCoder(riActive,riInactive,DEFAULT_RI_ACTIVE); + case 53: + return pinRI; + case 54: + return pinModeCoder(dtrActive,dtrInactive,DEFAULT_DTR_ACTIVE); + case 55: + return pinDTR; + case 56: + return pinModeCoder(dsrActive,dsrInactive,DEFAULT_DSR_ACTIVE); + case 57: + return pinDSR; + case 60: + return (preserveListeners)?1:0; + case 61: + return (int)round(printMode.getTimeoutDelayMs()/1000); + case 62: + return telnetSupport ? 1 : 0; + case 63: + switch(streamMode.getHangupType()) + { + case HANGUP_NONE: return 0; + case HANGUP_PPPHARD: return 1; + case HANGUP_DTR: return 2; + case HANGUP_PDP: return 3; + } + default: + break; + } + return 0; +} + +ZResult ZCommand::setStatusRegister(const int snum, const int sval, int *crc8, const ZResult oldRes) +{ + ZResult result = oldRes; + 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_ACTIVE,DEFAULT_CTS_INACTIVE); + 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_ACTIVE,DEFAULT_RTS_INACTIVE); + 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_ACTIVE,DEFAULT_RI_INACTIVE); + if(pinSupport[pinRI]) + 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(pinRI,riInactive); + result=ZOK; + } + else + result=ZERROR; + break; + case 54: + pinModeDecoder(sval,&dtrActive,&dtrInactive,DEFAULT_DTR_ACTIVE,DEFAULT_DTR_INACTIVE); + 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_ACTIVE,DEFAULT_DSR_INACTIVE); + 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); + break; + case 63: + streamMode.setHangupType(HANGUP_NONE); + if(sval == 1) + streamMode.setHangupType(HANGUP_PPPHARD); + else + if(sval == 2) + streamMode.setHangupType(HANGUP_DTR); + else + if(sval == 3) + streamMode.setHangupType(HANGUP_PDP); + break; + default: + break; + } + return result; +} + +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) + { + processPlusPlusPlus(c); + + 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()); + else + debugPrintf("Command: %s\r\n",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; + } + if((strchr("d", lastCmd) != null) + &&(isNumber) + && (c == ';')) // when at the end of the number, overrides the auto-stream-modem var + dmodifiers += ";"; + } + else + while((index='a')&&(lc(sbuf[index])<='z'))) + &&(sbuf[index]!='&') + &&(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); + else + logPrintfln("Proc: %c %lu ''",lastCmd,vval); + /* + * We have cmd and args, time to DO! + */ + switch(lastCmd) + { + case 'z': + result = doResetCommand(false); + break; + case 'n': + doNoListenCommand(vval,vbuf,vlen,isNumber); + 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(altOpMode != OPMODE_NONE) + { + streamMode.setDefaultEcho((isNumber && vval == 0)); + break; + } + else + if((!isNumber)||(vval>=FCT_INVALID)) + result=ZERROR; + else + { +#ifndef INCLUDE_CMDRX16 + packetXOn = true; +#endif + 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': + { + char *eq=strchr((char *)vbuf,'='); + if(eq == null) + { + int snum = vval; + eq=strchr((char *)vbuf,'?'); + if((eq != null) && (!isNumber)) + { + *eq = 0; + snum = atoi((char *)vbuf); + } + if((snum == 0)&&(vbuf[0]!='0')) + result=ZERROR; + else + { + int res = getStatusRegister(snum,crc8); + serial.printf("%d%s",res,EOLN.c_str()); + result = ZOK; + } + } + else + if((vlen<3)||(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 + result = setStatusRegister(snum,sval,&crc8,result); + } + } + 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,false); + } + } +# ifdef INCLUDE_HOSTCM + else + if((strstr((const char *)vbuf,"hostcm")==(char *)vbuf)) + { + result = ZOK; + hostcmMode.switchTo(); + } +# endif +# ifdef INCLUDE_COMET64 + else + if((strstr((const char *)vbuf,"comet64")==(char *)vbuf) + &&(SD.cardType() != CARD_NONE)) + { + result = ZOK; + comet64Mode.switchTo(); + } +# endif +#endif +# ifdef INCLUDE_CBMMODEM + else + if(strstr((const char *)vbuf,"1650")==(char *)vbuf) + { + altOpMode=OPMODE_1650; + setAltOpModeAdjustments(); + result = ZOK; + } + else + if(strstr((const char *)vbuf,"1660")==(char *)vbuf) + { + altOpMode=OPMODE_1660; + setAltOpModeAdjustments(); + result = ZOK; + } + else + if(strstr((const char *)vbuf,"1670")==(char *)vbuf) + { + altOpMode=OPMODE_1670; + setAltOpModeAdjustments(); + result = ZOK; + } +# 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; // left for x16 + 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': + if(!reSaveConfig(3)) + result = ZERROR; + break; + case 'f': + if(vval == 86) + { + loadConfig(); + zclock.reset(); + result = SPIFFS.format() ? ZOK : ZERROR; + if(!reSaveConfig(10)) + result = ZERROR; + } + 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=""; + wifiPW=""; + hostname=""; + staticIP = null; + staticDNS = null; + staticGW = null; + staticSN = null; + delay(500); + zclock.reset(); + result=doResetCommand(true); + if(altOpMode == OPMODE_NONE) + 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': + logFile2Uart=false; + 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) + logFile2Uart=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; +# ifdef INCLUDE_OTH_UPDATES + case 'u': + result=doUpdateFirmware(vval,vbuf,vlen,isNumber); + break; +# endif + 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)||(autoStreamMode)) ? 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 + 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,"tot=%dk heap=%dk fsize=%dk",(ESP.getFlashChipSize()/1024),(ESP.getFreeHeap()/1024),totalSPIFFSSize/1024); +#else + sprintf(s,"tot=%dk heap=%dk fsize=%dk",(ESP.getFlashChipRealSize()/1024),(ESP.getSketchSize()/1024),totalSPIFFSSize/1024); +#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;i=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::reSendLastPacket(WiFiClientNode *conn, uint8_t which) +{ + if((conn == NULL) + ||(which>2)) + { + 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 *cbuf = conn->lastPacket[which].buf; + uint8_t num = conn->lastPacket[which].num; + uint8_t *buf = (uint8_t *)malloc(bufLen); + memcpy(buf,cbuf,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,buf,bufLen,num); + } +} + +bool ZCommand::checkPlusPlusPlusDisconnect() +{ + if(checkPlusPlusPlusEscape()) + { + 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"); + if(longResponses && (!autoStreamMode)) + 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->isAnswered())) // being unanswered is a server thing that must be respected + //&& (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"); + if(longResponses && (!autoStreamMode)) + { + serial.prints(" "); + serial.printi(nextConn->id); + } + serial.prints(EOLN); + serial.flush(); + } + if(serial.getFlowControlType() == FCT_MANUAL) + { + checkOpenConnections(); + return; // don't handle more than one socket! + } + } + 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) +{ + if((altOpMode == OPMODE_1650)||(altOpMode == OPMODE_1660)) + return; + 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); +} + +bool ZCommand::acceptNewConnection() +{ + WiFiServerNode *serv = servs; + while(serv != null) + { + if(serv->hasClient()) + { + WiFiClient newClient = serv->server->available(); + if(newClient.connected()) + { + if(busyMode) + { + newClient.write((uint8_t *)busyMsg.c_str(), busyMsg.length()); + newClient.flush(); + delay(100); // yes, i know, but seriously.... + newClient.stop(); + serv=serv->next; + continue; + } + 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>0?(futureRings+1) * 2:0); + setCharArray(&(newClientNode->delimiters),serv->delimiters); + setCharArray(&(newClientNode->maskOuts),serv->maskOuts); + setCharArray(&(newClientNode->stateMachine),serv->stateMachine); + newClientNode->machineState = newClientNode->stateMachine; + s_pinWrite(pinRI,riActive); + if(futureRings>0) + { + 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; + } + return currMode != this; +} + +void ZCommand::checkPulseDial() +{ +#ifdef INCLUDE_CBMMODEM + if(pinSupport[pinOTH]) + { + /* + * One 'pulse' is: + * 1->0 over 50 ms + * 0->1 over 50ms + * (repeat) + * if 1->0 lasts 300-320ms, it is between numbers + */ + int bit = digitalRead(pinOTH); + if(bit != lastPulseState) + { + if(lastPulseTimeMs == 0) + lastPulseTimeMs = millis(); + else + if(bit == othActive) + { + unsigned short diff = (millis()-lastPulseTimeMs); + if((diff > 15) && (diff < 60)) + { + if(pulseWork < 10) + pulseWork++; + else + { + logPrintf("\n\rP.D.: ERROR -- OVERPULSE!\n\r"); + pulseBuf = ""; // error out + pulseWork = 0; + return; + } + } + else + { + logPrintf("\n\rP.D.: ERROR (%u ms)!\n\r",(unsigned int)diff); + pulseBuf = ""; // error out + lastPulseTimeMs = 0; + pulseWork = 0; + return; + } + } + else + if(bit == othInactive) + { + unsigned short diff = (millis()-lastPulseTimeMs); + if((diff > 225) + && (diff < 350) + && (pulseWork > 0)) + { + char nums[2]; + if(pulseWork > 9) + sprintf(nums,"0"); + else + sprintf(nums,"%u",pulseWork); + debugPrintf("\n\rP.D.: got digit: %u\n\r",(unsigned int)pulseWork); + pulseBuf += nums; + pulseWork=0; + } + else + if((diff > 15) + && (diff < 60)) + {} // between bits + else + logPrintf("\n\rP.D.: Ignoring FAIL (%u)\n\r",(unsigned int)diff); + //else if this happens too quickly, do nothing + } + lastPulseState = bit; + lastPulseTimeMs = millis(); + } + else + if((lastPulseTimeMs != 0) + &&((millis() - lastPulseTimeMs) > 350)) + { + if(pulseWork > 0) + { + char nums[2]; + if(pulseWork > 9) + sprintf(nums,"0"); + else + sprintf(nums,"%u",pulseWork); + debugPrintf("\n\rP.D.: got digit: %u\n\r",(unsigned int)pulseWork); + pulseBuf += nums; + pulseWork = 0; + lastPulseTimeMs = millis(); + if(pulseBuf.length() > 2) //2 digits is minimum to prevent false dials + { + unsigned long vval = atoi(pulseBuf.c_str()); + PhoneBookEntry *pb=PhoneBookEntry::findPhonebookEntry(vval); + if(pb != null) + { + logPrintf("\n\rP.D.: Dialing: %lu\n\r",vval); + doDialStreamCommand(vval, (uint8_t *)pulseBuf.c_str(), pulseBuf.length(), true, ""); + pulseBuf = ""; + lastPulseTimeMs = 0; + } + } + } + else + { + pulseBuf = ""; + lastPulseTimeMs = 0; + } + } + } +#endif +} + + +void ZCommand::serialIncoming() +{ + bool crReceived=readSerialStream(); + 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() +{ + checkPlusPlusPlusDisconnect(); + if(acceptNewConnection()) + { + checkBaudChange(); + return; + } + if(serial.isSerialOut()) + { + sendNextPacket(); + serialOutDeque(); + } +#ifdef INCLUDE_CBMMODEM + checkPulseDial(); +#endif + checkBaudChange(); + logFileLoop(); +} diff --git a/zimodem/zconfigmode.h b/zimodem/zconfigmode.h index e018d32..82d785d 100644 --- a/zimodem/zconfigmode.h +++ b/zimodem/zconfigmode.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/zimodem/zconfigmode.ino b/zimodem/zconfigmode.ino index b8cc0a3..a9c76d5 100644 --- a/zimodem/zconfigmode.ino +++ b/zimodem/zconfigmode.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ void ZConfig::switchTo() { + debugPrintf("\r\nMode:Config\r\n"); currMode=&configMode; serial.setFlowControlType(commandMode.serial.getFlowControlType()); serial.setPetsciiMode(commandMode.serial.isPetsciiMode()); @@ -23,7 +24,7 @@ void ZConfig::switchTo() newListen=commandMode.preserveListeners; commandMode.doEcho=true; serverSpec.port=6502; - serverSpec.flagsBitmap=commandMode.getConfigFlagBitmap(); + serverSpec.flagsBitmap=commandMode.getConfigFlagBitmap() & (~FLAG_ECHO); if(servs) serverSpec = *servs; serial.setXON(true); @@ -36,12 +37,15 @@ void ZConfig::switchTo() lastOptions=""; settingsChanged=false; lastNumNetworks=0; + 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); } void ZConfig::serialIncoming() { bool crReceived=commandMode.readSerialStream(); - commandMode.clearPlusProgress(); // re-check the plus-escape mode if(crReceived) { doModeCommand(); @@ -50,6 +54,7 @@ void ZConfig::serialIncoming() void ZConfig::switchBackToCommandMode() { + debugPrintf("\r\nMode:Command\r\n"); commandMode.doEcho=savedEcho; currMode = &commandMode; } @@ -214,8 +219,10 @@ void ZConfig::doModeCommand() commandMode.updateAutoAnswer(); } } - commandMode.reSaveConfig(); - serial.printf("%sSettings saved.%s",EOLNC,EOLNC); + if(!commandMode.reSaveConfig(3)) + serial.printf("%sFailed to save Settings.%s",EOLNC,EOLNC); + else + serial.printf("%sSettings saved.%s",EOLNC,EOLNC); commandMode.showInitMessage(); WiFiServerNode::SaveWiFiServers(); switchBackToCommandMode(); @@ -223,6 +230,7 @@ void ZConfig::doModeCommand() } else showMenu=true; + break; } case ZCFGMENU_NUM: { @@ -243,7 +251,9 @@ void ZConfig::doModeCommand() { lastNumber = atol((char *)cmd.c_str()); lastAddress = ""; - ConnSettings flags(commandMode.getConfigFlagBitmap()); + int flagsBitmap = commandMode.getConfigFlagBitmap(); + flagsBitmap = flagsBitmap & (~FLAG_ECHO); + ConnSettings flags(flagsBitmap); lastOptions = flags.getFlagString(); lastNotes = ""; currState=ZCFGMENU_ADDRESS; @@ -276,24 +286,20 @@ void ZConfig::doModeCommand() currState=ZCFGMENU_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) + { + 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 @@ -723,9 +729,9 @@ void ZConfig::loop() { PhoneBookEntry *lastEntry = PhoneBookEntry::findPhonebookEntry(lastNumber); if(lastEntry == null) - serial.printf("%sEnter a new hostname:port%s: ",EOLNC,EOLNC); + serial.printf("%sEnter hostname:port, or user:pass@hostname:port for SSH%s: ",EOLNC,EOLNC); else - serial.printf("%sModify hostname:port, or enter DELETE (%s)%s: ",EOLNC,lastAddress.c_str(),EOLNC); + serial.printf("%sModify address, or enter DELETE (%s)%s: ",EOLNC,lastAddress.c_str(),EOLNC); break; } case ZCFGMENU_OPTIONS: @@ -736,6 +742,7 @@ void ZConfig::loop() 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; } @@ -859,12 +866,12 @@ void ZConfig::loop() } case ZCFGMENU_WICONFIRM: { - serial.printf("%sYour setting changed. Save (y/N)?",EOLNC); + serial.printf("%sYour settings changed. Save (y/N)?",EOLNC); break; } } } - if(commandMode.checkPlusEscape()) + if(checkPlusPlusPlusEscape()) { switchBackToCommandMode(); } @@ -873,5 +880,6 @@ void ZConfig::loop() { serialOutDeque(); } + logFileLoop(); } diff --git a/zimodem/zhostcmmode.h b/zimodem/zhostcmmode.h index ed64902..f1b41eb 100644 --- a/zimodem/zhostcmmode.h +++ b/zimodem/zhostcmmode.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/zimodem/zhostcmmode.ino b/zimodem/zhostcmmode.ino index e410466..868aec3 100644 --- a/zimodem/zhostcmmode.ino +++ b/zimodem/zhostcmmode.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #ifdef INCLUDE_HOSTCM void ZHostCMMode::switchBackToCommandMode() { + debugPrintf("\r\nMode:Command\r\n"); if(proto != 0) delete proto; proto = 0; @@ -25,6 +26,7 @@ void ZHostCMMode::switchBackToCommandMode() void ZHostCMMode::switchTo() { + debugPrintf("\r\nMode:HostCM\r\n"); currMode=&hostcmMode; if(proto == 0) proto = new HostCM(&SD); @@ -41,6 +43,7 @@ void ZHostCMMode::loop() serialOutDeque(); if((proto != 0) && (proto->isAborted())) switchBackToCommandMode(); + logFileLoop(); } #endif diff --git a/zimodem/zimodem.ino b/zimodem/zimodem.ino index e8aa105..681fcb8 100644 --- a/zimodem/zimodem.ino +++ b/zimodem/zimodem.ino @@ -1,560 +1,702 @@ -/* - 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 "3.7.0" -const char compile_date[] = __DATE__ " " __TIME__; -#define DEFAULT_NO_DELAY true -#define null 0 -#define INCLUDE_IRCC true -//#define INCLUDE_SLIP 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_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 // do this for special SP9000 modems only -#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_RTSCTS -# define RS232_INVERTED 1 -# define debugPrintf doNothing -# define preEOLN(...) -# define echoEOLN(...) serial.prints(EOLN) -#endif - -#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 5000 -#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 bool browseEnabled = false; -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 = 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) - 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.changeBaudRate(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 %d.\n",(int)conf); - dequeSize=1+(baudRate/INTERNAL_FLOW_CONTROL_DIV); - debugPrintf("Deque constant now: %d\n",dequeSize); -#ifdef ZIMODEM_ESP32 - HWSerial.changeConfig(conf); -#else - HWSerial.begin(baudRate, conf); //Change baud rate -#endif - 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(); +/* + Copyright 2016-2025 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.2" +const char compile_date[] = __DATE__ " " __TIME__; +#define DEFAULT_NO_DELAY true +#define null 0 + + +#define INCLUDE_IRCC true +#define INCLUDE_SD_SHELL true /* ESP32 only, requires SPI SD card interface */ +#define INCLUDE_CBMMODEM true // ESP32 only, 1650, 1660, 1670, and Pulse Dialing support +#define INCLUDE_PING true +#define INCLUDE_SSH true // ESP32 only, adds SSH client +#define INCLUDE_HOSTCM true // requires sd shell +#define INCLUDE_FTP true +#define INCLUDE_COMET64 true // requires sd shell +#define INCLUDE_OTH_UPDATES true // comment out if you make incompatible firmware +//#define INCLUDE_SLIP true // enable this when it is working. it is not working. +//#define SUPPORT_LED_PINS true // enable if you have the spare gpio pins and leds +//#define INCLUDE_CMDRX16 true // enable this if you are David or Kevin +//#define USE_DEVUPDATER true // only enable this if your name is Bo + +// Figure out whether we are building for ESP8266 or ESP32 +#ifdef ARDUINO_ESP32S3_DEV || ARDUINO_ESP32C3_DEV +# define ZIMODEM_ESP32 +#elif 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 +# undef INCLUDE_SSH +# undef INCLUDE_SD_SHELL +# undef INCLUDE_CBMMODEM +#endif + +#ifdef SUPPORT_LED_PINS +# ifdef GPIO_NUM_0 +# define DEFAULT_PIN_AA GPIO_NUM_35 +# define DEFAULT_PIN_HS GPIO_NUM_34 +# define DEFAULT_PIN_WIFI GPIO_NUM_26 +# else +# define DEFAULT_PIN_AA 35 +# define DEFAULT_PIN_HS 34 +# define DEFAULT_PIN_WIFI 26 +# 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 + +#define DEFAULT_BAUD_RATE 1200 +#define DEFAULT_SERIAL_CONFIG SERIAL_8N1 +#define RX_BUFFER_SIZE 4096 +/* + * Unused pins on WROOM32: + * 2, 20, 21, 22. 36,39 (sensor) + */ + +#ifdef ZIMODEM_ESP32 +# define PIN_FACTORY_RESET GPIO_NUM_0 +# define DEFAULT_FCT FCT_DISABLED +# ifdef INCLUDE_CMDRX16 /* Configuration for the Commander X16 I/O Card */ +# undef INCLUDE_SD_SHELL +# undef DEFAULT_FCT +# define DEFAULT_FCT FCT_RTSCTS +# undef DEFAULT_BAUD_RATE +# define DEFAULT_BAUD_RATE 115200 +# define DEFAULT_PIN_DCD GPIO_NUM_22 +# define DEFAULT_PIN_CTS GPIO_NUM_19 +# define DEFAULT_PIN_RTS GPIO_NUM_21 +# define DEFAULT_PIN_RI GPIO_NUM_18 +# define DEFAULT_PIN_DSR GPIO_NUM_23 +# define DEFAULT_PIN_DTR GPIO_NUM_25 +//# define DEFAULT_PIN_OPA 26 +# define DEFAULT_PIN_OPB GPIO_NUM_27 +# define DEFAULT_PIN_OTH GPIO_NUM_26 // stream/command mode flag +# define DEFAULT_PIN_TXD GPIO_NUM_32 +# define DEFAULT_PIN_RXD GPIO_NUM_33 +# define DEFAULT_PIN_SND GPIO_NUM_4 +# elif defined(ARDUINO_ESP32C3_DEV) /* Configuration for the Esp32C3 4MB Dev Board */ +# undef INCLUDE_SD_SHELL +# undef PIN_FACTORY_RESET +# define DEFAULT_PIN_DCD GPIO_NUM_8 +# define DEFAULT_PIN_CTS GPIO_NUM_3 // espdev rts pin +# define DEFAULT_PIN_RTS GPIO_NUM_2 // espdev cts pin +# define DEFAULT_PIN_RI GPIO_NUM_1 +# define DEFAULT_PIN_DSR GPIO_NUM_4 +# define DEFAULT_PIN_SND GPIO_NUM_6 +# define DEFAULT_PIN_OTH GPIO_NUM_7 // pulse pin +# define DEFAULT_PIN_DTR GPIO_NUM_5 +# elif defined(ARDUINO_ESP32S3_DEV) /* Configuration for the Esp32S3 16MB Dev Board */ +# define DEFAULT_PIN_DCD GPIO_NUM_14 +# define DEFAULT_PIN_CTS GPIO_NUM_19 // espdev rts pin +# define DEFAULT_PIN_RTS GPIO_NUM_20 // espdev cts pin +# define DEFAULT_PIN_RI GPIO_NUM_10 +# define DEFAULT_PIN_DSR GPIO_NUM_12 +# define DEFAULT_PIN_SND GPIO_NUM_11 +# define DEFAULT_PIN_OTH GPIO_NUM_46 // pulse pin +# define DEFAULT_PIN_DTR GPIO_NUM_13 +# define DEFAULT_PIN_TXD GPIO_NUM_21 +# define DEFAULT_PIN_RXD GPIO_NUM_20 +# else /* Configuration for standard ESP32 4 & 8MB boards */ +# 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 // pulse pin +# define DEFAULT_PIN_DTR GPIO_NUM_27 +# endif +# define debugPrintf DBSerial.printf +# 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(...) +//# define preEOLN serial.prints +# define echoEOLN(...) serial.prints(EOLN) +//# define echoEOLN serial.write +//# define HARD_DCD_HIGH 1 +//# define HARD_DCD_LOW 1 +#else // ESP-8266, e.g. ESP-01, ESP-12E +# 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 +# define DEFAULT_PIN_DCD 2 +# define DEFAULT_PIN_OTH -1 // pulse pin +# define DEFAULT_FCT FCT_DISABLED +# define debugPrintf doNothing +# define preEOLN(...) +# define echoEOLN(...) serial.prints(EOLN) +#endif + +# define DEFAULT_DCD_ACTIVE LOW +# define DEFAULT_DCD_INACTIVE HIGH +# define DEFAULT_CTS_ACTIVE LOW +# define DEFAULT_CTS_INACTIVE HIGH +# define DEFAULT_RTS_ACTIVE LOW +# define DEFAULT_RTS_INACTIVE HIGH +# define DEFAULT_RI_ACTIVE LOW +# define DEFAULT_RI_INACTIVE HIGH +# define DEFAULT_DSR_ACTIVE LOW +# define DEFAULT_DSR_INACTIVE HIGH +# define DEFAULT_DTR_ACTIVE LOW +# define DEFAULT_DTR_INACTIVE HIGH +# define DEFAULT_OTH_ACTIVE LOW +# define DEFAULT_OTH_INACTIVE HIGH + +#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 +# ifdef INCLUDE_COMET64 +# include "zcomet64mode.h" +# endif +# include "proto_xmodem.h" +# include "proto_zmodem.h" +# include "proto_punter.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 int pinCache[MAX_PIN_NO]; +static String termType = DEFAULT_TERMTYPE; +static String busyMsg = DEFAULT_BUSYMSG; +static bool debugUart = false; + +static OpModes altOpMode = OPMODE_NONE; +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 +# ifdef INCLUDE_COMET64 + static ZComet64Mode comet64Mode; +# 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 unsigned int plussesInARow = 0; +static unsigned long lastInputTimeMs = 0; +static int dcdStatus = DEFAULT_DCD_INACTIVE; +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 pinOTH = DEFAULT_PIN_OTH; +static int pinRI = DEFAULT_PIN_RI; +static int dcdActive = DEFAULT_DCD_ACTIVE; +static int dcdInactive = DEFAULT_DCD_INACTIVE; +static int ctsActive = DEFAULT_CTS_ACTIVE; +static int ctsInactive = DEFAULT_CTS_INACTIVE; +static int rtsActive = DEFAULT_RTS_ACTIVE; +static int rtsInactive = DEFAULT_RTS_INACTIVE; +static int riActive = DEFAULT_RI_ACTIVE; +static int riInactive = DEFAULT_RI_INACTIVE; +static int dtrActive = DEFAULT_DTR_ACTIVE; +static int dtrInactive = DEFAULT_DTR_INACTIVE; +static int dsrActive = DEFAULT_DSR_ACTIVE; +static int dsrInactive = DEFAULT_DSR_INACTIVE; +static int othActive = DEFAULT_OTH_ACTIVE; +static int othInactive = DEFAULT_OTH_INACTIVE; + +static int getDefaultCtsPin() +{ + return DEFAULT_PIN_CTS; +} + +static void doNothing(const char* format, ...) +{ +} + +static void s_pinWrite(uint8_t pinNo, uint8_t value) +{ + if(pinSupport[pinNo]) + { + pinCache[pinNo] = value; + 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 + if(WiFi.status() == WL_CONNECTED) + debugPrintf("Connected to %s with IP %s.\r\n",ssid,WiFi.localIP().toString().c_str()); + 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.",baudRate); + dequeSize=1+(baudRate/INTERNAL_FLOW_CONTROL_DIV); + debugPrintf("Baud %d, Deque constant now: %d\r\n",baudRate,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.\r\n",(int)conf); + dequeSize=1+(baudRate/INTERNAL_FLOW_CONTROL_DIV); + debugPrintf("Deque constant now: %d\r\n",dequeSize); +# ifdef DEFAULT_PIN_RXD + HWSerial.begin(baudRate, conf, DEFAULT_PIN_RXD, DEFAULT_PIN_TXD); +# else + HWSerial.begin(baudRate, conf); //Change baud rate +# endif + debugPrintf("Config changed.\r\n"); +} + +static int checkOpenConnections() +{ + int num=WiFiClientNode::getNumOpenWiFiConnections(); + if(num == 0) + { + if((dcdStatus == dcdActive) + &&(dcdStatus != dcdInactive)) + { + 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)) + { + dcdStatus = dcdActive; + s_pinWrite(pinDCD,dcdStatus); + if((tempBaud > 0) && (baudState == BS_NORMAL)) + baudState = BS_SWITCH_TEMP_NEXT; + } + } + return num; +} + +static int processPlusPlusPlus(uint8_t c) +{ + if(c<0) + return 0; + int plusOut = 0; + if(c == commandMode.EC) + { + bool timeout = (millis()-lastInputTimeMs)>900; + if(plussesInARow==0) + { + if(timeout) + plussesInARow=1; // it begins! + // else got a +, but too quick after last char, so keep at 0 + } + else + if(!timeout) // quick PLUS + { + if(plussesInARow<3) + plussesInARow++; + else + { + plusOut = plussesInARow; // sur-plus, so reject + plussesInARow=0; // spamming plusses clears! + } + } + else // plus long after timeout + { + plusOut = plussesInARow; + plussesInARow=1; + } + } + else + if(plussesInARow>0) + { + plusOut = plussesInARow; + plussesInARow=0; + } + lastInputTimeMs = millis(); + return plusOut; +} + +static bool checkPlusPlusPlusEscape() +{ + if((plussesInARow == 3) && ((millis()-lastInputTimeMs)>900)) + { + plussesInARow = 0; + return true; + } + return false; +} + +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 +#ifdef DEFAULT_PIN_OPB + static bool OPB_stat=0; + pinMode(DEFAULT_PIN_OPB, INPUT); + OPB_stat=digitalRead(DEFAULT_PIN_OPB); +#endif + + debugPrintf("Zimodem %s firmware starting initialization\r\n",ZIMODEM_VERSION); + initSDShell(); + currMode = &commandMode; + if(!SPIFFS.begin()) + { + SPIFFS.format(); + SPIFFS.begin(); + debugPrintf("SPIFFS Formatted.\r\n"); + } +# ifdef DEFAULT_PIN_RXD + HWSerial.begin(DEFAULT_BAUD_RATE, DEFAULT_SERIAL_CONFIG, DEFAULT_PIN_RXD, DEFAULT_PIN_TXD); +# else + HWSerial.begin(DEFAULT_BAUD_RATE, DEFAULT_SERIAL_CONFIG); //Start Serial +# endif + HWSerial.setRxBufferSize(RX_BUFFER_SIZE); + 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\r\n",wifiSSI.c_str()); + unsigned long oldReconnectDelay = nextReconnectDelay; + if(!connectWifi(wifiSSI.c_str(),wifiPW.c_str(),staticIP,staticDNS,staticGW,staticSN)) + debugPrintf("Unable to reconnect to %s.\r\n",wifiSSI.c_str()); + nextReconnectDelay = oldReconnectDelay * 2; + if(nextReconnectDelay > MAX_RECONNECT_DELAY) + nextReconnectDelay = DEFAULT_RECONNECT_DELAY; + } + } +} + +void checkFactoryReset() +{ +#if defined(ZIMODEM_ESP32) && defined(PIN_FACTORY_RESET) + 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"); + SPIFFS.remove("/znick.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=""; + wifiPW=""; + hostname=""; + staticIP = null; + staticDNS = null; + staticGW = null; + staticSN = null; + 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/zimodem/zircmode.h b/zimodem/zircmode.h index 95dc23d..e2cfffd 100644 --- a/zimodem/zircmode.h +++ b/zimodem/zircmode.h @@ -1,11 +1,22 @@ +#ifdef INCLUDE_IRCC /* - * zircmode.h - * - * Created on: May 18, 2022 - * Author: Bo Zimmerman - */ + Copyright 2022-2025 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 const char *NICK_FILE = "/znick.txt"; -#ifdef INCLUDE_IRCC class ZIRCMode: public ZMode { private: @@ -15,10 +26,12 @@ class ZIRCMode: public ZMode String EOLN; const char *EOLNC; WiFiClientNode *current = null; + bool debugRaw; unsigned long lastNumber; unsigned long timeout=0; String buf; String nick; + String listFilter = ""; String lastAddress; String lastOptions; String lastNotes; diff --git a/zimodem/zircmode.ino b/zimodem/zircmode.ino index ba0fc1e..f10e0ea 100644 --- a/zimodem/zircmode.ino +++ b/zimodem/zircmode.ino @@ -1,13 +1,24 @@ -/* - * zircmode.ino - * - * Created on: May 18, 2022 - * Author: Bo Zimmerman - */ #ifdef INCLUDE_IRCC +/* + Copyright 2022-2025 Bo Zimmerman, Steve Gibson + + 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. +*/ //https://github.com/bl4de/irc-client/blob/master/irc_client.py void ZIRCMode::switchBackToCommandMode() { + debugPrintf("\r\nMode:Command\r\n"); + serial.println("Back in command mode."); if(current != null) { delete current; @@ -18,6 +29,7 @@ void ZIRCMode::switchBackToCommandMode() void ZIRCMode::switchTo() { + debugPrintf("\r\nMode:IRC\r\n"); currMode=&ircMode; savedEcho=commandMode.doEcho; commandMode.doEcho=true; @@ -31,13 +43,27 @@ void ZIRCMode::switchTo() lastAddress=""; lastOptions=""; channelName=""; + debugRaw = false; joinReceived=false; if(nick.length()==0) { - randomSeed(millis()); - char tempNick[50]; - sprintf(tempNick,"ChangeMe#%ld",random(1,999)); - nick = tempNick; + if(SPIFFS.exists(NICK_FILE)) + { + File f = SPIFFS.open(NICK_FILE, "r"); + while(f.available()>0) + { + char c=f.read(); + if((c != '\n') && (c>0)) + nick += c; + } + } + if(nick.length()==0) + { + randomSeed(millis()); + char tempNick[50]; + sprintf(tempNick,"ChangeMe#%ld",random(1,999)); + nick = tempNick; + } } } @@ -89,7 +115,7 @@ void ZIRCMode::doIRCCommand() char vbuf[address.length()+1]; strcpy(vbuf,pb->address); char *colon=strstr((char *)vbuf,":"); - int port=6666; + int port=6667; if(colon != null) { (*colon)=0; @@ -169,15 +195,20 @@ void ZIRCMode::doIRCCommand() currState=ZIRCMENU_NOTES; // just keep old values else { - boolean fail = cmd.indexOf(',') >= 0; + bool 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) + { nick=cmd; + File f = SPIFFS.open(NICK_FILE, "w"); + f.printf("%s",nick.c_str()); + f.close(); + } currState=ZIRCMENU_MAIN; showMenu=true; // re-show the menu break; @@ -261,29 +297,126 @@ void ZIRCMode::doIRCCommand() { String lccmd = cmd; lccmd.toLowerCase(); + // index the first and second arguments, space/tab delimited + int firstArgStart=0; + while((cmd.length()>firstArgStart)&&(cmd[firstArgStart]!=' ')&&(cmd[firstArgStart]!=9)) + firstArgStart++; + while((cmd.length()>firstArgStart) + &&((cmd[firstArgStart]==' ')||(cmd[firstArgStart]==9))) + firstArgStart++; + int nextArgStart = firstArgStart; + while((cmd.length()>nextArgStart)&&(cmd[nextArgStart]!=' ')&&(cmd[nextArgStart]!=9)) + nextArgStart++; + int firstArgEnd=nextArgStart; + while((cmd.length()>nextArgStart) + &&((cmd[nextArgStart]==' ')||(cmd[nextArgStart]==9))) + nextArgStart++; if(lccmd.startsWith("/join ")) { - int cs=5; - while((cmd.length()0 && joinReceived) - { - serial.println("* Already in "+channelName+": Not Yet Implemented"); - // we are already joined somewhere - } - else + channelName = cmd.substring(firstArgStart); + if(current != null) + current->print("JOIN :"+channelName+"\r\n"); + serial.println("* Now talking in: " + channelName); + } + else + serial.println("* A channel name is required *"); + } + else + if(lccmd.startsWith("/list")) + { + if(firstArgStart < cmd.length()) + { + listFilter = cmd.substring(firstArgStart); + listFilter.toLowerCase(); + if(current != null) + current->print("LIST\r\n"); + } + else + serial.println("* A channel name filter is required. Trust me. *"); + } + else + if (lccmd.startsWith("/part ")) + { + String reasonToLeave = "Leaving..."; + String channelToLeave = ""; + if(nextArgStart < cmd.length()) + { + channelToLeave = cmd.substring(firstArgStart,firstArgEnd); + reasonToLeave = cmd.substring(nextArgStart); + } + else + if(firstArgStart < cmd.length()) + channelToLeave = cmd.substring(firstArgStart); + if(firstArgStart < cmd.length()) + { + if(current != null) + current->print("PART " + channelToLeave + " :" + reasonToLeave + "\r\n"); + if(channelToLeave == channelName) { - channelName = cmd.substring(cs); - if(current != null) - current->print("JOIN "+channelName+"\r\n"); + serial.println("* You will need to /switch or /join a new channel to talk *"); + channelName = ""; } } + else + serial.println("* You must specify the channel to part/leave *"); + } + else + if (lccmd.startsWith("/whois ")) // WHOIS - get user info + { + if((current != null) + &&(nextArgStart < cmd.length())) + { + current->print("WHOIS " + cmd.substring(firstArgStart) + "\r\n"); + } + else + serial.println("* You must specify who to whois *"); + } + else + if (lccmd.startsWith("/switch ")) // Switch to a new Channel to talk on + { + if(firstArgStart < cmd.length()) + { + if(current != null) + channelName = cmd.substring(firstArgStart); + serial.println("* Now talking in: " + channelName); + } else serial.println("* A channel name is required *"); } else + if (lccmd.startsWith("/debug")) // Toggle debug mode to receive all RAW messages + { + if (debugRaw) + { + debugRaw = false; + if(current != null) + serial.println("* Debug OFF - no longer receive RAW IRC messages"); + } + else + { + debugRaw = true; + if(current != null) + serial.println("* Debug ON - receiving RAW IRC messages"); + } + } + else + if(lccmd.startsWith("/msg ")) // Send "private message" to user + { + if((firstArgStart < cmd.length()) + &&(nextArgStart < cmd.length())) + { + String sendToNick = cmd.substring(firstArgStart,firstArgEnd); + String msgToSend = cmd.substring(nextArgStart); + if(current != null) + current->print("PRIVMSG " + sendToNick + " :" + msgToSend + "\r\n"); + //current->printf("PRIVMSG %s :%s\r\n",sendToNick.c_str(),msgToSend.c_str()); + } + else + serial.println("* A nickname and message is required*"); + } + else if(lccmd.startsWith("/quit")) { if(current != null) @@ -291,7 +424,6 @@ void ZIRCMode::doIRCCommand() current->print("QUIT Good bye!\r\n"); current->flush(); delay(1000); - serial.println("Returning to command mode."); current->markForDisconnect(); delete current; current = null; @@ -308,7 +440,10 @@ void ZIRCMode::doIRCCommand() if((current != null) &&(joinReceived)) { - current->print("PRIVMSG "+channelName+": "+cmd); + if(channelName.length() == 0) + serial.println("* You must /switch or /join a channel to talk. *"); + else + current->printf("PRIVMSG %s :%s\r\n",channelName.c_str(),cmd.c_str()); } break; } @@ -378,9 +513,7 @@ void ZIRCMode::loopMenuMode() { showMenu=true; // keep coming back here, over and over and over if((current==null)||(!current->isConnected())) - { switchBackToCommandMode(); - } else { String cmd; @@ -389,12 +522,10 @@ void ZIRCMode::loopMenuMode() uint8_t c = current->read(); if((c == '\r')||(c == '\n')||(buf.length()>510)) { - //serial.prints(buf); if((c=='\r')||(c=='\n')) { cmd=buf; buf=""; - //serial.prints(EOLNC); break; } buf=""; @@ -418,7 +549,7 @@ void ZIRCMode::loopMenuMode() if(cmd.indexOf("376")>=0) { ircState = ZIRCSTATE_COMMAND; - //TODO: say something? + serial.prints("* Commands: /join #, /list , or /quit.\r\n"); } else if(cmd.indexOf("No Ident response")>=0) @@ -458,29 +589,160 @@ void ZIRCMode::loopMenuMode() } case ZIRCSTATE_COMMAND: { - if((!joinReceived) - && (channelName.length()>0) - && (cmd.indexOf("366")>=0)) + // Print copy of raw IRC messages for debugging. Comment in/out as needed. + if (debugRaw) { - joinReceived=true; - serial.prints("Channel joined. Enter a message to send, or /quit."); + serial.prints("RAW> " + cmd); serial.prints(EOLNC); } - int x0 = cmd.indexOf(":"); - int x1 = (x0>=0)?cmd.indexOf(":", x0+1):-1; - if(x1>0) + int rawMsgStart = cmd.indexOf(":"); // raw IRC messages start with ':' + // the first space occurs after the user details (which sometimes contain extra ':' chars) + int firstSpace = cmd.indexOf(" "); + // this is the 2nd colon, after which is the message + int secondColon = (firstSpace >= 0) ? cmd.indexOf(":", firstSpace) : -1; + String theMessage = (secondColon >= 0) ? cmd.substring(secondColon + 1) : ""; + theMessage.trim(); + String msgMetaData = (secondColon >= 0) ? cmd.substring(rawMsgStart + 1, secondColon) : theMessage; + msgMetaData.trim(); + int startMsgType = cmd.indexOf("PRIVMSG "); + if((startMsgType >= 0) + &&(secondColon > 0)) + { + int endOfNick = msgMetaData.indexOf("!"); + if (endOfNick >= 0) + { + String fromNick = msgMetaData.substring(0,endOfNick); + int endMsgType = cmd.indexOf(' ', startMsgType); + int startSentTo = endMsgType + 1; + int endSentTo = cmd.indexOf(' ', startSentTo); + String sentTo = cmd.substring(startSentTo, endSentTo); + //serial.println("* sentTo: " + sentTo); + if (sentTo.startsWith("#")) // contains channel name in msg, so it is a channel message + { + serial.print("[" + sentTo + "]: <" + fromNick+ "> " + theMessage); + } + else + { + serial.print("*PRIV* <" + fromNick+ "> " + theMessage); + } + } + } + else // QUIT - message when a client quits IRC + if (msgMetaData.indexOf(" QUIT") > 0) + { + int endOfNick = msgMetaData.indexOf("!"); + String fromNick = msgMetaData.substring(0,endOfNick); + serial.print("[" + fromNick+ ": QUIT (" + theMessage + ")"); + } + else // JOIN - message when a client joins a channel we are in + if (msgMetaData.indexOf(" JOIN") > 0) + { + int endOfNick = msgMetaData.indexOf("!"); + int startOfChanName = msgMetaData.indexOf("#"); + String chanName=""; + if(startOfChanName >0) + chanName = msgMetaData.substring(startOfChanName, msgMetaData.length()); + else + { + startOfChanName = theMessage.indexOf("#"); + if(startOfChanName >= 0) + chanName = theMessage.substring(startOfChanName, theMessage.length()); + } + if((chanName.length()>0)&&(endOfNick>0)) + { + String fromNick = msgMetaData.substring(0,endOfNick); + serial.print("[" + fromNick+ " --> JOIN (" + chanName + ")"); + } + } + else // PART - message when a client leaves a channel we are in + if (msgMetaData.indexOf(" PART ") > 0) + { + int endOfNick = msgMetaData.indexOf("!"); + String chanName=""; + int startOfChanName = msgMetaData.indexOf("#"); + if(startOfChanName >0) + chanName = msgMetaData.substring(startOfChanName, msgMetaData.length()); + else + { + startOfChanName = theMessage.indexOf("#"); + if(startOfChanName >= 0) + chanName = theMessage.substring(startOfChanName, theMessage.length()); + } + if((chanName.length()>0)&&(endOfNick>0)) + { + String fromNick = msgMetaData.substring(0,endOfNick); + serial.print("[" + fromNick+ " <-- PART (" + chanName + ")"); + } + } + else // 353 = Names (lists all nicknames on channel) -- ignore this to prevent nickname spam on channel join + if (msgMetaData.indexOf(" 353 ") >= 0) + { + break; + } + else // 332 = TOPIC + if (cmd.indexOf("332") >= 0) + { + serial.prints("TOPIC: " + theMessage); + } + else // 322 = LIST + if (cmd.indexOf("322") >= 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); + int channelStart = cmd.indexOf("#",cmd.indexOf("322")); + int channelEnd = channelStart>0?cmd.indexOf(" ",channelStart):-1; + if(channelEnd > 0) + { + String channelName = cmd.substring(channelStart,channelEnd); + channelName.toLowerCase(); + if((listFilter.length()==0)||(channelName.indexOf(listFilter)>=0)) + serial.prints(cmd.substring(channelStart)); + else + break; // no EOLN PLZ + } else - serial.prints(msg2); + break; // no EOLN PLZ + } + else + if (cmd.indexOf("333") >= 0) // 333 = Who set TOPIC & Time set + { + break; + } + else // 366 = End of Names (channel joined) + if(msgMetaData.indexOf(" 366 ")>=0) + { + joinReceived=true; + serial.prints("Channel joined. Enter a message to send to channel."); + serial.prints(EOLNC); + serial.prints("/msg , /part #, /switch #, /list, or /quit."); serial.prints(EOLNC); } + else // Handle message types from WHOIS lookups 319 = WHOIS (Channel) + if (msgMetaData.indexOf(" 319 ") > 0) + { + serial.prints("W| " + cmd.substring(cmd.indexOf(" 319 "))); + } + else // 312 = WHOIS (server) + if (msgMetaData.indexOf(" 312 ") > 0) + { + serial.prints("W| " + cmd.substring(cmd.indexOf(" 312 "))); + } + else // 311 = WHOIS (hostmask) + if (msgMetaData.indexOf(" 311 ") > 0) + { + serial.prints("W| " + cmd.substring(cmd.indexOf(" 311 "))); + } + else // 330 = WHOIS (hostmask) + if (msgMetaData.indexOf(" 330 ") > 0) + { + serial.prints("W| " + cmd.substring(cmd.indexOf(" 330 "))); + } + else // 671 = WHOIS (hostmask) + if (msgMetaData.indexOf(" 671 ") > 0) + { + serial.prints("W| " + cmd.substring(cmd.indexOf(" 671 "))); + } + else + serial.prints(theMessage); + serial.prints(EOLNC); break; } default: @@ -504,7 +766,7 @@ void ZIRCMode::loopMenuMode() } } } - if(commandMode.checkPlusEscape()) + if(checkPlusPlusPlusEscape()) { switchBackToCommandMode(); } @@ -518,17 +780,14 @@ void ZIRCMode::loopMenuMode() 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()) + if(checkPlusPlusPlusEscape()) { switchBackToCommandMode(); } @@ -537,6 +796,7 @@ void ZIRCMode::loop() { serialOutDeque(); } + logFileLoop(); } #endif /* INCLUDE_IRCC */ diff --git a/zimodem/zprint.h b/zimodem/zprint.h index 207240d..e9c7eee 100644 --- a/zimodem/zprint.h +++ b/zimodem/zprint.h @@ -1,5 +1,5 @@ /* - Copyright 2020-2020 Bo Zimmerman + Copyright 2020-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -34,8 +34,6 @@ class ZPrint : public ZMode 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]; diff --git a/zimodem/zprint.ino b/zimodem/zprint.ino index d4c0abe..86d91b0 100644 --- a/zimodem/zprint.ino +++ b/zimodem/zprint.ino @@ -1,5 +1,5 @@ /* - Copyright 2020-2020 Bo Zimmerman + Copyright 2020-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -80,9 +80,7 @@ size_t ZPrint::writeChunk(char *s, int len) void ZPrint::announcePrintJob(const char *hostIp, const int port, const char *req) { logPrintfln("Print Request to host=%s, port=%d",hostIp,port); - debugPrintf("Print Request to host=%s, port=%d\n",hostIp,port); logPrintfln("Print Request is /%s",req); - debugPrintf("Print Request is /%s\n",req); } ZResult ZPrint::switchToPostScript(char *prefix) @@ -93,7 +91,6 @@ ZResult ZPrint::switchToPostScript(char *prefix) return ZERROR; char *workBuf = (char *)malloc(strlen(lastPrinterSpec) +1); strcpy(workBuf, lastPrinterSpec); - char *hostIp; char *req; int port; @@ -174,6 +171,7 @@ bool ZPrint::testPrinterSpec(const char *vbuf, int vlen, bool petscii) ZResult ZPrint::switchTo(char *vbuf, int vlen, bool petscii) { + debugPrintf("\r\nMode:Print\r\n"); char *workBuf = (char *)malloc(vlen+1); strcpy(workBuf, vbuf); if(petscii) @@ -293,8 +291,6 @@ ZResult ZPrint::finishSwitchTo(char *hostIp, char *req, int port, bool doSSL) checkBaudChange(); pdex=0; coldex=0; - lastNonPlusTimeMs = 0; - plussesInARow=0; currentExpiresTimeMs = millis()+5000; currMode=&printMode; return ZIGNORE; @@ -308,22 +304,11 @@ void ZPrint::serialIncoming() { uint8_t c=HWSerial.read(); logSerialIn(c); - if((c==commandMode.EC) - &&(plussesInARow<3) - &&((plussesInARow>0)||((millis()-lastNonPlusTimeMs)>900))) - { - plussesInARow++; - continue; - } - else + int plusOut = processPlusPlusPlus(c); + if(plusOut > 0) { - if(plussesInARow > 0) - { - for(int i=0;iisConnected()))) { - debugPrintf("No printer connection\n"); + debugPrintf("No printer connection\r\n"); switchBackToCommandMode(true); } else - if(millis()>currentExpiresTimeMs) + if((checkPlusPlusPlusEscape()) + ||(millis()>currentExpiresTimeMs)) { - debugPrintf("Time-out in printing\n"); + debugPrintf("Time-out in printing\r\n"); if(pdex > 0) writeChunk(pbuf,pdex); writeStr("0\r\n\r\n"); @@ -413,5 +397,6 @@ void ZPrint::loop() switchBackToCommandMode(false); } checkBaudChange(); + logFileLoop(); } diff --git a/zimodem/zslipmode.h b/zimodem/zslipmode.h index f69408e..cf580d7 100644 --- a/zimodem/zslipmode.h +++ b/zimodem/zslipmode.h @@ -1,18 +1,39 @@ +#ifdef INCLUDE_SLIP /* - * zslipmode.h - * - * Created on: May 17, 2022 - * Author: Bo Zimmerman - */ + Copyright 2022-2025 Bo Zimmerman -#ifdef INCLUDE_SLIP + 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/raw.h" +#include "esp_netif_types.h" +#include "esp_netif_net_stack.h" +static ZSerial sserial; class ZSLIPMode: public ZMode { private: void switchBackToCommandMode(); + bool escaped=false; + int curBufLen = 0; + int maxBufSize = 4096; + uint8_t *buf = 0; + raw_pcb *_pcb[5]; 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(); diff --git a/zimodem/zslipmode.ino b/zimodem/zslipmode.ino index ffd8562..bbc6f3a 100644 --- a/zimodem/zslipmode.ino +++ b/zimodem/zslipmode.ino @@ -1,45 +1,225 @@ -/* - * zslipmode.ino - * - * Created on: May 17, 2022 - * Author: Bo Zimmerman - */ #ifdef INCLUDE_SLIP +/* + Copyright 2022-2025 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 -#include "zslipmode.h" -#include "lwip/ip.h" -#include "lwip/netif.h" -#include "netif/slipif.h" + 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 ZSLIPMode::switchBackToCommandMode() { + debugPrintf("\r\nMode:Command\r\n"); currMode = &commandMode; + if(this->buf != 0) + free(this->buf); + this->buf = 0; + //TODO: UNDO THIS: raw_recv(_pcb, &_raw_recv, (void *) _pcb); +} + +esp_netif_t* get_esp_interface_netif(esp_interface_t interface); + +static uint8_t slipmode_recv(const uint8_t *payload, const int plen) +{ + if((plen > 20) + &&(payload[19]==255) // broadcast + &&(payload[9]==17)) // udp + return 0; // skip it + + debugPrintf("\r\nSLIP-in packet: %d bytes\r\n",plen); + sserial.printb(ZSLIPMode::SLIP_END); + if(sserial.isSerialOut()) + serialOutDeque(); + for(int p=0;plen; + if((plen > 0)&&(p->payload != 0)) + slipmode_recv((uint8_t *)p->payload, plen); + p=p->next; + } + return false; // maybe this cancels? } void ZSLIPMode::switchTo() { - struct netif sl_netif; - ip_addr_t ipaddr; - ip_addr_t netmask; - ip_addr_t gw; - char int_no = 2; - - IP4_ADDR(&ipaddr, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); - IP4_ADDR(&netmask, WiFi.subnetMask()[0], WiFi.subnetMask()[1], WiFi.subnetMask()[2], WiFi.subnetMask()[3]); - IP4_ADDR(&gw, WiFi.gatewayIP()[0], WiFi.gatewayIP()[1], WiFi.gatewayIP()[2], WiFi.gatewayIP()[3]); - netif_add (&sl_netif, &ipaddr, &netmask, &gw, &int_no, slipif_init, ip_input); - netif_set_up(&sl_netif); - //ip_napt_enable(ipaddr.addr, 1); + debugPrintf("\r\nMode:SLIP\r\n"); + + sserial.setFlowControlType(FCT_DISABLED); + if(commandMode.getFlowControlType()==FCT_RTSCTS) + sserial.setFlowControlType(FCT_RTSCTS); + sserial.setPetsciiMode(false); + sserial.setXON(true); + this->curBufLen = 0; + this->escaped=false; + //WiFi.disconnect(false,false); disconnects too much. :( + // this is the 'raw' way, that appears to rx all packets, but + // so does the existing wifi, so Crap. + if(_pcb[0] == 0) + { + _pcb[0] = raw_new(IP_PROTO_TCP); + _pcb[1] = raw_new(IP_PROTO_UDP); + _pcb[2] = raw_new(IP_PROTO_ICMP); + _pcb[3] = raw_new(IP_PROTO_IGMP); + _pcb[4] = raw_new(IP_PROTO_UDPLITE); + } + for(int i=0;i<5;i++) + { + if(_pcb[i] != 0) + raw_recv(_pcb[i], &_raw_recv, (void *)_pcb[i]); + } + esp_netif_t* esp_netif = get_esp_interface_netif(ESP_IF_WIFI_STA); + struct netif *n = netif_list; + while(n != 0) + { + //n->input = slip_input; + n=n->next; + } currMode=&slipMode; + debugPrintf("Switched to SLIP mode\n\r"); +} + +static uint8_t _raw_recv(void *arg, raw_pcb *pcb, pbuf *pb, const ip_addr_t *addr) +{ + while(pb != NULL) + { + pbuf * this_pb = pb; + int plen = this_pb->len; + if(plen > 0) + { + uint8_t* payload = (uint8_t *)this_pb->payload; + //pbuf_free(this_pb); // at this point, there are no refs, so errs out. + slipmode_recv(payload, plen); + } + pb = pb->next; + //this_pb->next = NULL; //TODO: i wonder if I need to free something here? check refs? + } + return false;// maybe this cancels? } void ZSLIPMode::serialIncoming() { + while(HWSerial.available()>0) + { + uint8_t c = HWSerial.read(); + if(logFileOpen) + logSerialIn(c); + if(this->buf == 0) + { + this->buf = (uint8_t *)malloc(4096); + this->maxBufSize = 4096; + this->curBufLen = 0; + } + if (c == ZSLIPMode::SLIP_END) + { + if(this->curBufLen > 0) + { + if(logFileOpen) + logPrintln("SLIP-out packet."); + //TODO: this probably eats memory + struct pbuf *p = pbuf_alloc(PBUF_RAW,this->curBufLen,PBUF_RAM); + //struct pbuf *p = (struct pbuf *)malloc(sizeof(struct pbuf)); + p->next = NULL; + memcpy(p->payload,this->buf,this->curBufLen); + p->len = this->curBufLen; + p->tot_len = this->curBufLen; + //p->type_internal = PBUF_RAM; + //p->ref = 1; + //p->flags = 0; + raw_send(_pcb[0], p); // not sure this is working +#ifdef ZIMODEM_ESP32 + debugPrintf("tot=%dk heap=%dk",(ESP.getFlashChipSize()/1024),(ESP.getFreeHeap()/1024)); + struct netif *n = ip_current_netif(); // keep this forever +#endif + this->curBufLen = 0; + //free(this->buf); // this might crash + //this->buf = 0; + this->escaped=false; + } + } + else + if(c == ZSLIPMode::SLIP_ESC) + this->escaped=true; + else + if((c == ZSLIPMode::SLIP_ESC_END) + &&(this->escaped)) + { + this->buf[this->curBufLen++] = ZSLIPMode::SLIP_END; + this->escaped = false; + } + else + if((c == ZSLIPMode::SLIP_ESC_ESC) + &&(this->escaped)) + { + this->buf[this->curBufLen++] = ZSLIPMode::SLIP_ESC; + this->escaped=false; + } + else + if(this->escaped) + { + debugPrintf("SLIP Protocol Error\n"); + if(logFileOpen) + logPrintln("SLIP error."); + this->curBufLen = 0; + this->escaped=false; + } + else + this->buf[this->curBufLen++] = c; + if(this->curBufLen >= this->maxBufSize) + { + uint8_t *newBuf = (uint8_t *)malloc(this->maxBufSize*2); + memcpy(newBuf,this->buf,this->curBufLen); + maxBufSize *= 2; + free(this->buf); + this->buf = newBuf; + } + } } void ZSLIPMode::loop() { - serialOutDeque(); - //switchBackToCommandMode(); + if(sserial.isSerialOut()) + serialOutDeque(); + logFileLoop(); } + #endif /* INCLUDE_SLIP_ */ diff --git a/zimodem/zstream.h b/zimodem/zstream.h index 47b90eb..d2baa37 100644 --- a/zimodem/zstream.h +++ b/zimodem/zstream.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,20 +16,28 @@ #define ZSTREAM_ESC_BUF_MAX 10 +enum HangupType +{ + HANGUP_NONE, + HANGUP_PPPHARD, + HANGUP_DTR, + HANGUP_PDP +}; + 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; + HangupType hangupType = HANGUP_NONE; int lastDTR = 0; + bool defaultEcho=false; + int lastPDP = 0; uint8_t escBuf[ZSTREAM_ESC_BUF_MAX]; - unsigned long nextAlarm = millis() + 5000; + unsigned long switchAlarm = millis() + 5000; - void switchBackToCommandMode(bool logout); + void switchBackToCommandMode(bool pppMode); void socketWrite(uint8_t c); void socketWrite(uint8_t *buf, uint8_t len); void baudDelay(); @@ -38,12 +46,15 @@ class ZStream : public ZMode bool isEcho(); FlowControlType getFlowControl(); bool isTelnet(); + bool isDefaultEcho(); bool isDisconnectedOnStreamExit(); + void doHangupChecks(); - public: - + void setDefaultEcho(bool tf); void switchTo(WiFiClientNode *conn); + void setHangupType(HangupType type); + HangupType getHangupType(); void serialIncoming(); void loop(); diff --git a/zimodem/zstream.ino b/zimodem/zstream.ino index 8dfb683..9f05a71 100644 --- a/zimodem/zstream.ino +++ b/zimodem/zstream.ino @@ -1,5 +1,5 @@ /* - Copyright 2016-2019 Bo Zimmerman + Copyright 2016-2025 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,10 +16,8 @@ void ZStream::switchTo(WiFiClientNode *conn) { + debugPrintf("\r\nMode:Stream\r\n"); current = conn; - currentExpiresTimeMs = 0; - lastNonPlusTimeMs = 0; - plussesInARow=0; serial.setXON(true); serial.setPetsciiMode(isPETSCII()); serial.setFlowControlType(getFlowControl()); @@ -27,6 +25,9 @@ void ZStream::switchTo(WiFiClientNode *conn) checkBaudChange(); if(pinSupport[pinDTR]) lastDTR = digitalRead(pinDTR); + if(pinSupport[pinOTH]) + lastPDP = digitalRead(pinOTH); + switchAlarm = millis() + 3000; } bool ZStream::isPETSCII() @@ -36,7 +37,17 @@ bool ZStream::isPETSCII() bool ZStream::isEcho() { - return (current != null) && (current->isEcho()); + return defaultEcho || ((current != null) && (current->isEcho())); +} + +void ZStream::setDefaultEcho(bool tf) +{ + defaultEcho = tf; +} + +bool ZStream::isDefaultEcho() +{ + return defaultEcho; } FlowControlType ZStream::getFlowControl() @@ -93,16 +104,8 @@ void ZStream::serialIncoming() bytesAvailable=HWSerial.available(); } } + processPlusPlusPlus(c); 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 @@ -121,13 +124,25 @@ void ZStream::serialIncoming() if(escBufDex>0) socketWrite(escBuf,escBufDex); - currentExpiresTimeMs = 0; - if(plussesInARow==3) - currentExpiresTimeMs=millis()+800; } -void ZStream::switchBackToCommandMode(bool logout) +void ZStream::switchBackToCommandMode(bool pppMode) { + s_pinWrite(pinRI,riInactive); + bool logout = true; + if(pppMode) + { + if(current != NULL) + { + commandMode.sendOfficialResponse(ZOK); + if(hangupType != HANGUP_NONE) + { + current->flushAlways(); + current->markForDisconnect(); + } + logout = false; + } + } if(logout && (current != null) && isDisconnectedOnStreamExit()) { if(!commandMode.suppressResponses) @@ -148,6 +163,7 @@ void ZStream::switchBackToCommandMode(bool logout) } delete current; } + debugPrintf("\r\nMode:Command\r\n"); current = null; currMode = &commandMode; } @@ -191,6 +207,60 @@ void ZStream::socketWrite(uint8_t c) } } +void ZStream::setHangupType(HangupType type) +{ + hangupType = type; +} + +HangupType ZStream::getHangupType() +{ + return hangupType; +} + +void ZStream::doHangupChecks() +{ + if(hangupType == HANGUP_DTR) + { + if(pinSupport[pinDTR]) + { + if(lastDTR==dtrActive) + { + lastDTR = digitalRead(pinDTR); + if((lastDTR==dtrInactive) + &&(dtrInactive != dtrActive)) + { + logPrintln("Hangup: DTR"); + if(current != null) + current->setDisconnectOnStreamExit(true); + switchBackToCommandMode(false); + } + } + lastDTR = digitalRead(pinDTR); + } + } + else + if(hangupType == HANGUP_PDP) + { + if(pinSupport[pinOTH]) + { + if(lastPDP==othActive) + { + lastPDP = digitalRead(pinOTH); + if((lastPDP==othInactive) + &&(othInactive != othActive)) + { + logPrintln("Hangup: PDP"); + if(current != null) + current->setDisconnectOnStreamExit(true); + switchBackToCommandMode(false); + } + } + lastPDP = digitalRead(pinOTH); + } + } + +} + void ZStream::loop() { WiFiServerNode *serv = servs; @@ -240,40 +310,13 @@ void ZStream::loop() } WiFiClientNode::checkForAutoDisconnections(); + doHangupChecks(); - 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); - } + switchBackToCommandMode(false); else - if((currentExpiresTimeMs > 0) && (millis() > currentExpiresTimeMs)) - { - currentExpiresTimeMs = 0; - if(plussesInARow == 3) - { - plussesInARow=0; - if(current != 0) - { - commandMode.sendOfficialResponse(ZOK); - switchBackToCommandMode(false); - } - } - } + if(checkPlusPlusPlusEscape()) + switchBackToCommandMode(true); else if(serial.isSerialOut()) { @@ -312,5 +355,13 @@ void ZStream::loop() } } checkBaudChange(); + logFileLoop(); + // if it was ringing when we came in, turn off the ringer + if((switchAlarm != 0) + &&(millis()>switchAlarm)) + { + s_pinWrite(pinRI,riInactive); + switchAlarm = 0; + } }