package main import ( "crypto/tls" "encoding/binary" "encoding/json" "errors" "fmt" "net" "os" "strconv" "time" ) func (o *Oftp) New(filename string) error { // Set default values newOftp := Oftp{ LocalCode: "LOCALCODE", LocalPassword: "LOCALPWD", PartnerCode: "1234567890CODE", PartnerPassword: "SUPERSECRET", OftpLevel: 4, OftpBuffer: 512, OftpDuplex: "S", OftpCompression: "N", OftpRestart: "N", OftpCredit: 7, OftpAuthentication: "N", NetworkHost: "localhost", NetworkPort: 3305, NetworkTimeout: 10, NetworkTLS: false, } f, err := os.Open(filename) if err != nil { return err } defer f.Close() d := json.NewDecoder(f) if err := d.Decode(&newOftp); err != nil { return err } *o = newOftp return nil } func (o Oftp) Call() error { // 1. Open connection // 2. Wait for SSRM // 3. Send SSID // 4. Wait for SSID // 5. Validate SSID // 6. Send ESID // 1. Open connection var conn net.Conn var err error if o.NetworkTLS { netDialer := &net.Dialer{ Timeout: time.Duration(o.NetworkTimeout) * time.Second, } tlsConfig := &tls.Config{ InsecureSkipVerify: true, } conn, err = tls.DialWithDialer(netDialer, "tcp", fmt.Sprintf("%s:%d", o.NetworkHost, o.NetworkPort), tlsConfig) if err != nil { return fmt.Errorf("Error connecting: %s", err) } defer conn.Close() } else { conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", o.NetworkHost, o.NetworkPort), time.Duration(o.NetworkTimeout)*time.Second) if err != nil { return fmt.Errorf("Error connecting: %s", err) } defer conn.Close() } fmt.Printf("Connected to %s:%d\n", o.NetworkHost, o.NetworkPort) // 2. Wait for SSRM headerBuf := make([]byte, 4) dataBuf := make([]byte, o.OftpBuffer) // Read STH (header) conn.SetReadDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) readCnt, err := conn.Read(headerBuf) if err != nil { conn.Close() return fmt.Errorf("Error reading SSRM stream header: %s", err) } _, err = parseSTH(headerBuf[:4]) if err != nil { conn.Close() return fmt.Errorf("Error parsing SSRM stream header: %s", err) } // Read OFTP command conn.SetReadDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) readCnt, err = conn.Read(dataBuf) if err != nil { conn.Close() return fmt.Errorf("Error reading SSRM: %s", err) } if string(dataBuf[:readCnt]) != "IODETTE FTP READY \r" && string(dataBuf[:readCnt]) != "IODETTE FTP READY \n" { conn.Close() return errors.New(fmt.Sprintf("Expected SSRM (%x), got %x", "IODETTE FTP READY \r", dataBuf[:readCnt])) } fmt.Printf("Received SSRM (%d)\n", readCnt) // 3. Send SSID conn.SetWriteDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) writtenCnt, err := conn.Write(o.LocalSSID()) if err != nil { conn.Close() return fmt.Errorf("Error writing SSID: %s", err) } fmt.Printf("Sent SSID (%d)\n", writtenCnt) // 4. Wait for SSID // Read STH (header) conn.SetReadDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) readCnt, err = conn.Read(headerBuf) if err != nil { conn.Close() return fmt.Errorf("Error reading SSID stream header: %s", err) } _, err = parseSTH(headerBuf[:4]) if err != nil { conn.Close() return fmt.Errorf("Error parsing SSID: %s", err) } // Read OFTP command conn.SetReadDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) readCnt, err = conn.Read(dataBuf) if err != nil { conn.Close() return fmt.Errorf("Error reading SSID: %s", err) } // 5. Validate SSID ESIDCode := o.ValidateSSID(dataBuf[:readCnt]) if ESIDCode < 0 { fmt.Println(ESIDCode) var err error if ESIDCode > -100 { // ESID error, disconnect err = errors.New(fmt.Sprintf("Received negative ESID with reason code %d", ESIDCode*-1)) } else { err = errors.New("Bad non-ESID reply to SSID") } conn.Close() return err } fmt.Printf("Received SSID (%d)\n", readCnt) // 6. Send ESID conn.SetWriteDeadline(time.Now().Add(time.Duration(o.NetworkTimeout) * time.Second)) writtenCnt, err = conn.Write(o.ESID(ESIDCode)) if err != nil { conn.Close() return fmt.Errorf("Error writing ESID: %s", err) } fmt.Printf("Sent ESID (%d) with code %d\n", writtenCnt, ESIDCode) // Wait for partner to disconnect conn.SetReadDeadline(time.Now().Add(10 * time.Second)) conn.Read(dataBuf) conn.Close() fmt.Println("Disconnected") if ESIDCode != 0 { return errors.New(fmt.Sprintf("Ended session with ESID reason code %d", ESIDCode)) } return nil } func (o Oftp) LocalSSID() []byte { dataBuf := [61]byte{} copy(dataBuf[0:1], "X") copy(dataBuf[1:2], fmt.Sprintf("%d", o.OftpLevel)) copy(dataBuf[2:27], fmt.Sprintf("%-25s", o.LocalCode)) copy(dataBuf[27:35], fmt.Sprintf("%-8s", o.LocalPassword)) copy(dataBuf[35:40], fmt.Sprintf("%05d", o.OftpBuffer)) copy(dataBuf[40:41], o.OftpDuplex) copy(dataBuf[41:42], o.OftpCompression) copy(dataBuf[42:43], o.OftpRestart) copy(dataBuf[43:44], "N") copy(dataBuf[44:47], fmt.Sprintf("%03d", o.OftpCredit)) if o.OftpLevel > 4 { copy(dataBuf[47:48], o.OftpAuthentication) copy(dataBuf[48:52], fmt.Sprintf("%-4s", "")) } else { copy(dataBuf[47:52], fmt.Sprintf("%-5s", "")) } copy(dataBuf[52:60], fmt.Sprintf("%-8s", "")) copy(dataBuf[60:61], "\r") sth := buildSTH(len(dataBuf)) stb := []byte{} stb = append(stb, sth...) stb = append(stb, dataBuf[:]...) return stb } func (o Oftp) ESID(code int) []byte { dataBuf := [7]byte{} copy(dataBuf[0:1], "F") copy(dataBuf[1:3], fmt.Sprintf("%02d", code)) copy(dataBuf[3:6], fmt.Sprintf("%03d", 0)) copy(dataBuf[6:7], "\r") sth := buildSTH(len(dataBuf)) stb := []byte{} stb = append(stb, sth...) stb = append(stb, dataBuf[:]...) return stb } func (o Oftp) ValidateSSID(SSIDBytes []byte) int { // Validate cmd, code and password only cmdByte := string(SSIDBytes[0:1]) if cmdByte == "F" { ESIDCodeStr := string(SSIDBytes[1:3]) ESIDCodeInt, _ := strconv.Atoi(ESIDCodeStr) return -1 * ESIDCodeInt } if cmdByte != "X" { return -100 } codeBytes := string(SSIDBytes[2:27]) if codeBytes != fmt.Sprintf("%-25s", o.PartnerCode) { return 3 } passwordBytes := string(SSIDBytes[27:35]) if passwordBytes != fmt.Sprintf("%-8s", o.PartnerPassword) { return 4 } return 0 } func parseSTH(sth []byte) (int32, error) { sth[0] = 0 length := binary.BigEndian.Uint32(sth) return int32(length), nil } func buildSTH(length int) []byte { sth := make([]byte, 4) sth[0] = 16 lengthBytes := make([]byte, 4) binary.BigEndian.PutUint32(lengthBytes, uint32(length+4)) copy(sth[1:4], lengthBytes[1:4]) return sth }