Compare commits

..

No commits in common. "main" and "text" have entirely different histories.
main ... text

47 changed files with 1185 additions and 4562 deletions

View File

@ -1,9 +1,29 @@
--- ---
name: New issue name: Bug report
about: Create an issue about: Create a report to help us improve
title: '' title: ''
labels: ''
assignees: '' assignees: ''
--- ---
*Read this and delete before submitting:* Thanks for starting a discussion! Please provide as much context as possible so that others can understand your thoughts. If you have a specific change in mind, consider submitting a pull request instead. **Please try to [download the latest version](https://github.com/schollz/croc/releases/latest) of croc before reporting a bug!**
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Version**
Check `croc -version` and report it.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

View File

@ -1,52 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
unit-tests:
name: Go unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- run: go version
- run: go test -v ./...
- name: Build files
run: |
go version
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc.exe
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc.exe
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
GOARM=5 CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -ldflags '' -o croc
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm64 go build -ldflags '' -o croc
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.20.7' # go1.20.8+ refuses to build go1.22 code...
- name: Build Windows 7
run: |
go version
rm go.mod go.sum
go mod init github.com/schollz/croc/v10
go mod tidy
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe

View File

@ -1,64 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: CD
# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches:
- '*'
tags:
- 'v*'
pull_request:
branches:
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Docker meta
id: docker_meta
uses: docker/metadata-action@v5
with:
images: schollz/croc
# generate Docker tags based on the following events/attributes
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@ -1,131 +0,0 @@
name: Make release
on:
release:
types: [created]
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Prepare source tarball
run: |
git clone -b ${{ github.event.release.name }} --depth 1 https://github.com/schollz/croc croc-${{ github.event.release.name }}
cd croc-${{ github.event.release.name }} && go mod tidy && go mod vendor
cd .. && tar -czvf croc_${{ github.event.release.name }}_src.tar.gz croc-${{ github.event.release.name }}
- name: Build files
run: |
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows-64bit.zip croc.exe LICENSE
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows-32bit.zip croc.exe LICENSE
CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows-ARM.zip croc.exe LICENSE
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows-ARM64.zip croc.exe LICENSE
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc
tar -czvf croc_${{ github.event.release.name }}_Linux-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc
tar -czvf croc_${{ github.event.release.name }}_Linux-32bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
tar -czvf croc_${{ github.event.release.name }}_Linux-ARM.tar.gz croc LICENSE
GOARM=5 CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags '-extldflags "-static"' -o croc
tar -czvf croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-extldflags "-static"' -o croc
tar -czvf croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
tar -czvf croc_${{ github.event.release.name }}_macOS-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"' -o croc
tar -czvf croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz croc LICENSE
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm64 go build -ldflags '' -o croc
tar -czvf croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz croc LICENSE
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.20.7' # go1.20.8+ refuses to build go1.22 code...
- name: Build Windows 7
run: |
go version
rm go.mod go.sum
go mod init github.com/schollz/croc/v10
go mod tidy
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows7-64bit.zip croc.exe
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags '-extldflags "-static"' -o croc.exe
zip croc_${{ github.event.release.name }}_Windows7-32bit.zip croc.exe
- name: Create checksums.txt
run: |
touch croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_src.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows-64bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows-32bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows-ARM.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows-ARM64.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows7-64bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Windows7-32bit.zip >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Linux-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Linux-32bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Linux-ARM.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_macOS-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
sha256sum croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz >> croc_${{ github.event.release.name }}_checksums.txt
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |
croc_${{ github.event.release.name }}_checksums.txt
croc_${{ github.event.release.name }}_src.tar.gz
croc_${{ github.event.release.name }}_Windows-64bit.zip
croc_${{ github.event.release.name }}_Windows-32bit.zip
croc_${{ github.event.release.name }}_Windows-ARM.zip
croc_${{ github.event.release.name }}_Windows-ARM64.zip
croc_${{ github.event.release.name }}_Windows7-64bit.zip
croc_${{ github.event.release.name }}_Windows7-32bit.zip
croc_${{ github.event.release.name }}_Linux-64bit.tar.gz
croc_${{ github.event.release.name }}_Linux-32bit.tar.gz
croc_${{ github.event.release.name }}_Linux-ARM.tar.gz
croc_${{ github.event.release.name }}_Linux-ARMv5.tar.gz
croc_${{ github.event.release.name }}_Linux-ARM64.tar.gz
croc_${{ github.event.release.name }}_macOS-64bit.tar.gz
croc_${{ github.event.release.name }}_macOS-ARM64.tar.gz
croc_${{ github.event.release.name }}_DragonFlyBSD-64bit.tar.gz
croc_${{ github.event.release.name }}_FreeBSD-64bit.tar.gz
croc_${{ github.event.release.name }}_FreeBSD-ARM64.tar.gz
croc_${{ github.event.release.name }}_NetBSD-32bit.tar.gz
croc_${{ github.event.release.name }}_NetBSD-64bit.tar.gz
croc_${{ github.event.release.name }}_NetBSD-ARM64.tar.gz
croc_${{ github.event.release.name }}_OpenBSD-64bit.tar.gz
croc_${{ github.event.release.name }}_OpenBSD-ARM64.tar.gz

View File

@ -1,27 +0,0 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '19 12 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

View File

@ -1,14 +0,0 @@
name: Publish to Winget
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: schollz.croc
installers-regex: '_Windows-\w+\.zip$'
token: ${{ secrets.WINGET_TOKEN }}

13
.gitignore vendored
View File

@ -1,13 +0,0 @@
# Binaries
/croc
/croc.exe
zsh_autocomplete
bash_autocomplete
dist
bin
croc-stdin*
.idea/
.vscode/
src/utils/bigfile.test
test1/

View File

@ -10,12 +10,12 @@ install: true
script: script:
- env GO111MODULE=on go build -v - env GO111MODULE=on go build -v
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/compress - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/compress
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/croc - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/croc
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/crypt - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/crypt
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/tcp - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/tcp
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/utils - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/utils
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v10/src/comm - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v8/src/comm
branches: branches:
except: except:

View File

@ -1,16 +1,15 @@
FROM golang:1.22-alpine as builder FROM golang:1.14-alpine as builder
RUN apk add --no-cache git gcc musl-dev RUN apk add --no-cache git
WORKDIR /go/croc WORKDIR /go/croc
COPY . . COPY . .
RUN go build -v -ldflags="-s -w" RUN go build -v
FROM alpine:latest FROM alpine:latest
EXPOSE 9009 EXPOSE 9009
EXPOSE 9010 EXPOSE 9010
EXPOSE 9011 EXPOSE 9011
EXPOSE 9012 EXPOSE 9012
EXPOSE 9013 EXPOSE 9013
COPY --from=builder /go/croc/croc /go/croc/croc-entrypoint.sh / COPY --from=builder /go/croc/croc /go/croc/croc-entrypoint.sh /
USER nobody
ENTRYPOINT ["/croc-entrypoint.sh"] ENTRYPOINT ["/croc-entrypoint.sh"]
CMD ["relay"] CMD ["relay"]

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2024 Zack Copyright (c) 2017-2020 Zack
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

444
README.md
View File

@ -1,281 +1,163 @@
<p align="center"> <p align="center">
<img <img
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg" src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
width="408px" border="0" alt="croc"> width="408px" border="0" alt="croc">
<br> <br>
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-v10.1.1-brightgreen.svg?style=flat-square" alt="Version"></a> <a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-v8.1.3-brightgreen.svg?style=flat-square" alt="Version"></a>
<a href="https://github.com/schollz/croc/actions/workflows/ci.yml"><img <a href="https://coveralls.io/github/schollz/croc"><img src="https://img.shields.io/badge/coverage-81%25-green.svg?style=flat-square" alt="Coverage"></a>
src="https://github.com/schollz/croc/actions/workflows/ci.yml/badge.svg" alt="Build <a href="https://travis-ci.org/schollz/croc"><img
Status"></a> src="https://img.shields.io/travis/schollz/croc.svg?style=flat-square" alt="Build
<p align="center">This project is supported by <a href="https://github.com/sponsors/schollz">Github sponsors</a>.</p> Status"></a>
</p>
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool that does **all** of the following:
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool does **all** of the following:
- allows **any two computers** to transfer data (using a relay)
- provides **end-to-end encryption** (using PAKE) - allows **any two computers** to transfer data (using a relay)
- enables easy **cross-platform** transfers (Windows, Linux, Mac) - provides **end-to-end encryption** (using PAKE)
- allows **multiple file** transfers - enables easy **cross-platform** transfers (Windows, Linux, Mac)
- allows **resuming transfers** that are interrupted - allows **multiple file** transfers
- local server or port-forwarding **not needed** - allows **resuming transfers** that are interrupted
- **ipv6-first** with ipv4 fallback - local server or port-forwarding **not needed**
- can **use proxy**, like tor - **faster** than [wormhole](https://github.com/warner/magic-wormhole), [rsync](https://linux.die.net/man/1/rsync), [scp](https://linux.die.net/man/1/scp) through compression and multiplexing (speedups 1.5x to 4x)
- supports ipv6
For more information about `croc`, see [my blog post](https://schollz.com/software/croc6) or read a [recent interview I did](https://console.substack.com/p/console-91).
For more information about `croc`, see [my blog post](https://schollz.com/software/croc6).
![Example](src/install/customization.gif)
![Example](src/install/customization.gif)
## Install
## Install
Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line:
Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line:
```
curl https://getcroc.schollz.com | bash ```
``` $ curl https://getcroc.schollz.com | bash
```
On macOS you can install the latest release with [Homebrew](https://brew.sh/):
On macOS you can install the latest release with [Homebrew](https://brew.sh/):
```
brew install croc ```
``` $ brew install schollz/tap/croc
```
On macOS you can also install the latest release with [MacPorts](https://macports.org/):
``` On Windows you can install the latest release with [Scoop](https://scoop.sh/):
sudo port selfupdate
sudo port install croc ```
``` $ scoop install croc
```
On Windows you can install the latest release with [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org), or [Winget](https://learn.microsoft.com/en-us/windows/package-manager/):
On Unix you can install the latest release with [Nix](https://nixos.org/nix):
```
scoop install croc ```
``` $ nix-env -i croc
```
```
choco install croc
``` On Arch Linux you can install the latest release with `pacman`:
``` ```
winget install schollz.croc $ pacman -S croc
``` ```
On Unix you can install the latest release with [Nix](https://nixos.org/nix): On Ubuntu you can install with `snap`:
``` ```
nix-env -i croc $ snap install croc
``` ```
Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.12+):
On Alpine Linux you have to install dependencies first:
```
``` $ GO111MODULE=on go get -v github.com/schollz/croc/v8
apk add bash coreutils ```
wget -qO- https://getcroc.schollz.com | bash
```
On Arch Linux you can install the latest release with `pacman`: ## Usage
``` To send a file, simply do:
pacman -S croc
``` ```
$ croc send [file(s)-or-folder]
On Fedora you can install with `dnf`: Sending 'file-or-folder' (X MB)
Code is: code-phrase
``` ```
dnf install croc
``` Then to receive the file (or folder) on another computer, you can just do
On Gentoo you can install with `portage`: ```
``` $ croc code-phrase
emerge net-misc/croc ```
```
The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption.
On Termux you can install with `pkg`:
There are a number of configurable options (see `--help`). A set of options (like custom relay, ports, and code phrase) can be set using `--remember`.
```
pkg install croc ### Custom code phrase
```
You can send with your own code phrase (must be more than 4 characters).
On FreeBSD you can install with `pkg`:
```
``` $ croc send --code [code-phrase] [file(s)-or-folder]
pkg install croc ```
```
### Use pipes - stdin and stdout
On Linux, macOS, and Windows you can install from [conda-forge](https://github.com/conda-forge/croc-feedstock/) globally with [`pixi`](https://pixi.sh/):
You can pipe to `croc`:
```
pixi global install croc ```
``` $ cat [filename] | croc send
```
or into a particular environment with [`conda`](https://docs.conda.io/projects/conda/):
In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` will automatically approve the transfer and pipe it out to `stdout`.
```
conda install --channel conda-forge croc ```
``` $ croc --yes [code-phrase] > out
```
Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.17+):
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`.
```
go install github.com/schollz/croc/v10@latest ### Self-host relay
```
The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay:
On Android there is a 3rd party F-Droid app [available to download](https://f-droid.org/en/packages/com.github.howeyc.crocgui/).
```
$ croc relay
## Usage ```
To send a file, simply do: By default it uses TCP ports 9009-9013. Make sure to open those up. You can customized the ports (e.g. `croc relay --ports 1111,1112`), but you must have a minimum of **2** ports for the relay. The first port is for communication and the subsequent ports are used for the multiplexed data transfer.
``` You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own.
$ croc send [file(s)-or-folder]
Sending 'file-or-folder' (X MB) ```
Code is: code-phrase $ croc --relay "myrelay.example.com:9009" send [filename]
``` ```
Then to receive the file (or folder) on another computer, you can just do Note, when sending, you only need to include the first port (the communication port). The subsequent ports for data transfer will be transmitted back to the user from the relay.
``` #### Self-host relay (docker)
croc code-phrase
``` If it's easier you can also run a relay with Docker:
The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption.
```
There are a number of configurable options (see `--help`). A set of options (like custom relay, ports, and code phrase) can be set using `--remember`. $ docker run -d -p 9009:9009 -p 9010:9010 -p 9011:9011 -p 9012:9012 -p 9013:9013 -e CROC_PASS='YOURPASSWORD' schollz/croc
```
### Using `croc` on Linux or Mac OS
Be sure to include the password for the relay otherwise any requests will be rejected.
On Linux and Mac OS, the sending & receiving is slightly different to avoid [leaking the secret via the process name](https://nvd.nist.gov/vuln/detail/CVE-2023-43621). On these systems you will need to run `croc` with the secret as an environment variable. For example, to receive with the secret `***`:
```
``` $ croc --pass YOURPASSWORD --relay "myreal.example.com:9009" send [filename]
CROC_SECRET=*** croc ```
```
## License
This will show only `croc` in the process list of a multi-user system and not leak the secret.
MIT
For a single-user system the default behavior can be permanently enabled by running
## Acknowledgements
```
croc --classic `croc` has gone through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
```
Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@maximbaz](https://github.com/maximbaz), [@meyermarcel](https://github.com/meyermarcel), [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy), [@fbartels](https://github.com/fbartels), [@rkuprov](https://github.com/rkuprov), [@xenrox](https://github.com/xenrox) and [Ipar](https://github.com/lpar)!
and confirming.
Run this command again to disable classic mode.
### Custom code phrase
You can send with your own code phrase (must be more than 6 characters).
```
croc send --code [code-phrase] [file(s)-or-folder]
```
### Allow overwriting without prompt
By default, croc will prompt whether to overwrite a file. You can automatically overwrite files by using the `--overwrite` flag (recipient only). For example, receive a file to automatically overwrite:
```
croc --yes --overwrite <code>
```
### Use pipes - stdin and stdout
You can pipe to `croc`:
```
cat [filename] | croc send
```
In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` will automatically approve the transfer and pipe it out to `stdout`.
```
croc --yes [code-phrase] > out
```
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`.
### Send text
Sometimes you want to send URLs or short text. In addition to piping, you can easily send text with `croc`:
```
croc send --text "hello world"
```
This will automatically tell the receiver to use `stdout` when they receive the text so it will be displayed.
### Use a proxy
You can use a proxy as your connection to the relay by adding a proxy address with `--socks5`. For example, you can send via a tor relay:
```
croc --socks5 "127.0.0.1:9050" send SOMEFILE
```
### Change encryption curve
You can choose from several different elliptic curves to use for encryption by using the `--curve` flag. Only the recipient can choose the curve. For example, receive a file using the P-521 curve:
```
croc --curve p521 <codephrase>
```
Available curves are P-256, P-348, P-521 and SIEC. P-256 is the default curve.
### Change hash algorithm
You can choose from several different hash algorithms. The default is the `xxhash` algorithm which is fast and thorough. If you want to optimize for speed you can use the `imohash` algorithm which is even faster, but since it samples files (versus reading the whole file) it can mistakenly determine that a file is the same on the two computers transferring - though this is only a problem if you are syncing files versus sending a new file to a computer.
```
croc send --hash imohash SOMEFILE
```
### Self-host relay
The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay:
```
croc relay
```
By default it uses TCP ports 9009-9013. Make sure to open those up. You can customize the ports (e.g. `croc relay --ports 1111,1112`), but you must have a minimum of **2** ports for the relay. The first port is for communication and the subsequent ports are used for the multiplexed data transfer.
You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own.
```
croc --relay "myrelay.example.com:9009" send [filename]
```
Note, when sending, you only need to include the first port (the communication port). The subsequent ports for data transfer will be transmitted back to the user from the relay.
#### Self-host relay (docker)
If it's easier you can also run a relay with Docker:
```
docker run -d -p 9009-9013:9009-9013 -e CROC_PASS='YOURPASSWORD' schollz/croc
```
Be sure to include the password for the relay otherwise any requests will be rejected.
```
croc --pass YOURPASSWORD --relay "myreal.example.com:9009" send [filename]
```
Note: when including `--pass YOURPASSWORD` you can instead pass a file with the password, e.g. `--pass FILEWITHPASSWORD`.
## License
MIT
## Acknowledgements
`croc` has gone through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, or ask a question.
Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@maximbaz](https://github.com/maximbaz), [@meyermarcel](https://github.com/meyermarcel), [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy), [@fbartels](https://github.com/fbartels), [@rkuprov](https://github.com/rkuprov), [@hreese](https://github.com/hreese), [@xenrox](https://github.com/xenrox) and [Ipar](https://github.com/lpar)!

View File

@ -4,7 +4,7 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
DynamicUser=yes User=nobody
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/usr/bin/croc relay ExecStart=/usr/bin/croc relay

55
go.mod
View File

@ -1,39 +1,30 @@
module github.com/schollz/croc/v10 module github.com/schollz/croc/v8
go 1.22 go 1.13
toolchain go1.23.1
require ( require (
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/chzyer/readline v1.5.1 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/denisbrodbeck/machineid v1.0.1 github.com/denisbrodbeck/machineid v1.0.1
github.com/kalafut/imohash v1.1.0 github.com/fatih/color v1.9.0 // indirect
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b github.com/kalafut/imohash v1.0.0
github.com/minio/highwayhash v1.0.3 github.com/kr/pretty v0.1.0 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/mattn/go-colorable v0.1.7 // indirect
github.com/schollz/cli/v2 v2.2.1
github.com/schollz/logger v1.2.0 github.com/schollz/logger v1.2.0
github.com/schollz/pake/v3 v3.0.5 github.com/schollz/mnemonicode v1.0.1
github.com/schollz/peerdiscovery v1.7.5 github.com/schollz/pake/v2 v2.0.4
github.com/schollz/progressbar/v3 v3.17.1 github.com/schollz/peerdiscovery v1.5.2
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/schollz/progressbar/v3 v3.5.0
github.com/stretchr/testify v1.9.0 github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
golang.org/x/crypto v0.29.0 github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/net v0.31.0 github.com/stretchr/testify v1.4.0
golang.org/x/sys v0.27.0 github.com/tscholl2/siec v0.0.0-20191122224205-8da93652b094
golang.org/x/term v0.26.0 github.com/urfave/cli v1.22.4
golang.org/x/time v0.8.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
) golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect
require ( golang.org/x/text v0.3.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect gopkg.in/yaml.v2 v2.2.7 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

198
go.sum
View File

@ -1,148 +1,104 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/kalafut/imohash v1.1.0 h1:Lldcmx0SXgMSoABB2WBD8mTgf0OlVnISn2Dyrfg2Ep8= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/kalafut/imohash v1.1.0/go.mod h1:6cn9lU0Sj8M4eu9UaQm1kR/5y3k/ayB68yntRhGloL4= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI= github.com/kalafut/imohash v1.0.0 h1:LgCJ+p/BwM2HKpOxFopkeddpzVCfm15EtXMroXD1SYE=
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg= github.com/kalafut/imohash v1.0.0/go.mod h1:c3RHT80ZAp5C/aYgQI92ZlrOymqkZnRDprU87kg75HI=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/schollz/cli/v2 v2.2.1 h1:ou22Mj7ZPjrKz+8k2iDTWaHskEEV5NiAxGrdsCL36VU=
github.com/schollz/cli/v2 v2.2.1/go.mod h1:My6bfphRLZUhZdlFUK8scAxMWHydE7k4s2ed2Dtnn+s=
github.com/schollz/logger v1.2.0 h1:5WXfINRs3lEUTCZ7YXhj0uN+qukjizvITLm3Ca2m0Ho= github.com/schollz/logger v1.2.0 h1:5WXfINRs3lEUTCZ7YXhj0uN+qukjizvITLm3Ca2m0Ho=
github.com/schollz/logger v1.2.0/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM= github.com/schollz/logger v1.2.0/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM=
github.com/schollz/pake/v3 v3.0.5 h1:MnZVdI987lkjln9BSx/zUb724TZISa2jbO+dPj6BvgQ= github.com/schollz/mnemonicode v1.0.1 h1:LiH5hwADZwjwnfXsaD4xgnMyTAtaKHN+e5AyjRU6WSU=
github.com/schollz/pake/v3 v3.0.5/go.mod h1:OGbG6htRwSKo6V8R5tg61ufpFmZM1b/PrrSp6g2ZLLc= github.com/schollz/mnemonicode v1.0.1/go.mod h1:cl4UAOhUV0mkdjMj/QYaUZbZZdF8BnOqoz8rHMzwboY=
github.com/schollz/peerdiscovery v1.7.5 h1:0cEhO+o8i4fpeKBwl7u0UY3Kt3XVt5fSzS4rg17ZPb4= github.com/schollz/pake/v2 v2.0.4 h1:C49hvs+4J+Vl375IKzyC/RDYzGO1q4zPfuWtr3ehl88=
github.com/schollz/peerdiscovery v1.7.5/go.mod h1:Crht2FOfD1/eL3U/AIM0vvwVZDPePlBgSX3Xw+TnJoE= github.com/schollz/pake/v2 v2.0.4/go.mod h1:y4xMfWSvjSFF2I0RRWZl1o/vskyh9dmKmmvFUXTsyRg=
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U= github.com/schollz/peerdiscovery v1.5.2 h1:gc1iXQ0A9xj58nm7QHs5a1tCoYv7QDIrZbQfZgvVmiE=
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4= github.com/schollz/peerdiscovery v1.5.2/go.mod h1:hSU7N/NkfNH6AZwU/WBcDZtMABVbTfAWk/XD3XKxN+s=
github.com/schollz/progressbar/v2 v2.15.0 h1:dVzHQ8fHRmtPjD3K10jT3Qgn/+H+92jhPrhmxIJfDz8=
github.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI=
github.com/schollz/progressbar/v3 v3.5.0 h1:rVya3NTks1kFmuFKaLYWt/3TYDiE+Cxb1iXuxeEjf5c=
github.com/schollz/progressbar/v3 v3.5.0/go.mod h1:Rp5lZwpgtYmlvmGo1FyDwXMqagyRBQYSDwzlP9QDu84=
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9 h1:y08o5oQ/slxXE/F0uh5dd8mdVvb+w4NLcNSDSq4c2F0=
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9/go.mod h1:kCMoQsqzx4MzGJWaALr6tKyCnlrY0kILGLkA1FOiLF4=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tscholl2/siec v0.0.0-20210707234609-9bdfc483d499/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw= github.com/tscholl2/siec v0.0.0-20191122224205-8da93652b094 h1:tZWtuLE+LbUwT4OP1oWBSB9zXA8qmQ5qEm4kV9R72oo=
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 h1:sDWDZkwYqX0jvLWstKzFwh+pYhQNaVg65BgSkCP/f7U= github.com/tscholl2/siec v0.0.0-20191122224205-8da93652b094/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw=
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tylerb/is.v1 v1.1.2 h1:AB/MANFml2ySf+adwcinvajyHvsYltAOD+rb/8njfSU=
gopkg.in/tylerb/is.v1 v1.1.2/go.mod h1:9yQB2tyIhZ5oph6Kk5Sq7cJMd9c5Jpa1p3hr9kxzPqo=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -21,10 +21,10 @@ build:
ignore: ignore:
- goos: darwin - goos: darwin
goarch: 386 goarch: 386
- goos: freebsd
goarch: arm
goarm: goarm:
- 7 - 7
hooks:
post: sh ./src/install/prepare-sources-tarball.sh
nfpms: nfpms:
- -
formats: formats:
@ -69,12 +69,10 @@ archives:
files: files:
- README.md - README.md
- LICENSE - LICENSE
- zsh_autocomplete
- bash_autocomplete
brews: brews:
- -
tap: github:
owner: schollz owner: schollz
name: homebrew-tap name: homebrew-tap
folder: Formula folder: Formula
@ -93,8 +91,4 @@ scoop:
homepage: "https://schollz.com/software/croc/" homepage: "https://schollz.com/software/croc/"
description: "croc is a tool that allows any two computers to simply and securely transfer files and folders." description: "croc is a tool that allows any two computers to simply and securely transfer files and folders."
license: MIT license: MIT
announce:
twitter:
# Wether its enabled or not.
# Defaults to false.
enabled: false

31
main.go
View File

@ -1,17 +1,14 @@
package main package main
//go:generate git tag -af v$VERSION -m "v$VERSION"
//go:generate go run src/install/updateversion.go //go:generate go run src/install/updateversion.go
//go:generate git commit -am "bump $VERSION" //go:generate git commit -am "bump $VERSION"
//go:generate git tag -af v$VERSION -m "v$VERSION" //go:generate git tag -af v$VERSION -m "v$VERSION"
import ( import (
"fmt" "fmt"
"os"
"os/signal"
"syscall"
"github.com/schollz/croc/v10/src/cli" "github.com/schollz/croc/v8/src/cli"
"github.com/schollz/croc/v10/src/utils"
) )
func main() { func main() {
@ -31,25 +28,7 @@ func main() {
// fmt.Println("wrote profile") // fmt.Println("wrote profile")
// } // }
// }() // }()
if err := cli.Run(); err != nil {
// Create a channel to receive OS signals fmt.Println(err)
sigs := make(chan os.Signal, 1) }
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := cli.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// Exit the program gracefully
utils.RemoveMarkedFiles()
os.Exit(0)
}()
// Wait for a termination signal
_ = <-sigs
utils.RemoveMarkedFiles()
// Exit the program gracefully
os.Exit(0)
} }

View File

@ -5,30 +5,26 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
"github.com/chzyer/readline" "github.com/schollz/croc/v8/src/croc"
"github.com/schollz/cli/v2" "github.com/schollz/croc/v8/src/models"
"github.com/schollz/croc/v10/src/comm" "github.com/schollz/croc/v8/src/tcp"
"github.com/schollz/croc/v10/src/croc" "github.com/schollz/croc/v8/src/utils"
"github.com/schollz/croc/v10/src/mnemonicode"
"github.com/schollz/croc/v10/src/models"
"github.com/schollz/croc/v10/src/tcp"
"github.com/schollz/croc/v10/src/utils"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/schollz/pake/v3" "github.com/urfave/cli"
) )
// Version specifies the version // Version specifies the version
var Version string var Version string
// Run will run the command line program // Run will run the command line proram
func Run() (err error) { func Run() (err error) {
// use all of the processors // use all of the processors
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
@ -36,197 +32,104 @@ func Run() (err error) {
app := cli.NewApp() app := cli.NewApp()
app.Name = "croc" app.Name = "croc"
if Version == "" { if Version == "" {
Version = "v10.1.0" Version = "v8.1.3-2096751"
} }
app.Version = Version app.Version = Version
app.Compiled = time.Now() app.Compiled = time.Now()
app.Usage = "easily and securely transfer stuff from one computer to another" app.Usage = "easily and securely transfer stuff from one computer to another"
app.UsageText = `croc [GLOBAL OPTIONS] [COMMAND] [COMMAND OPTIONS] [filename(s) or folder] app.UsageText = `Send a file:
USAGE EXAMPLES:
Send a file:
croc send file.txt croc send file.txt
-git to respect your .gitignore
Send multiple files:
croc send file1.txt file2.txt file3.txt
or
croc send *.jpg
Send everything in a folder:
croc send example-folder-name
Send a file with a custom code: Send a file with a custom code:
croc send --code secret-code file.txt croc send --code secret-passphrase file.txt`
app.Commands = []cli.Command{
Receive a file using code:
croc secret-code`
app.Commands = []*cli.Command{
{ {
Name: "send", Name: "send",
Usage: "send file(s), or folder (see options with croc send -h)", Usage: "send a file (see options with croc send -h)",
Description: "send file(s), or folder, over the relay", Description: "send a file over the relay",
ArgsUsage: "[filename(s) or folder]", ArgsUsage: "[filename]",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{Name: "zip", Usage: "zip folder before sending"}, cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"},
&cli.StringFlag{Name: "code", Aliases: []string{"c"}, Usage: "codephrase used to connect to relay"}, cli.StringFlag{Name: "text, t", Usage: "send some text"},
&cli.StringFlag{Name: "hash", Value: "xxhash", Usage: "hash algorithm (xxhash, imohash, md5)"}, cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"},
&cli.StringFlag{Name: "text", Aliases: []string{"t"}, Usage: "send some text"}, cli.BoolFlag{Name: "no-multi", Usage: "disable multiplexing"},
&cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"}, cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the local relay (optional)"},
&cli.BoolFlag{Name: "no-multi", Usage: "disable multiplexing"},
&cli.BoolFlag{Name: "git", Usage: "enable .gitignore respect / don't send ignored files"},
&cli.IntFlag{Name: "port", Value: 9009, Usage: "base port for the relay"},
&cli.IntFlag{Name: "transfers", Value: 4, Usage: "number of ports to use for transfers"},
&cli.BoolFlag{Name: "qrcode", Aliases: []string{"qr"}, Usage: "show receive code as a qrcode"},
}, },
HelpName: "croc send", HelpName: "croc send",
Action: send, Action: func(c *cli.Context) error {
return send(c)
},
}, },
{ {
Name: "relay", Name: "relay",
Usage: "start your own relay (optional)", Usage: "start your own relay (optional)",
Description: "start relay", Description: "start relay",
HelpName: "croc relay", HelpName: "croc relay",
Action: relay, Action: func(c *cli.Context) error {
return relay(c)
},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "host", Usage: "host of the relay"}, cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
&cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
&cli.IntFlag{Name: "port", Value: 9009, Usage: "base port for the relay"},
&cli.IntFlag{Name: "transfers", Value: 5, Usage: "number of ports to use for relay"},
}, },
}, },
} }
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
&cli.BoolFlag{Name: "internal-dns", Usage: "use a built-in DNS stub resolver rather than the host operating system"}, cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"},
&cli.BoolFlag{Name: "classic", Usage: "toggle between the classic mode (insecure due to local attack vector) and new mode (secure)"}, cli.BoolFlag{Name: "debug", Usage: "toggle debug mode"},
&cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"}, cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"},
&cli.BoolFlag{Name: "debug", Usage: "toggle debug mode"}, cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"},
&cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"}, cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"},
&cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"}, cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay"},
&cli.BoolFlag{Name: "no-compress", Usage: "disable compression"}, cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay"},
&cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"}, cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
&cli.BoolFlag{Name: "local", Usage: "force to use only local connections"}, cli.StringFlag{Name: "pass", Value: "pass123", Usage: "password for the relay"},
&cli.BoolFlag{Name: "ignore-stdin", Usage: "ignore piped stdin"},
&cli.BoolFlag{Name: "overwrite", Usage: "do not prompt to overwrite or resume"},
&cli.BoolFlag{Name: "testing", Usage: "flag for testing purposes"},
&cli.StringFlag{Name: "multicast", Value: "239.255.255.250", Usage: "multicast address to use for local discovery"},
&cli.StringFlag{Name: "curve", Value: "p256", Usage: "choose an encryption curve (" + strings.Join(pake.AvailableCurves(), ", ") + ")"},
&cli.StringFlag{Name: "ip", Value: "", Usage: "set sender ip if known e.g. 10.0.0.1:9009, [::1]:9009"},
&cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay", EnvVars: []string{"CROC_RELAY"}},
&cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay", EnvVars: []string{"CROC_RELAY6"}},
&cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
&cli.StringFlag{Name: "pass", Value: models.DEFAULT_PASSPHRASE, Usage: "password for the relay", EnvVars: []string{"CROC_PASS"}},
&cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}},
&cli.StringFlag{Name: "connect", Value: "", Usage: "add a http proxy", EnvVars: []string{"HTTP_PROXY"}},
&cli.StringFlag{Name: "throttleUpload", Value: "", Usage: "Throttle the upload speed e.g. 500k"},
} }
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.HideHelp = false app.HideHelp = false
app.HideVersion = false app.HideVersion = false
app.BashComplete = func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "send\nreceive\relay")
}
app.Action = func(c *cli.Context) error { app.Action = func(c *cli.Context) error {
allStringsAreFiles := func(strs []string) bool {
for _, str := range strs {
if !utils.Exists(str) {
return false
}
}
return true
}
// check if "classic" is set
classicFile := getClassicConfigFile(true)
classicInsecureMode := utils.Exists(classicFile)
if c.Bool("classic") {
if classicInsecureMode {
// classic mode not enabled
fmt.Print(`Classic mode is currently ENABLED.
Disabling this mode will prevent the shared secret from being visible
on the host's process list when passed via the command line. On a
multi-user system, this will help ensure that other local users cannot
access the shared secret and receive the files instead of the intended
recipient.
Do you wish to continue to DISABLE the classic mode? (y/N) `)
choice := strings.ToLower(utils.GetInput(""))
if choice == "y" || choice == "yes" {
os.Remove(classicFile)
fmt.Print("\nClassic mode DISABLED.\n\n")
fmt.Print(`To send and receive, export the CROC_SECRET variable with the code phrase:
Send: CROC_SECRET=*** croc send file.txt
Receive: CROC_SECRET=*** croc` + "\n\n")
} else {
fmt.Print("\nClassic mode ENABLED.\n")
}
} else {
// enable classic mode
// touch the file
fmt.Print(`Classic mode is currently DISABLED.
Please note that enabling this mode will make the shared secret visible
on the host's process list when passed via the command line. On a
multi-user system, this could allow other local users to access the
shared secret and receive the files instead of the intended recipient.
Do you wish to continue to enable the classic mode? (y/N) `)
choice := strings.ToLower(utils.GetInput(""))
if choice == "y" || choice == "yes" {
fmt.Print("\nClassic mode ENABLED.\n\n")
os.WriteFile(classicFile, []byte("enabled"), 0o644)
fmt.Print(`To send and receive, use the code phrase:
Send: croc send --code *** file.txt
Receive: croc ***` + "\n\n")
} else {
fmt.Print("\nClassic mode DISABLED.\n")
}
}
os.Exit(0)
}
// if trying to send but forgot send, let the user know // if trying to send but forgot send, let the user know
if c.Args().Present() && allStringsAreFiles(c.Args().Slice()) { if c.Args().First() != "" && utils.Exists(c.Args().First()) {
fnames := []string{} _, fname := filepath.Split(c.Args().First())
for _, fpath := range c.Args().Slice() { yn := utils.GetInput(fmt.Sprintf("Did you mean to send '%s'? (y/n) ", fname))
_, basename := filepath.Split(fpath) if strings.ToLower(yn) == "y" {
fnames = append(fnames, "'"+basename+"'")
}
promptMessage := fmt.Sprintf("Did you mean to send %s? (Y/n) ", strings.Join(fnames, ", "))
choice := strings.ToLower(utils.GetInput(promptMessage))
if choice == "" || choice == "y" || choice == "yes" {
return send(c) return send(c)
} }
} }
return receive(c) return receive(c)
} }
return app.Run(os.Args) return app.Run(os.Args)
} }
func getConfigDir() (homedir string, err error) {
homedir, err = os.UserHomeDir()
if err != nil {
log.Error(err)
return
}
homedir = path.Join(homedir, ".config", "croc")
if _, err = os.Stat(homedir); os.IsNotExist(err) {
log.Debugf("creating home directory %s", homedir)
err = os.MkdirAll(homedir, 0700)
}
return
}
func setDebugLevel(c *cli.Context) { func setDebugLevel(c *cli.Context) {
if c.Bool("debug") { if c.GlobalBool("debug") {
log.SetLevel("debug") log.SetLevel("debug")
log.Debug("debug mode on") log.Debug("debug mode on")
// print the public IP address
ip, err := utils.PublicIP()
if err == nil {
log.Debugf("public IP address: %s", ip)
} else {
log.Debug(err)
}
} else { } else {
log.SetLevel("info") log.SetLevel("info")
} }
} }
func getSendConfigFile(requireValidPath bool) string { func getConfigFile() string {
configFile, err := utils.GetConfigDir(requireValidPath) configFile, err := getConfigDir()
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return "" return ""
@ -234,85 +137,30 @@ func getSendConfigFile(requireValidPath bool) string {
return path.Join(configFile, "send.json") return path.Join(configFile, "send.json")
} }
func getClassicConfigFile(requireValidPath bool) string {
configFile, err := utils.GetConfigDir(requireValidPath)
if err != nil {
log.Error(err)
return ""
}
return path.Join(configFile, "classic_enabled")
}
func getReceiveConfigFile(requireValidPath bool) (string, error) {
configFile, err := utils.GetConfigDir(requireValidPath)
if err != nil {
log.Error(err)
return "", err
}
return path.Join(configFile, "receive.json"), nil
}
func determinePass(c *cli.Context) (pass string) {
pass = c.String("pass")
b, err := os.ReadFile(pass)
if err == nil {
pass = strings.TrimSpace(string(b))
}
return
}
func send(c *cli.Context) (err error) { func send(c *cli.Context) (err error) {
setDebugLevel(c) setDebugLevel(c)
comm.Socks5Proxy = c.String("socks5")
comm.HttpProxy = c.String("connect")
portParam := c.Int("port")
if portParam == 0 {
portParam = 9009
}
transfersParam := c.Int("transfers")
if transfersParam == 0 {
transfersParam = 4
}
ports := make([]string, transfersParam+1)
for i := 0; i <= transfersParam; i++ {
ports[i] = strconv.Itoa(portParam + i)
}
crocOptions := croc.Options{ crocOptions := croc.Options{
SharedSecret: c.String("code"), SharedSecret: c.String("code"),
IsSender: true, IsSender: true,
Debug: c.Bool("debug"), Debug: c.GlobalBool("debug"),
NoPrompt: c.Bool("yes"), NoPrompt: c.GlobalBool("yes"),
RelayAddress: c.String("relay"), RelayAddress: c.GlobalString("relay"),
RelayAddress6: c.String("relay6"), RelayAddress6: c.GlobalString("relay6"),
Stdout: c.Bool("stdout"), Stdout: c.GlobalBool("stdout"),
DisableLocal: c.Bool("no-local"), DisableLocal: c.Bool("no-local"),
OnlyLocal: c.Bool("local"), RelayPorts: strings.Split(c.String("ports"), ","),
IgnoreStdin: c.Bool("ignore-stdin"), Ask: c.GlobalBool("ask"),
RelayPorts: ports, NoMultiplexing: c.Bool("no-multi"),
Ask: c.Bool("ask"), RelayPassword: c.GlobalString("pass"),
NoMultiplexing: c.Bool("no-multi"), SendingText: c.String("text") != "",
RelayPassword: determinePass(c),
SendingText: c.String("text") != "",
NoCompress: c.Bool("no-compress"),
Overwrite: c.Bool("overwrite"),
Curve: c.String("curve"),
HashAlgorithm: c.String("hash"),
ThrottleUpload: c.String("throttleUpload"),
ZipFolder: c.Bool("zip"),
GitIgnore: c.Bool("git"),
ShowQrCode: c.Bool("qrcode"),
MulticastAddress: c.String("multicast"),
} }
if crocOptions.RelayAddress != models.DEFAULT_RELAY { if crocOptions.RelayAddress != models.DEFAULT_RELAY {
crocOptions.RelayAddress6 = "" crocOptions.RelayAddress6 = ""
} else if crocOptions.RelayAddress6 != models.DEFAULT_RELAY6 { } else if crocOptions.RelayAddress6 != models.DEFAULT_RELAY6 {
crocOptions.RelayAddress = "" crocOptions.RelayAddress = ""
} }
b, errOpen := os.ReadFile(getSendConfigFile(false)) b, errOpen := ioutil.ReadFile(getConfigFile())
if errOpen == nil && !c.Bool("remember") { if errOpen == nil && !c.GlobalBool("remember") {
var rememberedOptions croc.Options var rememberedOptions croc.Options
err = json.Unmarshal(b, &rememberedOptions) err = json.Unmarshal(b, &rememberedOptions)
if err != nil { if err != nil {
@ -320,53 +168,34 @@ func send(c *cli.Context) (err error) {
return return
} }
// update anything that isn't explicitly set // update anything that isn't explicitly set
if !c.IsSet("relay") && rememberedOptions.RelayAddress != "" { if !c.GlobalIsSet("relay") {
crocOptions.RelayAddress = rememberedOptions.RelayAddress crocOptions.RelayAddress = rememberedOptions.RelayAddress
} }
if !c.IsSet("no-local") { if !c.IsSet("no-local") {
crocOptions.DisableLocal = rememberedOptions.DisableLocal crocOptions.DisableLocal = rememberedOptions.DisableLocal
} }
if !c.IsSet("ports") && len(rememberedOptions.RelayPorts) > 0 { if !c.IsSet("ports") {
crocOptions.RelayPorts = rememberedOptions.RelayPorts crocOptions.RelayPorts = rememberedOptions.RelayPorts
} }
if !c.IsSet("code") { if !c.IsSet("code") {
crocOptions.SharedSecret = rememberedOptions.SharedSecret crocOptions.SharedSecret = rememberedOptions.SharedSecret
} }
if !c.IsSet("pass") && rememberedOptions.RelayPassword != "" { if !c.GlobalIsSet("pass") {
crocOptions.RelayPassword = rememberedOptions.RelayPassword crocOptions.RelayPassword = rememberedOptions.RelayPassword
} }
if !c.IsSet("relay6") && rememberedOptions.RelayAddress6 != "" {
crocOptions.RelayAddress6 = rememberedOptions.RelayAddress6
}
if !c.IsSet("overwrite") {
crocOptions.Overwrite = rememberedOptions.Overwrite
}
if !c.IsSet("curve") && rememberedOptions.Curve != "" {
crocOptions.Curve = rememberedOptions.Curve
}
if !c.IsSet("local") {
crocOptions.OnlyLocal = rememberedOptions.OnlyLocal
}
if !c.IsSet("hash") {
crocOptions.HashAlgorithm = rememberedOptions.HashAlgorithm
}
if !c.IsSet("git") {
crocOptions.GitIgnore = rememberedOptions.GitIgnore
}
} }
var fnames []string var fnames []string
stat, _ := os.Stdin.Stat() stat, _ := os.Stdin.Stat()
if ((stat.Mode() & os.ModeCharDevice) == 0) && !c.Bool("ignore-stdin") { if (stat.Mode() & os.ModeCharDevice) == 0 {
fnames, err = getStdin() fnames, err = getStdin()
if err != nil { if err != nil {
return return
} }
utils.MarkFileForRemoval(fnames[0])
defer func() { defer func() {
e := os.Remove(fnames[0]) err = os.Remove(fnames[0])
if e != nil { if err != nil {
log.Error(e) log.Error(err)
} }
}() }()
} else if c.String("text") != "" { } else if c.String("text") != "" {
@ -374,51 +203,26 @@ func send(c *cli.Context) (err error) {
if err != nil { if err != nil {
return return
} }
utils.MarkFileForRemoval(fnames[0])
defer func() { defer func() {
e := os.Remove(fnames[0]) err = os.Remove(fnames[0])
if e != nil { if err != nil {
log.Error(e) log.Error(err)
} }
}() }()
} else { } else {
fnames = c.Args().Slice() fnames = append([]string{c.Args().First()}, c.Args().Tail()...)
} }
if len(fnames) == 0 { if len(fnames) == 0 {
return errors.New("must specify file: croc send [filename(s) or folder]") return errors.New("must specify file: croc send [filename]")
}
classicInsecureMode := utils.Exists(getClassicConfigFile(true))
if !classicInsecureMode {
// if operating system is UNIX, then use environmental variable to set the code
if (!(runtime.GOOS == "windows") && c.IsSet("code")) || os.Getenv("CROC_SECRET") != "" {
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
if crocOptions.SharedSecret == "" {
fmt.Printf(`On UNIX systems, to send with a custom code phrase,
you need to set the environmental variable CROC_SECRET:
CROC_SECRET=**** croc send file.txt
Or you can have the code phrase automatically generated:
croc send file.txt
Or you can go back to the classic croc behavior by enabling classic mode:
croc --classic
`)
os.Exit(0)
}
}
} }
if len(crocOptions.SharedSecret) == 0 { if len(crocOptions.SharedSecret) == 0 {
// generate code phrase // generate code phrase
crocOptions.SharedSecret = utils.GetRandomName() crocOptions.SharedSecret = utils.GetRandomName()
} }
minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders, err := croc.GetFilesInfo(fnames, crocOptions.ZipFolder, crocOptions.GitIgnore)
paths, haveFolder, err := getPaths(fnames)
if err != nil { if err != nil {
return return
} }
@ -431,13 +235,16 @@ Or you can go back to the classic croc behavior by enabling classic mode:
// save the config // save the config
saveConfig(c, crocOptions) saveConfig(c, crocOptions)
err = cr.Send(minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders) err = cr.Send(croc.TransferOptions{
PathToFiles: paths,
KeepPathInRemote: haveFolder,
})
return return
} }
func getStdin() (fnames []string, err error) { func getStdin() (fnames []string, err error) {
f, err := os.CreateTemp(".", "croc-stdin-") f, err := ioutil.TempFile(".", "croc-stdin-")
if err != nil { if err != nil {
return return
} }
@ -454,7 +261,7 @@ func getStdin() (fnames []string, err error) {
} }
func makeTempFileWithString(s string) (fnames []string, err error) { func makeTempFileWithString(s string) (fnames []string, err error) {
f, err := os.CreateTemp(".", "croc-stdin-") f, err := ioutil.TempFile(".", "croc-stdin-")
if err != nil { if err != nil {
return return
} }
@ -470,11 +277,43 @@ func makeTempFileWithString(s string) (fnames []string, err error) {
} }
fnames = []string{f.Name()} fnames = []string{f.Name()}
return return
}
func getPaths(fnames []string) (paths []string, haveFolder bool, err error) {
haveFolder = false
paths = []string{}
for _, fname := range fnames {
stat, errStat := os.Stat(fname)
if errStat != nil {
err = errStat
return
}
if stat.IsDir() {
haveFolder = true
err = filepath.Walk(fname,
func(pathName string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
paths = append(paths, filepath.ToSlash(pathName))
}
return nil
})
if err != nil {
return
}
} else {
paths = append(paths, filepath.ToSlash(fname))
}
}
return
} }
func saveConfig(c *cli.Context, crocOptions croc.Options) { func saveConfig(c *cli.Context, crocOptions croc.Options) {
if c.Bool("remember") { if c.GlobalBool("remember") {
configFile := getSendConfigFile(true) configFile := getConfigFile()
log.Debug("saving config file") log.Debug("saving config file")
var bConfig []byte var bConfig []byte
// if the code wasn't set, don't save it // if the code wasn't set, don't save it
@ -486,7 +325,7 @@ func saveConfig(c *cli.Context, crocOptions croc.Options) {
log.Error(err) log.Error(err)
return return
} }
err = os.WriteFile(configFile, bConfig, 0o644) err = ioutil.WriteFile(configFile, bConfig, 0644)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return return
@ -495,55 +334,17 @@ func saveConfig(c *cli.Context, crocOptions croc.Options) {
} }
} }
type TabComplete struct{}
func (t TabComplete) Do(line []rune, pos int) ([][]rune, int) {
var words = strings.SplitAfter(string(line), "-")
var lastPartialWord = words[len(words)-1]
var nbCharacter = len(lastPartialWord)
if nbCharacter == 0 {
// No completion
return [][]rune{[]rune("")}, 0
}
if len(words) == 1 && nbCharacter == utils.NbPinNumbers {
// Check if word is indeed a number
_, err := strconv.Atoi(lastPartialWord)
if err == nil {
return [][]rune{[]rune("-")}, nbCharacter
}
}
var strArray [][]rune
for _, s := range mnemonicode.WordList {
if strings.HasPrefix(s, lastPartialWord) {
var completionCandidate = s[nbCharacter:]
if len(words) <= mnemonicode.WordsRequired(utils.NbBytesWords) {
completionCandidate += "-"
}
strArray = append(strArray, []rune(completionCandidate))
}
}
return strArray, nbCharacter
}
func receive(c *cli.Context) (err error) { func receive(c *cli.Context) (err error) {
comm.Socks5Proxy = c.String("socks5")
comm.HttpProxy = c.String("connect")
crocOptions := croc.Options{ crocOptions := croc.Options{
SharedSecret: c.String("code"), SharedSecret: c.String("code"),
IsSender: false, IsSender: false,
Debug: c.Bool("debug"), Debug: c.GlobalBool("debug"),
NoPrompt: c.Bool("yes"), NoPrompt: c.GlobalBool("yes"),
RelayAddress: c.String("relay"), RelayAddress: c.GlobalString("relay"),
RelayAddress6: c.String("relay6"), RelayAddress6: c.GlobalString("relay6"),
Stdout: c.Bool("stdout"), Stdout: c.GlobalBool("stdout"),
Ask: c.Bool("ask"), Ask: c.GlobalBool("ask"),
RelayPassword: determinePass(c), RelayPassword: c.GlobalString("pass"),
OnlyLocal: c.Bool("local"),
IP: c.String("ip"),
Overwrite: c.Bool("overwrite"),
Curve: c.String("curve"),
TestFlag: c.Bool("testing"),
MulticastAddress: c.String("multicast"),
} }
if crocOptions.RelayAddress != models.DEFAULT_RELAY { if crocOptions.RelayAddress != models.DEFAULT_RELAY {
crocOptions.RelayAddress6 = "" crocOptions.RelayAddress6 = ""
@ -551,12 +352,10 @@ func receive(c *cli.Context) (err error) {
crocOptions.RelayAddress = "" crocOptions.RelayAddress = ""
} }
switch c.Args().Len() { switch len(c.Args()) {
case 1: case 1:
crocOptions.SharedSecret = c.Args().First() crocOptions.SharedSecret = c.Args().First()
case 3: case 3:
fallthrough
case 4:
var phrase []string var phrase []string
phrase = append(phrase, c.Args().First()) phrase = append(phrase, c.Args().First())
phrase = append(phrase, c.Args().Tail()...) phrase = append(phrase, c.Args().Tail()...)
@ -565,87 +364,40 @@ func receive(c *cli.Context) (err error) {
// load options here // load options here
setDebugLevel(c) setDebugLevel(c)
configFile, err := getConfigDir()
doRemember := c.Bool("remember") if err != nil {
configFile, err := getReceiveConfigFile(doRemember) log.Error(err)
if err != nil && doRemember {
return return
} }
b, errOpen := os.ReadFile(configFile) configFile = path.Join(configFile, "receive.json")
if errOpen == nil && !doRemember { b, errOpen := ioutil.ReadFile(configFile)
if errOpen == nil && !c.GlobalBool("remember") {
var rememberedOptions croc.Options var rememberedOptions croc.Options
err = json.Unmarshal(b, &rememberedOptions) err = json.Unmarshal(b, &rememberedOptions)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return return
} }
// update anything that isn't explicitly Globally set // update anything that isn't explicitly set
if !c.IsSet("relay") && rememberedOptions.RelayAddress != "" { if !c.GlobalIsSet("relay") {
crocOptions.RelayAddress = rememberedOptions.RelayAddress crocOptions.RelayAddress = rememberedOptions.RelayAddress
} }
if !c.IsSet("yes") { if !c.GlobalIsSet("yes") {
crocOptions.NoPrompt = rememberedOptions.NoPrompt crocOptions.NoPrompt = rememberedOptions.NoPrompt
} }
if crocOptions.SharedSecret == "" { if crocOptions.SharedSecret == "" {
crocOptions.SharedSecret = rememberedOptions.SharedSecret crocOptions.SharedSecret = rememberedOptions.SharedSecret
} }
if !c.IsSet("pass") && rememberedOptions.RelayPassword != "" { if !c.GlobalIsSet("pass") {
crocOptions.RelayPassword = rememberedOptions.RelayPassword crocOptions.RelayPassword = rememberedOptions.RelayPassword
} }
if !c.IsSet("relay6") && rememberedOptions.RelayAddress6 != "" {
crocOptions.RelayAddress6 = rememberedOptions.RelayAddress6
}
if !c.IsSet("overwrite") {
crocOptions.Overwrite = rememberedOptions.Overwrite
}
if !c.IsSet("curve") && rememberedOptions.Curve != "" {
crocOptions.Curve = rememberedOptions.Curve
}
if !c.IsSet("local") {
crocOptions.OnlyLocal = rememberedOptions.OnlyLocal
}
} }
classicInsecureMode := utils.Exists(getClassicConfigFile(true))
if crocOptions.SharedSecret == "" && os.Getenv("CROC_SECRET") != "" {
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
} else if !(runtime.GOOS == "windows") && crocOptions.SharedSecret != "" && !classicInsecureMode {
crocOptions.SharedSecret = os.Getenv("CROC_SECRET")
if crocOptions.SharedSecret == "" {
fmt.Printf(`On UNIX systems, to receive with croc you either need
to set a code phrase using your environmental variables:
CROC_SECRET=**** croc
Or you can specify the code phrase when you run croc without
declaring the secret on the command line:
croc
Enter receive code: ****
Or you can go back to the classic croc behavior by enabling classic mode:
croc --classic
`)
os.Exit(0)
}
}
if crocOptions.SharedSecret == "" { if crocOptions.SharedSecret == "" {
l, err := readline.NewEx(&readline.Config{ crocOptions.SharedSecret = utils.GetInput("Enter receive code: ")
Prompt: "Enter receive code: ",
AutoComplete: TabComplete{},
})
if err != nil {
return err
}
crocOptions.SharedSecret, err = l.Readline()
if err != nil {
return err
}
} }
if c.String("out") != "" { if c.GlobalString("out") != "" {
if err = os.Chdir(c.String("out")); err != nil { if err = os.Chdir(c.GlobalString("out")); err != nil {
return err return err
} }
} }
@ -656,7 +408,7 @@ Or you can go back to the classic croc behavior by enabling classic mode:
} }
// save the config // save the config
if doRemember { if c.GlobalBool("remember") {
log.Debug("saving config file") log.Debug("saving config file")
var bConfig []byte var bConfig []byte
bConfig, err = json.MarshalIndent(crocOptions, "", " ") bConfig, err = json.MarshalIndent(crocOptions, "", " ")
@ -664,7 +416,7 @@ Or you can go back to the classic croc behavior by enabling classic mode:
log.Error(err) log.Error(err)
return return
} }
err = os.WriteFile(configFile, bConfig, 0o644) err = ioutil.WriteFile(configFile, bConfig, 0644)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return return
@ -679,40 +431,21 @@ Or you can go back to the classic croc behavior by enabling classic mode:
func relay(c *cli.Context) (err error) { func relay(c *cli.Context) (err error) {
log.Infof("starting croc relay version %v", Version) log.Infof("starting croc relay version %v", Version)
debugString := "info" debugString := "info"
if c.Bool("debug") { if c.GlobalBool("debug") {
debugString = "debug" debugString = "debug"
} }
host := c.String("host") ports := strings.Split(c.String("ports"), ",")
var ports []string
if c.IsSet("ports") {
ports = strings.Split(c.String("ports"), ",")
} else {
portString := c.Int("port")
if portString == 0 {
portString = 9009
}
transfersString := c.Int("transfers")
if transfersString == 0 {
transfersString = 4
}
ports = make([]string, transfersString)
for i := range ports {
ports[i] = strconv.Itoa(portString + i)
}
}
tcpPorts := strings.Join(ports[1:], ",") tcpPorts := strings.Join(ports[1:], ",")
for i, port := range ports { for i, port := range ports {
if i == 0 { if i == 0 {
continue continue
} }
go func(portStr string) { go func(portStr string) {
err := tcp.Run(debugString, host, portStr, determinePass(c)) err = tcp.Run(debugString, portStr, c.GlobalString("pass"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
}(port) }(port)
} }
return tcp.Run(debugString, host, ports[0], determinePass(c), tcpPorts) return tcp.Run(debugString, ports[0], c.GlobalString("pass"), tcpPorts)
} }

View File

@ -4,22 +4,13 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"net" "net"
"net/url"
"strings"
"time" "time"
"github.com/magisterquis/connectproxy"
"github.com/schollz/croc/v10/src/utils"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"golang.org/x/net/proxy"
) )
var Socks5Proxy = "" const MAXBYTES = 4000000
var HttpProxy = ""
var MAGIC_BYTES = []byte("croc")
// Comm is some basic TCP communication // Comm is some basic TCP communication
type Comm struct { type Comm struct {
@ -32,55 +23,9 @@ func NewConnection(address string, timelimit ...time.Duration) (c *Comm, err err
if len(timelimit) > 0 { if len(timelimit) > 0 {
tlimit = timelimit[0] tlimit = timelimit[0]
} }
var connection net.Conn connection, err := net.DialTimeout("tcp", address, tlimit)
if Socks5Proxy != "" && !utils.IsLocalIP(address) {
var dialer proxy.Dialer
// prepend schema if no schema is given
if !strings.Contains(Socks5Proxy, `://`) {
Socks5Proxy = `socks5://` + Socks5Proxy
}
socks5ProxyURL, urlParseError := url.Parse(Socks5Proxy)
if urlParseError != nil {
err = fmt.Errorf("unable to parse socks proxy url: %s", urlParseError)
log.Debug(err)
return
}
dialer, err = proxy.FromURL(socks5ProxyURL, proxy.Direct)
if err != nil {
err = fmt.Errorf("proxy failed: %w", err)
log.Debug(err)
return
}
log.Debug("dialing with dialer.Dial")
connection, err = dialer.Dial("tcp", address)
} else if HttpProxy != "" && !utils.IsLocalIP(address) {
var dialer proxy.Dialer
// prepend schema if no schema is given
if !strings.Contains(HttpProxy, `://`) {
HttpProxy = `http://` + HttpProxy
}
HttpProxyURL, urlParseError := url.Parse(HttpProxy)
if urlParseError != nil {
err = fmt.Errorf("unable to parse http proxy url: %s", urlParseError)
log.Debug(err)
return
}
dialer, err = connectproxy.New(HttpProxyURL, proxy.Direct)
if err != nil {
err = fmt.Errorf("proxy failed: %w", err)
log.Debug(err)
return
}
log.Debug("dialing with dialer.Dial")
connection, err = dialer.Dial("tcp", address)
} else {
log.Debugf("dialing to %s with timelimit %s", address, tlimit)
connection, err = net.DialTimeout("tcp", address, tlimit)
}
if err != nil { if err != nil {
err = fmt.Errorf("comm.NewConnection failed: %w", err) err = fmt.Errorf("comm.NewConnection failed: %w", err)
log.Debug(err)
return return
} }
c = New(connection) c = New(connection)
@ -116,52 +61,46 @@ func (c *Comm) Close() {
} }
} }
func (c *Comm) Write(b []byte) (n int, err error) { func (c *Comm) Write(b []byte) (int, error) {
header := new(bytes.Buffer) header := new(bytes.Buffer)
err = binary.Write(header, binary.LittleEndian, uint32(len(b))) err := binary.Write(header, binary.LittleEndian, uint32(len(b)))
if err != nil { if err != nil {
fmt.Println("binary.Write failed:", err) fmt.Println("binary.Write failed:", err)
} }
tmpCopy := append(header.Bytes(), b...) tmpCopy := append(header.Bytes(), b...)
tmpCopy = append(MAGIC_BYTES, tmpCopy...) n, err := c.connection.Write(tmpCopy)
n, err = c.connection.Write(tmpCopy)
if err != nil {
err = fmt.Errorf("connection.Write failed: %w", err)
return
}
if n != len(tmpCopy) { if n != len(tmpCopy) {
err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n) if err != nil {
return err = fmt.Errorf("wanted to write %d but wrote %d: %w", len(b), n, err)
} else {
err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n)
}
} }
return // log.Printf("wanted to write %d but wrote %d", n, len(b))
return n, err
} }
func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) { func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
// long read deadline in case waiting for file // long read deadline in case waiting for file
if err = c.connection.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil { if err := c.connection.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil {
log.Warnf("error setting read deadline: %v", err) log.Warnf("error setting read deadline: %v", err)
} }
// must clear the timeout setting
defer c.connection.SetDeadline(time.Time{})
// read until we get 4 bytes for the magic
header := make([]byte, 4)
_, err = io.ReadFull(c.connection, header)
if err != nil {
log.Debugf("initial read error: %v", err)
return
}
if !bytes.Equal(header, MAGIC_BYTES) {
err = fmt.Errorf("initial bytes are not magic: %x", header)
return
}
// read until we get 4 bytes for the header // read until we get 4 bytes for the header
header = make([]byte, 4) var header []byte
_, err = io.ReadFull(c.connection, header) numBytes = 4
if err != nil { for {
log.Debugf("initial read error: %v", err) tmp := make([]byte, numBytes-len(header))
return n, errRead := c.connection.Read(tmp)
if errRead != nil {
err = errRead
log.Debugf("initial read error: %v", err)
return
}
header = append(header, tmp[:n]...)
if numBytes == len(header) {
break
}
} }
var numBytesUint32 uint32 var numBytesUint32 uint32
@ -173,16 +112,30 @@ func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
return return
} }
numBytes = int(numBytesUint32) numBytes = int(numBytesUint32)
if numBytes > MAXBYTES {
err = fmt.Errorf("too many bytes: %d", numBytes)
log.Debug(err)
return
}
buf = make([]byte, 0)
// shorten the reading deadline in case getting weird data // shorten the reading deadline in case getting weird data
if err = c.connection.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { if err := c.connection.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil {
log.Warnf("error setting read deadline: %v", err) log.Warnf("error setting read deadline: %v", err)
} }
buf = make([]byte, numBytes) for {
_, err = io.ReadFull(c.connection, buf) // log.Debugf("bytes: %d/%d", len(buf), numBytes)
if err != nil { tmp := make([]byte, numBytes-len(buf))
log.Debugf("consecutive read error: %v", err) n, errRead := c.connection.Read(tmp)
return if errRead != nil {
err = errRead
log.Debugf("consecutive read error: %v", err)
return
}
buf = append(buf, tmp[:n]...)
if numBytes == len(buf) {
break
}
} }
return return
} }

View File

@ -11,7 +11,7 @@ import (
) )
func TestComm(t *testing.T) { func TestComm(t *testing.T) {
token := make([]byte, 3000) token := make([]byte, MAXBYTES)
if _, err := rand.Read(token); err != nil { if _, err := rand.Read(token); err != nil {
t.Error(err) t.Error(err)
} }
@ -31,7 +31,7 @@ func TestComm(t *testing.T) {
log.Error(err) log.Error(err)
} }
log.Debugf("client %s connected", connection.RemoteAddr().String()) log.Debugf("client %s connected", connection.RemoteAddr().String())
go func(_ string, connection net.Conn) { go func(port string, connection net.Conn) {
c := New(connection) c := New(connection)
err = c.Send([]byte("hello, world")) err = c.Send([]byte("hello, world"))
assert.Nil(t, err) assert.Nil(t, err)
@ -49,7 +49,7 @@ func TestComm(t *testing.T) {
}() }()
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
a, err := NewConnection("127.0.0.1:"+port, 10*time.Minute) a, err := NewConnection("localhost:"+port, 10*time.Minute)
assert.Nil(t, err) assert.Nil(t, err)
data, err := a.Receive() data, err := a.Receive()
assert.Equal(t, []byte("hello, world"), data) assert.Equal(t, []byte("hello, world"), data)
@ -63,4 +63,5 @@ func TestComm(t *testing.T) {
assert.NotNil(t, a.Send(token)) assert.NotNil(t, a.Send(token))
_, err = a.Write(token) _, err = a.Write(token)
assert.NotNil(t, err) assert.NotNil(t, err)
} }

View File

@ -32,13 +32,9 @@ func Decompress(src []byte) []byte {
// compress uses flate to compress a byte slice to a corresponding level // compress uses flate to compress a byte slice to a corresponding level
func compress(src []byte, dest io.Writer, level int) { func compress(src []byte, dest io.Writer, level int) {
compressor, err := flate.NewWriter(dest, level) compressor, _ := flate.NewWriter(dest, level)
if err != nil {
log.Debugf("error level data: %v", err)
return
}
if _, err := compressor.Write(src); err != nil { if _, err := compressor.Write(src); err != nil {
log.Debugf("error writing data: %v", err) log.Errorf("error writing data: %v", err)
} }
compressor.Close() compressor.Close()
} }
@ -47,7 +43,7 @@ func compress(src []byte, dest io.Writer, level int) {
func decompress(src io.Reader, dest io.Writer) { func decompress(src io.Reader, dest io.Writer) {
decompressor := flate.NewReader(src) decompressor := flate.NewReader(src)
if _, err := io.Copy(dest, decompressor); err != nil { if _, err := io.Copy(dest, decompressor); err != nil {
log.Debugf("error copying data: %v", err) log.Errorf("error copying data: %v", err)
} }
decompressor.Close() decompressor.Close()
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,31 @@
package croc package croc
import ( import (
"io/ioutil"
"os" "os"
"path"
"path/filepath"
"runtime"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/schollz/croc/v10/src/tcp" "github.com/schollz/croc/v8/src/tcp"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func must(err error) {
if err != nil {
panic(err)
}
}
func init() { func init() {
log.SetLevel("trace") log.SetLevel("trace")
go tcp.Run("debug", "127.0.0.1", "8281", "pass123", "8282,8283,8284,8285") go tcp.Run("debug", "8081", "pass123", "8082,8083,8084,8085")
go tcp.Run("debug", "127.0.0.1", "8282", "pass123") go tcp.Run("debug", "8082", "pass123")
go tcp.Run("debug", "127.0.0.1", "8283", "pass123") go tcp.Run("debug", "8083", "pass123")
go tcp.Run("debug", "127.0.0.1", "8284", "pass123") go tcp.Run("debug", "8084", "pass123")
go tcp.Run("debug", "127.0.0.1", "8285", "pass123") go tcp.Run("debug", "8085", "pass123")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
@ -32,17 +35,14 @@ func TestCrocReadme(t *testing.T) {
log.Debug("setting up sender") log.Debug("setting up sender")
sender, err := New(Options{ sender, err := New(Options{
IsSender: true, IsSender: true,
SharedSecret: "8123-testingthecroc", SharedSecret: "test",
Debug: true, Debug: true,
RelayAddress: "127.0.0.1:8281", RelayAddress: "localhost:8081",
RelayPorts: []string{"8281"}, RelayPorts: []string{"8081"},
RelayPassword: "pass123", RelayPassword: "pass123",
Stdout: false, Stdout: false,
NoPrompt: true, NoPrompt: true,
DisableLocal: true, DisableLocal: true,
Curve: "siec",
Overwrite: true,
GitIgnore: false,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -51,15 +51,13 @@ func TestCrocReadme(t *testing.T) {
log.Debug("setting up receiver") log.Debug("setting up receiver")
receiver, err := New(Options{ receiver, err := New(Options{
IsSender: false, IsSender: false,
SharedSecret: "8123-testingthecroc", SharedSecret: "test",
Debug: true, Debug: true,
RelayAddress: "127.0.0.1:8281", RelayAddress: "localhost:8081",
RelayPassword: "pass123", RelayPassword: "pass123",
Stdout: false, Stdout: false,
NoPrompt: true, NoPrompt: true,
DisableLocal: true, DisableLocal: true,
Curve: "siec",
Overwrite: true,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -68,11 +66,9 @@ func TestCrocReadme(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
go func() { go func() {
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{"../../README.md"}, false, false) err := sender.Send(TransferOptions{
if errGet != nil { PathToFiles: []string{"../../README.md"},
t.Errorf("failed to get minimal info: %v", errGet) })
}
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
if err != nil { if err != nil {
t.Errorf("send failed: %v", err) t.Errorf("send failed: %v", err)
} }
@ -90,175 +86,6 @@ func TestCrocReadme(t *testing.T) {
wg.Wait() wg.Wait()
} }
func TestCrocEmptyFolder(t *testing.T) {
pathName := "../../testEmpty"
defer os.RemoveAll(pathName)
defer os.RemoveAll("./testEmpty")
os.MkdirAll(pathName, 0o755)
log.Debug("setting up sender")
sender, err := New(Options{
IsSender: true,
SharedSecret: "8123-testingthecroc",
Debug: true,
RelayAddress: "127.0.0.1:8281",
RelayPorts: []string{"8281"},
RelayPassword: "pass123",
Stdout: false,
NoPrompt: true,
DisableLocal: true,
Curve: "siec",
Overwrite: true,
})
if err != nil {
panic(err)
}
log.Debug("setting up receiver")
receiver, err := New(Options{
IsSender: false,
SharedSecret: "8123-testingthecroc",
Debug: true,
RelayAddress: "127.0.0.1:8281",
RelayPassword: "pass123",
Stdout: false,
NoPrompt: true,
DisableLocal: true,
Curve: "siec",
Overwrite: true,
})
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{pathName}, false, false)
if errGet != nil {
t.Errorf("failed to get minimal info: %v", errGet)
}
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
if err != nil {
t.Errorf("send failed: %v", err)
}
wg.Done()
}()
time.Sleep(100 * time.Millisecond)
go func() {
err := receiver.Receive()
if err != nil {
t.Errorf("receive failed: %v", err)
}
wg.Done()
}()
wg.Wait()
}
func TestCrocSymlink(t *testing.T) {
pathName := "../link-in-folder"
defer os.RemoveAll(pathName)
defer os.RemoveAll("./link-in-folder")
os.MkdirAll(pathName, 0o755)
os.Symlink("../../README.md", filepath.Join(pathName, "README.link"))
log.Debug("setting up sender")
sender, err := New(Options{
IsSender: true,
SharedSecret: "8124-testingthecroc",
Debug: true,
RelayAddress: "127.0.0.1:8281",
RelayPorts: []string{"8281"},
RelayPassword: "pass123",
Stdout: false,
NoPrompt: true,
DisableLocal: true,
Curve: "siec",
Overwrite: true,
GitIgnore: false,
})
if err != nil {
panic(err)
}
log.Debug("setting up receiver")
receiver, err := New(Options{
IsSender: false,
SharedSecret: "8124-testingthecroc",
Debug: true,
RelayAddress: "127.0.0.1:8281",
RelayPassword: "pass123",
Stdout: false,
NoPrompt: true,
DisableLocal: true,
Curve: "siec",
Overwrite: true,
})
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{pathName}, false, false)
if errGet != nil {
t.Errorf("failed to get minimal info: %v", errGet)
}
err = sender.Send(filesInfo, emptyFolders, totalNumberFolders)
if err != nil {
t.Errorf("send failed: %v", err)
}
wg.Done()
}()
time.Sleep(100 * time.Millisecond)
go func() {
err = receiver.Receive()
if err != nil {
t.Errorf("receive failed: %v", err)
}
wg.Done()
}()
wg.Wait()
s, err := filepath.EvalSymlinks(path.Join(pathName, "README.link"))
if s != "../../README.md" && s != "..\\..\\README.md" {
log.Debug(s)
t.Errorf("symlink failed to transfer in folder")
}
if err != nil {
t.Errorf("symlink transfer failed: %s", err.Error())
}
}
func TestCrocIgnoreGit(t *testing.T) {
log.SetLevel("trace")
defer os.Remove(".gitignore")
time.Sleep(300 * time.Millisecond)
time.Sleep(1 * time.Second)
file, err := os.Create(".gitignore")
if err != nil {
log.Errorf("error creating file")
}
_, err = file.WriteString("LICENSE")
if err != nil {
log.Errorf("error writing to file")
}
time.Sleep(1 * time.Second)
// due to how files are ignored in this function, all we have to do to test is make sure LICENSE doesn't get included in FilesInfo.
filesInfo, _, _, errGet := GetFilesInfo([]string{"../../LICENSE", ".gitignore", "croc.go"}, false, true)
if errGet != nil {
t.Errorf("failed to get minimal info: %v", errGet)
}
for _, file := range filesInfo {
if strings.Contains(file.Name, "LICENSE") {
t.Errorf("test failed, should ignore LICENSE")
}
}
}
func TestCrocLocal(t *testing.T) { func TestCrocLocal(t *testing.T) {
log.SetLevel("trace") log.SetLevel("trace")
defer os.Remove("LICENSE") defer os.Remove("LICENSE")
@ -268,17 +95,14 @@ func TestCrocLocal(t *testing.T) {
log.Debug("setting up sender") log.Debug("setting up sender")
sender, err := New(Options{ sender, err := New(Options{
IsSender: true, IsSender: true,
SharedSecret: "8123-testingthecroc", SharedSecret: "test",
Debug: true, Debug: true,
RelayAddress: "127.0.0.1:8181", RelayAddress: "localhost:8181",
RelayPorts: []string{"8181", "8182"}, RelayPorts: []string{"8181", "8182"},
RelayPassword: "pass123", RelayPassword: "pass123",
Stdout: true, Stdout: true,
NoPrompt: true, NoPrompt: true,
DisableLocal: false, DisableLocal: false,
Curve: "siec",
Overwrite: true,
GitIgnore: false,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -288,15 +112,13 @@ func TestCrocLocal(t *testing.T) {
log.Debug("setting up receiver") log.Debug("setting up receiver")
receiver, err := New(Options{ receiver, err := New(Options{
IsSender: false, IsSender: false,
SharedSecret: "8123-testingthecroc", SharedSecret: "test",
Debug: true, Debug: true,
RelayAddress: "127.0.0.1:8181", RelayAddress: "localhost:8181",
RelayPassword: "pass123", RelayPassword: "pass123",
Stdout: true, Stdout: true,
NoPrompt: true, NoPrompt: true,
DisableLocal: false, DisableLocal: false,
Curve: "siec",
Overwrite: true,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -306,11 +128,10 @@ func TestCrocLocal(t *testing.T) {
os.Create("touched") os.Create("touched")
wg.Add(2) wg.Add(2)
go func() { go func() {
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{"../../LICENSE", "touched"}, false, false) err := sender.Send(TransferOptions{
if errGet != nil { PathToFiles: []string{"../../LICENSE", "touched"},
t.Errorf("failed to get minimal info: %v", errGet) KeepPathInRemote: false,
} })
err := sender.Send(filesInfo, emptyFolders, totalNumberFolders)
if err != nil { if err != nil {
t.Errorf("send failed: %v", err) t.Errorf("send failed: %v", err)
} }
@ -330,17 +151,17 @@ func TestCrocLocal(t *testing.T) {
func TestCrocError(t *testing.T) { func TestCrocError(t *testing.T) {
content := []byte("temporary file's content") content := []byte("temporary file's content")
tmpfile, err := os.CreateTemp("", "example") tmpfile, err := ioutil.TempFile("", "example")
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer os.Remove(tmpfile.Name()) // clean up defer os.Remove(tmpfile.Name()) // clean up
if _, err = tmpfile.Write(content); err != nil { if _, err := tmpfile.Write(content); err != nil {
panic(err) panic(err)
} }
if err = tmpfile.Close(); err != nil { if err := tmpfile.Close(); err != nil {
panic(err) panic(err)
} }
@ -348,7 +169,7 @@ func TestCrocError(t *testing.T) {
log.SetLevel("warn") log.SetLevel("warn")
sender, _ := New(Options{ sender, _ := New(Options{
IsSender: true, IsSender: true,
SharedSecret: "8123-testingthecroc2", SharedSecret: "test33",
Debug: true, Debug: true,
RelayAddress: "doesntexistok.com:8381", RelayAddress: "doesntexistok.com:8381",
RelayPorts: []string{"8381", "8382"}, RelayPorts: []string{"8381", "8382"},
@ -356,45 +177,12 @@ func TestCrocError(t *testing.T) {
Stdout: true, Stdout: true,
NoPrompt: true, NoPrompt: true,
DisableLocal: true, DisableLocal: true,
Curve: "siec",
Overwrite: true,
}) })
filesInfo, emptyFolders, totalNumberFolders, errGet := GetFilesInfo([]string{tmpfile.Name()}, false, false) err = sender.Send(TransferOptions{
if errGet != nil { PathToFiles: []string{tmpfile.Name()},
t.Errorf("failed to get minimal info: %v", errGet) KeepPathInRemote: true,
} })
err = sender.Send(filesInfo, emptyFolders, totalNumberFolders)
log.Debug(err) log.Debug(err)
assert.NotNil(t, err) assert.NotNil(t, err)
}
func TestCleanUp(t *testing.T) {
// windows allows files to be deleted only if they
// are not open by another program so the remove actions
// from the above tests will not always do a good clean up
// This "test" will make sure
operatingSystem := runtime.GOOS
log.Debugf("The operating system is %s", operatingSystem)
if operatingSystem == "windows" {
time.Sleep(1 * time.Second)
log.Debug("Full cleanup")
var err error
for _, file := range []string{"README.md", "./README.md"} {
err = os.Remove(file)
if err == nil {
log.Debugf("Successfully purged %s", file)
} else {
log.Debugf("%s was already purged.", file)
}
}
for _, folder := range []string{"./testEmpty", "./link-in-folder"} {
err = os.RemoveAll(folder)
if err == nil {
log.Debugf("Successfully purged %s", folder)
} else {
log.Debugf("%s was already purged.", folder)
}
}
}
} }

View File

@ -8,8 +8,6 @@ import (
"fmt" "fmt"
"log" "log"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
@ -39,7 +37,7 @@ func Encrypt(plaintext []byte, key []byte) (encrypted []byte, err error) {
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
// Section 8.2 // Section 8.2
ivBytes := make([]byte, 12) ivBytes := make([]byte, 12)
if _, err = rand.Read(ivBytes); err != nil { if _, err := rand.Read(ivBytes); err != nil {
log.Fatalf("can't initialize crypto: %v", err) log.Fatalf("can't initialize crypto: %v", err)
} }
b, err := aes.NewCipher(key) b, err := aes.NewCipher(key)
@ -72,54 +70,3 @@ func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) {
plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil) plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil)
return return
} }
// NewArgon2 generates a new key based on a passphrase and salt
// using argon2
// https://pkg.go.dev/golang.org/x/crypto/argon2
func NewArgon2(passphrase []byte, usersalt []byte) (aead cipher.AEAD, salt []byte, err error) {
if len(passphrase) < 1 {
err = fmt.Errorf("need more than that for passphrase")
return
}
if usersalt == nil {
salt = make([]byte, 8)
// http://www.ietf.org/rfc/rfc2898.txt
// Salt.
if _, err = rand.Read(salt); err != nil {
log.Fatalf("can't get random salt: %v", err)
}
} else {
salt = usersalt
}
aead, err = chacha20poly1305.NewX(argon2.IDKey(passphrase, salt, 1, 64*1024, 4, 32))
return
}
// EncryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
func EncryptChaCha(plaintext []byte, aead cipher.AEAD) (encrypted []byte, err error) {
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
if _, err := rand.Read(nonce); err != nil {
panic(err)
}
// Encrypt the message and append the ciphertext to the nonce.
encrypted = aead.Seal(nonce, nonce, plaintext, nil)
return
}
// DecryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
func DecryptChaCha(encryptedMsg []byte, aead cipher.AEAD) (encrypted []byte, err error) {
if len(encryptedMsg) < aead.NonceSize() {
err = fmt.Errorf("ciphertext too short")
return
}
// Split nonce and ciphertext.
nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
// Decrypt the message and check it wasn't tampered with.
encrypted, err = aead.Open(nil, nonce, ciphertext, nil)
return
}

View File

@ -1,7 +1,6 @@
package crypt package crypt
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -24,37 +23,6 @@ func BenchmarkDecrypt(b *testing.B) {
} }
} }
func BenchmarkNewPbkdf2(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
New([]byte("password"), nil)
}
}
func BenchmarkNewArgon2(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewArgon2([]byte("password"), nil)
}
}
func BenchmarkEncryptChaCha(b *testing.B) {
bob, _, _ := NewArgon2([]byte("password"), nil)
for i := 0; i < b.N; i++ {
EncryptChaCha([]byte("hello, world"), bob)
}
}
func BenchmarkDecryptChaCha(b *testing.B) {
key, _, _ := NewArgon2([]byte("password"), nil)
msg := []byte("hello, world")
enc, _ := EncryptChaCha(msg, key)
b.ResetTimer()
for i := 0; i < b.N; i++ {
DecryptChaCha(enc, key)
}
}
func TestEncryption(t *testing.T) { func TestEncryption(t *testing.T) {
key, salt, err := New([]byte("password"), nil) key, salt, err := New([]byte("password"), nil)
assert.Nil(t, err) assert.Nil(t, err)
@ -66,54 +34,22 @@ func TestEncryption(t *testing.T) {
assert.Equal(t, msg, dec) assert.Equal(t, msg, dec)
// check reusing the salt // check reusing the salt
key2, _, _ := New([]byte("password"), salt) key2, _, err := New([]byte("password"), salt)
dec, err = Decrypt(enc, key2) dec, err = Decrypt(enc, key2)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, msg, dec) assert.Equal(t, msg, dec)
// check reusing the salt // check reusing the salt
key2, _, _ = New([]byte("wrong password"), salt) key2, _, err = New([]byte("wrong password"), salt)
dec, err = Decrypt(enc, key2) dec, err = Decrypt(enc, key2)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.NotEqual(t, msg, dec) assert.NotEqual(t, msg, dec)
// error with no password // error with no password
_, err = Decrypt([]byte(""), key) dec, err = Decrypt([]byte(""), key)
assert.NotNil(t, err) assert.NotNil(t, err)
// error with small password // error with small password
_, _, err = New([]byte(""), nil) _, _, err = New([]byte(""), nil)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestEncryptionChaCha(t *testing.T) {
key, salt, err := NewArgon2([]byte("password"), nil)
fmt.Printf("key: %x\n", key)
assert.Nil(t, err)
msg := []byte("hello, world")
enc, err := EncryptChaCha(msg, key)
assert.Nil(t, err)
dec, err := DecryptChaCha(enc, key)
assert.Nil(t, err)
assert.Equal(t, msg, dec)
// check reusing the salt
key2, _, _ := NewArgon2([]byte("password"), salt)
dec, err = DecryptChaCha(enc, key2)
assert.Nil(t, err)
assert.Equal(t, msg, dec)
// check reusing the salt
key2, _, _ = NewArgon2([]byte("wrong password"), salt)
dec, err = DecryptChaCha(enc, key2)
assert.NotNil(t, err)
assert.NotEqual(t, msg, dec)
// error with no password
_, err = DecryptChaCha([]byte(""), key)
assert.NotNil(t, err)
// error with small password
_, _, err = NewArgon2([]byte(""), nil)
assert.NotNil(t, err)
}

View File

@ -1,49 +0,0 @@
//go:build !windows
// +build !windows
package diskusage
import (
"golang.org/x/sys/unix"
)
// DiskUsage contains usage data and provides user-friendly access methods
type DiskUsage struct {
stat *unix.Statfs_t
}
// NewDiskUsage returns an object holding the disk usage of volumePath
// or nil in case of error (invalid path, etc)
func NewDiskUsage(volumePath string) *DiskUsage {
stat := unix.Statfs_t{}
err := unix.Statfs(volumePath, &stat)
if err != nil {
return nil
}
return &DiskUsage{&stat}
}
// Free returns total free bytes on file system
func (du *DiskUsage) Free() uint64 {
return uint64(du.stat.Bfree) * uint64(du.stat.Bsize)
}
// Available return total available bytes on file system to an unprivileged user
func (du *DiskUsage) Available() uint64 {
return uint64(du.stat.Bavail) * uint64(du.stat.Bsize)
}
// Size returns total size of the file system
func (du *DiskUsage) Size() uint64 {
return uint64(du.stat.Blocks) * uint64(du.stat.Bsize)
}
// Used returns total bytes used in file system
func (du *DiskUsage) Used() uint64 {
return du.Size() - du.Free()
}
// Usage returns percentage of use on the file system
func (du *DiskUsage) Usage() float32 {
return float32(du.Used()) / float32(du.Size())
}

View File

@ -1,17 +0,0 @@
package diskusage
import (
"fmt"
"testing"
)
var KB = uint64(1024)
func TestNewDiskUsage(t *testing.T) {
usage := NewDiskUsage(".")
fmt.Println("Free:", usage.Free()/(KB*KB))
fmt.Println("Available:", usage.Available()/(KB*KB))
fmt.Println("Size:", usage.Size()/(KB*KB))
fmt.Println("Used:", usage.Used()/(KB*KB))
fmt.Println("Usage:", usage.Usage()*100, "%")
}

View File

@ -1,55 +0,0 @@
package diskusage
import (
"unsafe"
"golang.org/x/sys/windows"
)
type DiskUsage struct {
freeBytes int64
totalBytes int64
availBytes int64
}
// NewDiskUsage returns an object holding the disk usage of volumePath
// or nil in case of error (invalid path, etc)
func NewDiskUsage(volumePath string) *DiskUsage {
h := windows.MustLoadDLL("kernel32.dll")
c := h.MustFindProc("GetDiskFreeSpaceExW")
du := &DiskUsage{}
c.Call(
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(volumePath))),
uintptr(unsafe.Pointer(&du.freeBytes)),
uintptr(unsafe.Pointer(&du.totalBytes)),
uintptr(unsafe.Pointer(&du.availBytes)))
return du
}
// Free returns total free bytes on file system
func (du *DiskUsage) Free() uint64 {
return uint64(du.freeBytes)
}
// Available returns total available bytes on file system to an unprivileged user
func (du *DiskUsage) Available() uint64 {
return uint64(du.availBytes)
}
// Size returns total size of the file system
func (du *DiskUsage) Size() uint64 {
return uint64(du.totalBytes)
}
// Used returns total bytes used in file system
func (du *DiskUsage) Used() uint64 {
return du.Size() - du.Free()
}
// Usage returns percentage of use on the file system
func (du *DiskUsage) Usage() float32 {
return float32(du.Used()) / float32(du.Size())
}

View File

@ -1,19 +1,8 @@
# VERSION=8.X.Y make release # VERSION=8.X.Y make release
release: release:
cd ../../ && go run src/install/updateversion.go cd ../../ && go generate
git commit -am "bump ${VERSION}"
git tag -af v${VERSION} -m "v${VERSION}"
git push
git push --tags
cp zsh_autocomplete ../../
cp bash_autocomplete ../../
cd ../../ && goreleaser release cd ../../ && goreleaser release
cd ../../ && ./src/install/prepare-sources-tarball.sh cd ../../ && ./src/install/prepare-sources-tarball.sh
cd ../../ && ./src/install/upload-src-tarball.sh cd ../../ && ./src/install/upload-src-tarball.sh
git push
test:
cp zsh_autocomplete ../../
cp bash_autocomplete ../../
cd ../../ && go generate
cd ../../ && goreleaser release --skip-publish

View File

@ -1,19 +0,0 @@
: ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
unset PROG

View File

@ -23,7 +23,7 @@
# Issues: https://github.com/schollz/croc/issues # Issues: https://github.com/schollz/croc/issues
# #
# CREATED: 08/10/2019 16:41 # CREATED: 08/10/2019 16:41
# REVISION: 0.9.2 # REVISION: 0.9.1
#=============================================================================== #===============================================================================
set -o nounset # Treat unset variables as an error set -o nounset # Treat unset variables as an error
@ -86,7 +86,7 @@ print_help() {
Default = /usr/local/bin ('\${PREFIX}/bin' on Termux for Android) Default = /usr/local/bin ('\${PREFIX}/bin' on Termux for Android)
-h -h
Prints this helpful message and exit." Prints this helpfull message and exit."
echo "${help_header}" echo "${help_header}"
echo "" echo ""
@ -156,10 +156,10 @@ make_tempdir() {
#--- FUNCTION ---------------------------------------------------------------- #--- FUNCTION ----------------------------------------------------------------
# NAME: determine_os # NAME: determine_os
# DESCRIPTION: Attempts to determine host os using uname # DESCRIPTION: Attempts to determin host os using uname
# PARAMETERS: none # PARAMETERS: none
# RETURNS: 0 = OS Detected. Also prints detected os to stdout # RETURNS: 0 = OS Detected. Also prints detected os to stdout
# 1 = Unknown OS # 1 = Unkown OS
# 20 = 'uname' not found in path # 20 = 'uname' not found in path
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
determine_os() { determine_os() {
@ -180,10 +180,10 @@ determine_os() {
#--- FUNCTION ---------------------------------------------------------------- #--- FUNCTION ----------------------------------------------------------------
# NAME: determine_arch # NAME: determine_arch
# DESCRIPTION: Attempt to determine architecture of host # DESCRIPTION: Attempt to determin architecture of host
# PARAMETERS: none # PARAMETERS: none
# RETURNS: 0 = Arch Detected. Also prints detected arch to stdout # RETURNS: 0 = Arch Detected. Also prints detected arch to stdout
# 1 = Unknown arch # 1 = Unkown arch
# 20 = 'uname' not found in path # 20 = 'uname' not found in path
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
determine_arch() { determine_arch() {
@ -238,7 +238,7 @@ download_file() {
#--- FUNCTION ---------------------------------------------------------------- #--- FUNCTION ----------------------------------------------------------------
# NAME: checksum_check # NAME: checksum_check
# DESCRIPTION: Attempt to verify checksum of downloaded file to ensure # DESCRIPTION: Attempt to verify checksum of downloaded file to ensure
# integrity. Tries multiple tools before failing. # integrity. Tries multiple tools before faling.
# PARAMETERS: $1 = path to checksum file # PARAMETERS: $1 = path to checksum file
# $2 = location of file to check # $2 = location of file to check
# $3 = working directory # $3 = working directory
@ -300,10 +300,10 @@ checksum_check() {
#--- FUNCTION ---------------------------------------------------------------- #--- FUNCTION ----------------------------------------------------------------
# NAME: extract_file # NAME: extract_file
# DESCRIPTION: Extracts a file into a location. Attempts to determine which # DESCRIPTION: Extracts a file into a location. Attempts to determine which
# tool to use by checking file extension. # tool to use by checking file extention.
# PARAMETERS: $1 = file to extract # PARAMETERS: $1 = file to extract
# $2 = location to extract file into # $2 = location to extract file into
# $3 = extension # $3 = extention
# RETURNS: Return code of the tool used to extract the file # RETURNS: Return code of the tool used to extract the file
# 20 = Failed to determine which tool to use # 20 = Failed to determine which tool to use
# 30 = Failed to find tool in path # 30 = Failed to find tool in path
@ -521,21 +521,12 @@ main() {
local extract_file_rcode local extract_file_rcode
local install_file_rcode local install_file_rcode
local create_prefix_rcode local create_prefix_rcode
local bash_autocomplete_file
local bash_autocomplete_prefix
local zsh_autocomplete_file
local zsh_autocomplete_prefix
local autocomplete_install_rcode
croc_bin_name="croc" croc_bin_name="croc"
croc_version="10.1.1" croc_version="8.1.3"
croc_dl_ext="tar.gz" croc_dl_ext="tar.gz"
croc_base_url="https://github.com/schollz/croc/releases/download" croc_base_url="https://github.com/schollz/croc/releases/download"
prefix="${1}" prefix="${1}"
bash_autocomplete_file="bash_autocomplete"
bash_autocomplete_prefix="/etc/bash_completion.d"
zsh_autocomplete_file="zsh_autocomplete"
zsh_autocomplete_prefix="/etc/zsh"
print_banner print_banner
print_message "== Install prefix set to ${prefix}" "info" print_message "== Install prefix set to ${prefix}" "info"
@ -577,9 +568,6 @@ main() {
case "${croc_os}" in case "${croc_os}" in
"Darwin" ) croc_os="macOS";; "Darwin" ) croc_os="macOS";;
*"BusyBox"* )
croc_os="Linux"
;;
"CYGWIN"* ) croc_os="Windows"; "CYGWIN"* ) croc_os="Windows";
croc_dl_ext="zip"; croc_dl_ext="zip";
print_message "== Cygwin is currently unsupported." "error"; print_message "== Cygwin is currently unsupported." "error";
@ -590,19 +578,16 @@ main() {
"x86_64" ) croc_arch="64bit";; "x86_64" ) croc_arch="64bit";;
"amd64" ) croc_arch="64bit";; "amd64" ) croc_arch="64bit";;
"aarch64" ) croc_arch="ARM64";; "aarch64" ) croc_arch="ARM64";;
"arm64" ) croc_arch="ARM64";;
"armv7l" ) croc_arch="ARM";; "armv7l" ) croc_arch="ARM";;
"armv8l" ) croc_arch="ARM";;
"armv9l" ) croc_arch="ARM";;
"i686" ) croc_arch="32bit";; "i686" ) croc_arch="32bit";;
* ) croc_arch="unknown";; * ) croc_arch="unknown";;
esac esac
croc_file="${croc_bin_name}_v${croc_version}_${croc_os}-${croc_arch}.${croc_dl_ext}" croc_file="${croc_bin_name}_${croc_version}_${croc_os}-${croc_arch}.${croc_dl_ext}"
croc_checksum_file="${croc_bin_name}_v${croc_version}_checksums.txt" croc_checksum_file="${croc_bin_name}_${croc_version}_checksums.txt"
croc_url="${croc_base_url}/v${croc_version}/${croc_file}" croc_url="${croc_base_url}/v${croc_version}/${croc_file}"
croc_checksum_url="${croc_base_url}/v${croc_version}/${croc_checksum_file}" croc_checksum_url="${croc_base_url}/v${croc_version}/${croc_checksum_file}"
echo "${croc_url}" "${tmpdir}" "${croc_file}"
download_file "${croc_url}" "${tmpdir}" "${croc_file}" download_file "${croc_url}" "${tmpdir}" "${croc_file}"
download_file_rcode="${?}" download_file_rcode="${?}"
if [[ "${download_file_rcode}" == "0" ]]; then if [[ "${download_file_rcode}" == "0" ]]; then
@ -690,7 +675,7 @@ main() {
print_message "== Install prefix already exists. No need to create it." "info" print_message "== Install prefix already exists. No need to create it." "info"
fi fi
[ ! -d "${bash_autocomplete_prefix}/croc" ] && mkdir -p "${bash_autocomplete_prefix}/croc" >/dev/null 2>&1
case "${croc_os}" in case "${croc_os}" in
"Linux" ) install_file_linux "${tmpdir}/${croc_bin_name}" "${prefix}/"; "Linux" ) install_file_linux "${tmpdir}/${croc_bin_name}" "${prefix}/";
install_file_rcode="${?}";; install_file_rcode="${?}";;
@ -701,8 +686,7 @@ main() {
"Windows" ) install_file_cygwin "${tmpdir}/${croc_bin_name}" "${prefix}/"; "Windows" ) install_file_cygwin "${tmpdir}/${croc_bin_name}" "${prefix}/";
install_file_rcode="${?}";; install_file_rcode="${?}";;
esac esac
if [[ "${install_file_rcode}" == "0" ]]; then
if [[ "${install_file_rcode}" == "0" ]] ; then
print_message "== Installed ${croc_bin_name} to ${prefix}/" "ok" print_message "== Installed ${croc_bin_name} to ${prefix}/" "ok"
elif [[ "${install_file_rcode}" == "1" ]]; then elif [[ "${install_file_rcode}" == "1" ]]; then
print_message "== Failed to install ${croc_bin_name}" "error" print_message "== Failed to install ${croc_bin_name}" "error"
@ -718,28 +702,6 @@ main() {
exit 1 exit 1
fi fi
# case "$(basename ${SHELL})" in
# "bash" ) install_file_linux "${tmpdir}/${bash_autocomplete_file}" "${bash_autocomplete_prefix}/croc";
# autocomplete_install_rcode="${?}";;
# "zsh" ) install_file_linux "${tmpdir}/${zsh_autocomplete_file}" "${zsh_autocomplete_prefix}/zsh_autocomplete_croc";
# autocomplete_install_rcode="${?}";
# print_message "== You will need to add the following to your ~/.zshrc to enable autocompletion" "info";
# print_message "\nPROG=croc\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/zsh_autocomplete_croc\n" "info";;
# *) autocomplete_install_rcode="1";;
# esac
# if [[ "${autocomplete_install_rcode}" == "0" ]] ; then
# print_message "== Installed autocompletions for $(basename "${SHELL}")" "ok"
# elif [[ "${autocomplete_install_rcode}" == "1" ]]; then
# print_message "== Failed to install ${bash_autocomplete_file}" "error"
# elif [[ "${autocomplete_install_rcode}" == "20" ]]; then
# print_message "== Failed to locate 'install' command" "error"
# elif [[ "${autocomplete_install_rcode}" == "21" ]]; then
# print_message "== Failed to locate 'sudo' command" "error"
# else
# print_message "== Install attempt returned an unexpected value of ${autocomplete_install_rcode}" "error"
# fi
print_message "== Installation complete" "ok" print_message "== Installation complete" "ok"
exit 0 exit 0

View File

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
tmp=$(mktemp -d) tmp=$(mktemp -d)
VERSION=$(cat ./src/cli/cli.go | grep 'Version = "v' | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
echo $VERSION echo $VERSION
git clone -b v${VERSION} --depth 1 https://github.com/schollz/croc $tmp/croc-${VERSION} git clone -b v${VERSION} --depth 1 https://github.com/schollz/croc $tmp/croc-${VERSION}
(cd $tmp/croc-${VERSION} && go mod tidy && go mod vendor) (cd $tmp/croc-${VERSION} && go mod tidy && go mod vendor)

View File

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "io/ioutil"
"os/exec" "os/exec"
"strings" "strings"
) )
@ -15,7 +15,11 @@ func main() {
} }
func run() (err error) { func run() (err error) {
versionNew := "v" + os.Getenv("VERSION") version, err := exec.Command("git", "describe", "--abbrev=0").Output()
if err != nil {
return
}
versionNew := strings.TrimSpace(string(version))
versionHash, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output() versionHash, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
if err != nil { if err != nil {
return return
@ -42,7 +46,7 @@ func run() (err error) {
} }
func replaceInFile(fname, start, end, replacement string) (err error) { func replaceInFile(fname, start, end, replacement string) (err error) {
b, err := os.ReadFile(fname) b, err := ioutil.ReadFile(fname)
if err != nil { if err != nil {
return return
} }
@ -57,7 +61,7 @@ func replaceInFile(fname, start, end, replacement string) (err error) {
fmt.Sprintf("%s%s%s", start, replacement, end), fmt.Sprintf("%s%s%s", start, replacement, end),
1, 1,
) )
err = os.WriteFile(fname, []byte(newF), 0o644) err = ioutil.WriteFile(fname, []byte(newF), 0644)
return return
} }

View File

@ -1,23 +0,0 @@
#compdef $PROG
_cli_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
return
}
compdef _cli_zsh_autocomplete $PROG

View File

@ -3,32 +3,17 @@ package message
import ( import (
"encoding/json" "encoding/json"
"github.com/schollz/croc/v10/src/comm" "github.com/schollz/croc/v8/src/comm"
"github.com/schollz/croc/v10/src/compress" "github.com/schollz/croc/v8/src/compress"
"github.com/schollz/croc/v10/src/crypt" "github.com/schollz/croc/v8/src/crypt"
log "github.com/schollz/logger" log "github.com/schollz/logger"
) )
// Type is a message type
type Type string
const (
TypePAKE Type = "pake"
TypeExternalIP Type = "externalip"
TypeFinished Type = "finished"
TypeError Type = "error"
TypeCloseRecipient Type = "close-recipient"
TypeCloseSender Type = "close-sender"
TypeRecipientReady Type = "recipientready"
TypeFileInfo Type = "fileinfo"
)
// Message is the possible payload for messaging // Message is the possible payload for messaging
type Message struct { type Message struct {
Type Type `json:"t,omitempty"` Type string `json:"t,omitempty"`
Message string `json:"m,omitempty"` Message string `json:"m,omitempty"`
Bytes []byte `json:"b,omitempty"` Bytes []byte `json:"b,omitempty"`
Bytes2 []byte `json:"b2,omitempty"`
Num int `json:"n,omitempty"` Num int `json:"n,omitempty"`
} }
@ -43,7 +28,7 @@ func Send(c *comm.Comm, key []byte, m Message) (err error) {
if err != nil { if err != nil {
return return
} }
err = c.Send(mSend) _, err = c.Write(mSend)
return return
} }
@ -58,7 +43,7 @@ func Encode(key []byte, m Message) (b []byte, err error) {
log.Debugf("writing %s message (encrypted)", m.Type) log.Debugf("writing %s message (encrypted)", m.Type)
b, err = crypt.Encrypt(b, key) b, err = crypt.Encrypt(b, key)
} else { } else {
log.Debugf("writing %s message (unencrypted)", m.Type) log.Debugf("writing %s message", m.Type)
} }
return return
} }
@ -73,12 +58,5 @@ func Decode(key []byte, b []byte) (m Message, err error) {
} }
b = compress.Decompress(b) b = compress.Decompress(b)
err = json.Unmarshal(b, &m) err = json.Unmarshal(b, &m)
if err == nil {
if key != nil {
log.Debugf("read %s message (encrypted)", m.Type)
} else {
log.Debugf("read %s message (unencrypted)", m.Type)
}
}
return return
} }

View File

@ -7,20 +7,18 @@ import (
"testing" "testing"
"time" "time"
"github.com/schollz/croc/v10/src/comm" "github.com/schollz/croc/v8/src/comm"
"github.com/schollz/croc/v10/src/crypt" "github.com/schollz/croc/v8/src/crypt"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var TypeMessage Type = "message"
func TestMessage(t *testing.T) { func TestMessage(t *testing.T) {
log.SetLevel("debug") log.SetLevel("debug")
m := Message{Type: TypeMessage, Message: "hello, world"} m := Message{Type: "message", Message: "hello, world"}
e, salt, err := crypt.New([]byte("pass"), nil) e, salt, err := crypt.New([]byte("pass"), nil)
assert.Nil(t, err) assert.Nil(t, err)
fmt.Println(string(salt)) fmt.Println(salt)
b, err := Encode(e, m) b, err := Encode(e, m)
assert.Nil(t, err) assert.Nil(t, err)
fmt.Printf("%x\n", b) fmt.Printf("%x\n", b)
@ -37,7 +35,7 @@ func TestMessage(t *testing.T) {
func TestMessageNoPass(t *testing.T) { func TestMessageNoPass(t *testing.T) {
log.SetLevel("debug") log.SetLevel("debug")
m := Message{Type: TypeMessage, Message: "hello, world"} m := Message{Type: "message", Message: "hello, world"}
b, err := Encode(nil, m) b, err := Encode(nil, m)
assert.Nil(t, err) assert.Nil(t, err)
fmt.Printf("%x\n", b) fmt.Printf("%x\n", b)
@ -67,7 +65,7 @@ func TestSend(t *testing.T) {
log.Error(err) log.Error(err)
} }
log.Debugf("client %s connected", connection.RemoteAddr().String()) log.Debugf("client %s connected", connection.RemoteAddr().String())
go func(_ string, connection net.Conn) { go func(port string, connection net.Conn) {
c := comm.New(connection) c := comm.New(connection)
err = c.Send([]byte("hello, world")) err = c.Send([]byte("hello, world"))
assert.Nil(t, err) assert.Nil(t, err)
@ -84,10 +82,10 @@ func TestSend(t *testing.T) {
} }
}() }()
time.Sleep(800 * time.Millisecond) time.Sleep(300 * time.Millisecond)
a, err := comm.NewConnection("127.0.0.1:"+port, 10*time.Minute) a, err := comm.NewConnection("localhost:"+port, 10*time.Minute)
assert.Nil(t, err) assert.Nil(t, err)
m := Message{Type: TypeMessage, Message: "hello, world"} m := Message{Type: "message", Message: "hello, world"}
e, salt, err := crypt.New([]byte("pass"), nil) e, salt, err := crypt.New([]byte("pass"), nil)
log.Debug(salt) log.Debug(salt)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -1,88 +0,0 @@
// From GitHub version/fork maintained by Stephen Paul Weber available at:
// https://github.com/singpolyma/mnemonicode
//
// Originally from:
// http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/
/*
Copyright (c) 2000 Oren Tirosh <oren@hishome.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package mnemonicode
const base = 1626
// WordsRequired returns the number of words required to encode input
// data of length bytes using mnomonic encoding.
//
// Every four bytes of input is encoded into three words. If there
// is an extra one or two bytes they get an extra one or two words
// respectively. If there is an extra three bytes, they will be encoded
// into three words with the last word being one of a small set of very
// short words (only needed to encode the last 3 bits).
func WordsRequired(length int) int {
return ((length + 1) * 3) / 4
}
// EncodeWordList encodes src into mnemomic words which are appended to dst.
// The final wordlist is returned.
// There will be WordsRequired(len(src)) words appeneded.
func EncodeWordList(dst []string, src []byte) (result []string) {
if n := len(dst) + WordsRequired(len(src)); cap(dst) < n {
result = make([]string, len(dst), n)
copy(result, dst)
} else {
result = dst
}
var x uint32
for len(src) >= 4 {
x = uint32(src[0])
x |= uint32(src[1]) << 8
x |= uint32(src[2]) << 16
x |= uint32(src[3]) << 24
src = src[4:]
i0 := int(x % base)
i1 := int(x/base) % base
i2 := int(x/base/base) % base
result = append(result, WordList[i0], WordList[i1], WordList[i2])
}
if len(src) > 0 {
x = 0
for i := len(src) - 1; i >= 0; i-- {
x <<= 8
x |= uint32(src[i])
}
i := int(x % base)
result = append(result, WordList[i])
if len(src) >= 2 {
i = int(x/base) % base
result = append(result, WordList[i])
}
if len(src) == 3 {
i = base + int(x/base/base)%7
result = append(result, WordList[i])
}
}
return result
}

View File

@ -1,318 +0,0 @@
// From GitHub version/fork maintained by Stephen Paul Weber available at:
// https://github.com/singpolyma/mnemonicode
//
// Originally from:
// http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/
/*
Copyright (c) 2000 Oren Tirosh <oren@hishome.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package mnemonicode
// WordListVersion is the version of compiled in word list.
const WordListVersion = "0.7"
var wordMap = make(map[string]int, len(WordList))
func init() {
for i, w := range WordList {
wordMap[w] = i
}
}
const longestWord = 7
var WordList = []string{
"academy", "acrobat", "active", "actor", "adam", "admiral",
"adrian", "africa", "agenda", "agent", "airline", "airport",
"aladdin", "alarm", "alaska", "albert", "albino", "album",
"alcohol", "alex", "algebra", "alibi", "alice", "alien",
"alpha", "alpine", "amadeus", "amanda", "amazon", "amber",
"america", "amigo", "analog", "anatomy", "angel", "animal",
"antenna", "antonio", "apollo", "april", "archive", "arctic",
"arizona", "arnold", "aroma", "arthur", "artist", "asia",
"aspect", "aspirin", "athena", "athlete", "atlas", "audio",
"august", "austria", "axiom", "aztec", "balance", "ballad",
"banana", "bandit", "banjo", "barcode", "baron", "basic",
"battery", "belgium", "berlin", "bermuda", "bernard", "bikini",
"binary", "bingo", "biology", "block", "blonde", "bonus",
"boris", "boston", "boxer", "brandy", "bravo", "brazil",
"bronze", "brown", "bruce", "bruno", "burger", "burma",
"cabinet", "cactus", "cafe", "cairo", "cake", "calypso",
"camel", "camera", "campus", "canada", "canal", "cannon",
"canoe", "cantina", "canvas", "canyon", "capital", "caramel",
"caravan", "carbon", "cargo", "carlo", "carol", "carpet",
"cartel", "casino", "castle", "castro", "catalog", "caviar",
"cecilia", "cement", "center", "century", "ceramic", "chamber",
"chance", "change", "chaos", "charlie", "charm", "charter",
"chef", "chemist", "cherry", "chess", "chicago", "chicken",
"chief", "china", "cigar", "cinema", "circus", "citizen",
"city", "clara", "classic", "claudia", "clean", "client",
"climax", "clinic", "clock", "club", "cobra", "coconut",
"cola", "collect", "colombo", "colony", "color", "combat",
"comedy", "comet", "command", "compact", "company", "complex",
"concept", "concert", "connect", "consul", "contact", "context",
"contour", "control", "convert", "copy", "corner", "corona",
"correct", "cosmos", "couple", "courage", "cowboy", "craft",
"crash", "credit", "cricket", "critic", "crown", "crystal",
"cuba", "culture", "dallas", "dance", "daniel", "david",
"decade", "decimal", "deliver", "delta", "deluxe", "demand",
"demo", "denmark", "derby", "design", "detect", "develop",
"diagram", "dialog", "diamond", "diana", "diego", "diesel",
"diet", "digital", "dilemma", "diploma", "direct", "disco",
"disney", "distant", "doctor", "dollar", "dominic", "domino",
"donald", "dragon", "drama", "dublin", "duet", "dynamic",
"east", "ecology", "economy", "edgar", "egypt", "elastic",
"elegant", "element", "elite", "elvis", "email", "energy",
"engine", "english", "episode", "equator", "escort", "ethnic",
"europe", "everest", "evident", "exact", "example", "exit",
"exotic", "export", "express", "extra", "fabric", "factor",
"falcon", "family", "fantasy", "fashion", "fiber", "fiction",
"fidel", "fiesta", "figure", "film", "filter", "final",
"finance", "finish", "finland", "flash", "florida", "flower",
"fluid", "flute", "focus", "ford", "forest", "formal",
"format", "formula", "fortune", "forum", "fragile", "france",
"frank", "friend", "frozen", "future", "gabriel", "galaxy",
"gallery", "gamma", "garage", "garden", "garlic", "gemini",
"general", "genetic", "genius", "germany", "global", "gloria",
"golf", "gondola", "gong", "good", "gordon", "gorilla",
"grand", "granite", "graph", "green", "group", "guide",
"guitar", "guru", "hand", "happy", "harbor", "harmony",
"harvard", "havana", "hawaii", "helena", "hello", "henry",
"hilton", "history", "horizon", "hotel", "human", "humor",
"icon", "idea", "igloo", "igor", "image", "impact",
"import", "index", "india", "indigo", "input", "insect",
"instant", "iris", "italian", "jacket", "jacob", "jaguar",
"janet", "japan", "jargon", "jazz", "jeep", "john",
"joker", "jordan", "jumbo", "june", "jungle", "junior",
"jupiter", "karate", "karma", "kayak", "kermit", "kilo",
"king", "koala", "korea", "labor", "lady", "lagoon",
"laptop", "laser", "latin", "lava", "lecture", "left",
"legal", "lemon", "level", "lexicon", "liberal", "libra",
"limbo", "limit", "linda", "linear", "lion", "liquid",
"liter", "little", "llama", "lobby", "lobster", "local",
"logic", "logo", "lola", "london", "lotus", "lucas",
"lunar", "machine", "macro", "madam", "madonna", "madrid",
"maestro", "magic", "magnet", "magnum", "major", "mama",
"mambo", "manager", "mango", "manila", "marco", "marina",
"market", "mars", "martin", "marvin", "master", "matrix",
"maximum", "media", "medical", "mega", "melody", "melon",
"memo", "mental", "mentor", "menu", "mercury", "message",
"metal", "meteor", "meter", "method", "metro", "mexico",
"miami", "micro", "million", "mineral", "minimum", "minus",
"minute", "miracle", "mirage", "miranda", "mister", "mixer",
"mobile", "model", "modem", "modern", "modular", "moment",
"monaco", "monica", "monitor", "mono", "monster", "montana",
"morgan", "motel", "motif", "motor", "mozart", "multi",
"museum", "music", "mustang", "natural", "neon", "nepal",
"neptune", "nerve", "neutral", "nevada", "news", "ninja",
"nirvana", "normal", "nova", "novel", "nuclear", "numeric",
"nylon", "oasis", "object", "observe", "ocean", "octopus",
"olivia", "olympic", "omega", "opera", "optic", "optimal",
"orange", "orbit", "organic", "orient", "origin", "orlando",
"oscar", "oxford", "oxygen", "ozone", "pablo", "pacific",
"pagoda", "palace", "pamela", "panama", "panda", "panel",
"panic", "paradox", "pardon", "paris", "parker", "parking",
"parody", "partner", "passage", "passive", "pasta", "pastel",
"patent", "patriot", "patrol", "patron", "pegasus", "pelican",
"penguin", "pepper", "percent", "perfect", "perfume", "period",
"permit", "person", "peru", "phone", "photo", "piano",
"picasso", "picnic", "picture", "pigment", "pilgrim", "pilot",
"pirate", "pixel", "pizza", "planet", "plasma", "plaster",
"plastic", "plaza", "pocket", "poem", "poetic", "poker",
"polaris", "police", "politic", "polo", "polygon", "pony",
"popcorn", "popular", "postage", "postal", "precise", "prefix",
"premium", "present", "price", "prince", "printer", "prism",
"private", "product", "profile", "program", "project", "protect",
"proton", "public", "pulse", "puma", "pyramid", "queen",
"radar", "radio", "random", "rapid", "rebel", "record",
"recycle", "reflex", "reform", "regard", "regular", "relax",
"report", "reptile", "reverse", "ricardo", "ringo", "ritual",
"robert", "robot", "rocket", "rodeo", "romeo", "royal",
"russian", "safari", "salad", "salami", "salmon", "salon",
"salute", "samba", "sandra", "santana", "sardine", "school",
"screen", "script", "second", "secret", "section", "segment",
"select", "seminar", "senator", "senior", "sensor", "serial",
"service", "sheriff", "shock", "sierra", "signal", "silicon",
"silver", "similar", "simon", "single", "siren", "slogan",
"social", "soda", "solar", "solid", "solo", "sonic",
"soviet", "special", "speed", "spiral", "spirit", "sport",
"static", "station", "status", "stereo", "stone", "stop",
"street", "strong", "student", "studio", "style", "subject",
"sultan", "super", "susan", "sushi", "suzuki", "switch",
"symbol", "system", "tactic", "tahiti", "talent", "tango",
"tarzan", "taxi", "telex", "tempo", "tennis", "texas",
"textile", "theory", "thermos", "tiger", "titanic", "tokyo",
"tomato", "topic", "tornado", "toronto", "torpedo", "total",
"totem", "tourist", "tractor", "traffic", "transit", "trapeze",
"travel", "tribal", "trick", "trident", "trilogy", "tripod",
"tropic", "trumpet", "tulip", "tuna", "turbo", "twist",
"ultra", "uniform", "union", "uranium", "vacuum", "valid",
"vampire", "vanilla", "vatican", "velvet", "ventura", "venus",
"vertigo", "veteran", "victor", "video", "vienna", "viking",
"village", "vincent", "violet", "violin", "virtual", "virus",
"visa", "vision", "visitor", "visual", "vitamin", "viva",
"vocal", "vodka", "volcano", "voltage", "volume", "voyage",
"water", "weekend", "welcome", "western", "window", "winter",
"wizard", "wolf", "world", "xray", "yankee", "yoga",
"yogurt", "yoyo", "zebra", "zero", "zigzag", "zipper",
"zodiac", "zoom", "abraham", "action", "address", "alabama",
"alfred", "almond", "ammonia", "analyze", "annual", "answer",
"apple", "arena", "armada", "arsenal", "atlanta", "atomic",
"avenue", "average", "bagel", "baker", "ballet", "bambino",
"bamboo", "barbara", "basket", "bazaar", "benefit", "bicycle",
"bishop", "blitz", "bonjour", "bottle", "bridge", "british",
"brother", "brush", "budget", "cabaret", "cadet", "candle",
"capitan", "capsule", "career", "cartoon", "channel", "chapter",
"cheese", "circle", "cobalt", "cockpit", "college", "compass",
"comrade", "condor", "crimson", "cyclone", "darwin", "declare",
"degree", "delete", "delphi", "denver", "desert", "divide",
"dolby", "domain", "domingo", "double", "drink", "driver",
"eagle", "earth", "echo", "eclipse", "editor", "educate",
"edward", "effect", "electra", "emerald", "emotion", "empire",
"empty", "escape", "eternal", "evening", "exhibit", "expand",
"explore", "extreme", "ferrari", "first", "flag", "folio",
"forget", "forward", "freedom", "fresh", "friday", "fuji",
"galileo", "garcia", "genesis", "gold", "gravity", "habitat",
"hamlet", "harlem", "helium", "holiday", "house", "hunter",
"ibiza", "iceberg", "imagine", "infant", "isotope", "jackson",
"jamaica", "jasmine", "java", "jessica", "judo", "kitchen",
"lazarus", "letter", "license", "lithium", "loyal", "lucky",
"magenta", "mailbox", "manual", "marble", "mary", "maxwell",
"mayor", "milk", "monarch", "monday", "money", "morning",
"mother", "mystery", "native", "nectar", "nelson", "network",
"next", "nikita", "nobel", "nobody", "nominal", "norway",
"nothing", "number", "october", "office", "oliver", "opinion",
"option", "order", "outside", "package", "pancake", "pandora",
"panther", "papa", "patient", "pattern", "pedro", "pencil",
"people", "phantom", "philips", "pioneer", "pluto", "podium",
"portal", "potato", "prize", "process", "protein", "proxy",
"pump", "pupil", "python", "quality", "quarter", "quiet",
"rabbit", "radical", "radius", "rainbow", "ralph", "ramirez",
"ravioli", "raymond", "respect", "respond", "result", "resume",
"retro", "richard", "right", "risk", "river", "roger",
"roman", "rondo", "sabrina", "salary", "salsa", "sample",
"samuel", "saturn", "savage", "scarlet", "scoop", "scorpio",
"scratch", "scroll", "sector", "serpent", "shadow", "shampoo",
"sharon", "sharp", "short", "shrink", "silence", "silk",
"simple", "slang", "smart", "smoke", "snake", "society",
"sonar", "sonata", "soprano", "source", "sparta", "sphere",
"spider", "sponsor", "spring", "acid", "adios", "agatha",
"alamo", "alert", "almanac", "aloha", "andrea", "anita",
"arcade", "aurora", "avalon", "baby", "baggage", "balloon",
"bank", "basil", "begin", "biscuit", "blue", "bombay",
"brain", "brenda", "brigade", "cable", "carmen", "cello",
"celtic", "chariot", "chrome", "citrus", "civil", "cloud",
"common", "compare", "cool", "copper", "coral", "crater",
"cubic", "cupid", "cycle", "depend", "door", "dream",
"dynasty", "edison", "edition", "enigma", "equal", "eric",
"event", "evita", "exodus", "extend", "famous", "farmer",
"food", "fossil", "frog", "fruit", "geneva", "gentle",
"george", "giant", "gilbert", "gossip", "gram", "greek",
"grille", "hammer", "harvest", "hazard", "heaven", "herbert",
"heroic", "hexagon", "husband", "immune", "inca", "inch",
"initial", "isabel", "ivory", "jason", "jerome", "joel",
"joshua", "journal", "judge", "juliet", "jump", "justice",
"kimono", "kinetic", "leonid", "lima", "maze", "medusa",
"member", "memphis", "michael", "miguel", "milan", "mile",
"miller", "mimic", "mimosa", "mission", "monkey", "moral",
"moses", "mouse", "nancy", "natasha", "nebula", "nickel",
"nina", "noise", "orchid", "oregano", "origami", "orinoco",
"orion", "othello", "paper", "paprika", "prelude", "prepare",
"pretend", "profit", "promise", "provide", "puzzle", "remote",
"repair", "reply", "rival", "riviera", "robin", "rose",
"rover", "rudolf", "saga", "sahara", "scholar", "shelter",
"ship", "shoe", "sigma", "sister", "sleep", "smile",
"spain", "spark", "split", "spray", "square", "stadium",
"star", "storm", "story", "strange", "stretch", "stuart",
"subway", "sugar", "sulfur", "summer", "survive", "sweet",
"swim", "table", "taboo", "target", "teacher", "telecom",
"temple", "tibet", "ticket", "tina", "today", "toga",
"tommy", "tower", "trivial", "tunnel", "turtle", "twin",
"uncle", "unicorn", "unique", "update", "valery", "vega",
"version", "voodoo", "warning", "william", "wonder", "year",
"yellow", "young", "absent", "absorb", "accent", "alfonso",
"alias", "ambient", "andy", "anvil", "appear", "apropos",
"archer", "ariel", "armor", "arrow", "austin", "avatar",
"axis", "baboon", "bahama", "bali", "balsa", "bazooka",
"beach", "beast", "beatles", "beauty", "before", "benny",
"betty", "between", "beyond", "billy", "bison", "blast",
"bless", "bogart", "bonanza", "book", "border", "brave",
"bread", "break", "broken", "bucket", "buenos", "buffalo",
"bundle", "button", "buzzer", "byte", "caesar", "camilla",
"canary", "candid", "carrot", "cave", "chant", "child",
"choice", "chris", "cipher", "clarion", "clark", "clever",
"cliff", "clone", "conan", "conduct", "congo", "content",
"costume", "cotton", "cover", "crack", "current", "danube",
"data", "decide", "desire", "detail", "dexter", "dinner",
"dispute", "donor", "druid", "drum", "easy", "eddie",
"enjoy", "enrico", "epoxy", "erosion", "except", "exile",
"explain", "fame", "fast", "father", "felix", "field",
"fiona", "fire", "fish", "flame", "flex", "flipper",
"float", "flood", "floor", "forbid", "forever", "fractal",
"frame", "freddie", "front", "fuel", "gallop", "game",
"garbo", "gate", "gibson", "ginger", "giraffe", "gizmo",
"glass", "goblin", "gopher", "grace", "gray", "gregory",
"grid", "griffin", "ground", "guest", "gustav", "gyro",
"hair", "halt", "harris", "heart", "heavy", "herman",
"hippie", "hobby", "honey", "hope", "horse", "hostel",
"hydro", "imitate", "info", "ingrid", "inside", "invent",
"invest", "invite", "iron", "ivan", "james", "jester",
"jimmy", "join", "joseph", "juice", "julius", "july",
"justin", "kansas", "karl", "kevin", "kiwi", "ladder",
"lake", "laura", "learn", "legacy", "legend", "lesson",
"life", "light", "list", "locate", "lopez", "lorenzo",
"love", "lunch", "malta", "mammal", "margo", "marion",
"mask", "match", "mayday", "meaning", "mercy", "middle",
"mike", "mirror", "modest", "morph", "morris", "nadia",
"nato", "navy", "needle", "neuron", "never", "newton",
"nice", "night", "nissan", "nitro", "nixon", "north",
"oberon", "octavia", "ohio", "olga", "open", "opus",
"orca", "oval", "owner", "page", "paint", "palma",
"parade", "parent", "parole", "paul", "peace", "pearl",
"perform", "phoenix", "phrase", "pierre", "pinball", "place",
"plate", "plato", "plume", "pogo", "point", "polite",
"polka", "poncho", "powder", "prague", "press", "presto",
"pretty", "prime", "promo", "quasi", "quest", "quick",
"quiz", "quota", "race", "rachel", "raja", "ranger",
"region", "remark", "rent", "reward", "rhino", "ribbon",
"rider", "road", "rodent", "round", "rubber", "ruby",
"rufus", "sabine", "saddle", "sailor", "saint", "salt",
"satire", "scale", "scuba", "season", "secure", "shake",
"shallow", "shannon", "shave", "shelf", "sherman", "shine",
"shirt", "side", "sinatra", "sincere", "size", "slalom",
"slow", "small", "snow", "sofia", "song", "sound",
"south", "speech", "spell", "spend", "spoon", "stage",
"stamp", "stand", "state", "stella", "stick", "sting",
"stock", "store", "sunday", "sunset", "support", "sweden",
"swing", "tape", "think", "thomas", "tictac", "time",
"toast", "tobacco", "tonight", "torch", "torso", "touch",
"toyota", "trade", "tribune", "trinity", "triton", "truck",
"trust", "type", "under", "unit", "urban", "urgent",
"user", "value", "vendor", "venice", "verona", "vibrate",
"virgo", "visible", "vista", "vital", "voice", "vortex",
"waiter", "watch", "wave", "weather", "wedding", "wheel",
"whiskey", "wisdom", "deal", "null", "nurse", "quebec",
"reserve", "reunion", "roof", "singer", "verbal", "amen",
"ego", "fax", "jet", "job", "rio", "ski",
"yes",
}

View File

@ -1,169 +1,8 @@
package models package models
import (
"context"
"fmt"
"net"
"os"
"path"
"time"
"github.com/schollz/croc/v10/src/utils"
log "github.com/schollz/logger"
)
// TCP_BUFFER_SIZE is the maximum packet size // TCP_BUFFER_SIZE is the maximum packet size
const TCP_BUFFER_SIZE = 1024 * 64 const TCP_BUFFER_SIZE = 1024 * 64
// DEFAULT_RELAY is the default relay used (can be set using --relay) // DEFAULT_RELAY is the default relay used (can be set using --relay)
var ( const DEFAULT_RELAY = "142.93.177.120:9009"
DEFAULT_RELAY = "croc.schollz.com" const DEFAULT_RELAY6 = "[2604:a880:800:c1::14c:1]:9009"
DEFAULT_RELAY6 = "croc6.schollz.com"
DEFAULT_PORT = "9009"
DEFAULT_PASSPHRASE = "pass123"
INTERNAL_DNS = false
)
// publicDNS are servers to be queried if a local lookup fails
var publicDNS = []string{
"1.0.0.1", // Cloudflare
"1.1.1.1", // Cloudflare
"[2606:4700:4700::1111]", // Cloudflare
"[2606:4700:4700::1001]", // Cloudflare
"8.8.4.4", // Google
"8.8.8.8", // Google
"[2001:4860:4860::8844]", // Google
"[2001:4860:4860::8888]", // Google
"9.9.9.9", // Quad9
"149.112.112.112", // Quad9
"[2620:fe::fe]", // Quad9
"[2620:fe::fe:9]", // Quad9
"8.26.56.26", // Comodo
"8.20.247.20", // Comodo
"208.67.220.220", // Cisco OpenDNS
"208.67.222.222", // Cisco OpenDNS
"[2620:119:35::35]", // Cisco OpenDNS
"[2620:119:53::53]", // Cisco OpenDNS
}
func getConfigFile(requireValidPath bool) (fname string, err error) {
configFile, err := utils.GetConfigDir(requireValidPath)
if err != nil {
return
}
fname = path.Join(configFile, "internal-dns")
return
}
func init() {
log.SetLevel("info")
log.SetOutput(os.Stderr)
doRemember := false
for _, flag := range os.Args {
if flag == "--internal-dns" {
INTERNAL_DNS = true
break
}
if flag == "--remember" {
doRemember = true
}
}
if doRemember {
// save in config file
fname, err := getConfigFile(true)
if err == nil {
f, _ := os.Create(fname)
f.Close()
}
}
if !INTERNAL_DNS {
fname, err := getConfigFile(false)
if err == nil {
INTERNAL_DNS = utils.Exists(fname)
}
}
log.Trace("Using internal DNS: ", INTERNAL_DNS)
var err error
var addr string
addr, err = lookup(DEFAULT_RELAY)
if err == nil {
DEFAULT_RELAY = net.JoinHostPort(addr, DEFAULT_PORT)
} else {
DEFAULT_RELAY = ""
}
log.Tracef("Default ipv4 relay: %s", addr)
addr, err = lookup(DEFAULT_RELAY6)
if err == nil {
DEFAULT_RELAY6 = net.JoinHostPort(addr, DEFAULT_PORT)
} else {
DEFAULT_RELAY6 = ""
}
log.Tracef("Default ipv6 relay: %s", addr)
}
// Resolve a hostname to an IP address using DNS.
func lookup(address string) (ipaddress string, err error) {
if !INTERNAL_DNS {
log.Tracef("Using local DNS to resolve %s", address)
return localLookupIP(address)
}
type Result struct {
s string
err error
}
result := make(chan Result, len(publicDNS))
for _, dns := range publicDNS {
go func(dns string) {
var r Result
r.s, r.err = remoteLookupIP(address, dns)
log.Tracef("Resolved %s to %s using %s", address, r.s, dns)
result <- r
}(dns)
}
for i := 0; i < len(publicDNS); i++ {
ipaddress = (<-result).s
log.Tracef("Resolved %s to %s", address, ipaddress)
if ipaddress != "" {
return
}
}
err = fmt.Errorf("failed to resolve %s: all DNS servers exhausted", address)
return
}
// localLookupIP returns a host's IP address using the local DNS configuration.
func localLookupIP(address string) (ipaddress string, err error) {
// Create a context with a 500 millisecond timeout
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
r := &net.Resolver{}
// Use the context with timeout in the LookupHost function
ip, err := r.LookupHost(ctx, address)
if err != nil {
return
}
ipaddress = ip[0]
return
}
// remoteLookupIP returns a host's IP address based on a remote DNS server.
func remoteLookupIP(address, dns string) (ipaddress string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
r := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
d := new(net.Dialer)
return d.DialContext(ctx, network, dns+":53")
},
}
ip, err := r.LookupHost(ctx, address)
if err != nil {
return
}
ipaddress = ip[0]
return
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@ -1,9 +0,0 @@
package tcp
import "time"
const (
DEFAULT_LOG_LEVEL = "debug"
DEFAULT_ROOM_CLEANUP_INTERVAL = 10 * time.Minute
DEFAULT_ROOM_TTL = 3 * time.Hour
)

View File

@ -1,53 +0,0 @@
package tcp
import (
"fmt"
"time"
)
// TODO: maybe export from logger library?
var availableLogLevels = []string{"info", "error", "warn", "debug", "trace"}
type serverOptsFunc func(s *server) error
func WithBanner(banner ...string) serverOptsFunc {
return func(s *server) error {
if len(banner) > 0 {
s.banner = banner[0]
}
return nil
}
}
func WithLogLevel(level string) serverOptsFunc {
return func(s *server) error {
if !containsSlice(availableLogLevels, level) {
return fmt.Errorf("invalid log level specified: %s", level)
}
s.debugLevel = level
return nil
}
}
func WithRoomCleanupInterval(interval time.Duration) serverOptsFunc {
return func(s *server) error {
s.roomCleanupInterval = interval
return nil
}
}
func WithRoomTTL(ttl time.Duration) serverOptsFunc {
return func(s *server) error {
s.roomTTL = ttl
return nil
}
}
func containsSlice(s []string, e string) bool {
for _, ss := range s {
if e == ss {
return true
}
}
return false
}

View File

@ -9,25 +9,19 @@ import (
"time" "time"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/schollz/pake/v3" "github.com/schollz/pake/v2"
"github.com/schollz/croc/v10/src/comm" "github.com/schollz/croc/v8/src/comm"
"github.com/schollz/croc/v10/src/crypt" "github.com/schollz/croc/v8/src/crypt"
"github.com/schollz/croc/v10/src/models" "github.com/schollz/croc/v8/src/models"
) )
type server struct { type server struct {
host string
port string port string
debugLevel string debugLevel string
banner string banner string
password string password string
rooms roomMap rooms roomMap
roomCleanupInterval time.Duration
roomTTL time.Duration
stopRoomCleanup chan struct{}
} }
type roomInfo struct { type roomInfo struct {
@ -42,57 +36,45 @@ type roomMap struct {
sync.Mutex sync.Mutex
} }
const pingRoom = "pinglkasjdlfjsaldjf" var timeToRoomDeletion = 10 * time.Minute
// newDefaultServer initializes a new server, with some default configuration options // Run starts a tcp listener, run async
func newDefaultServer() *server { func Run(debugLevel, port, password string, banner ...string) (err error) {
s := new(server) s := new(server)
s.roomCleanupInterval = DEFAULT_ROOM_CLEANUP_INTERVAL
s.roomTTL = DEFAULT_ROOM_TTL
s.debugLevel = DEFAULT_LOG_LEVEL
s.stopRoomCleanup = make(chan struct{})
return s
}
// RunWithOptionsAsync asynchronously starts a TCP listener.
func RunWithOptionsAsync(host, port, password string, opts ...serverOptsFunc) error {
s := newDefaultServer()
s.host = host
s.port = port s.port = port
s.password = password s.password = password
for _, opt := range opts { s.debugLevel = debugLevel
err := opt(s) if len(banner) > 0 {
if err != nil { s.banner = banner[0]
return fmt.Errorf("could not apply optional configurations: %w", err)
}
} }
return s.start() return s.start()
} }
// Run starts a tcp listener, run async
func Run(debugLevel, host, port, password string, banner ...string) (err error) {
return RunWithOptionsAsync(host, port, password, WithBanner(banner...), WithLogLevel(debugLevel))
}
func (s *server) start() (err error) { func (s *server) start() (err error) {
log.SetLevel(s.debugLevel) log.SetLevel(s.debugLevel)
log.Debugf("starting with password '%s'", s.password)
// Mask our password in logs
maskedPassword := ""
if len(s.password) > 2 {
maskedPassword = fmt.Sprintf("%c***%c", s.password[0], s.password[len(s.password)-1])
} else {
maskedPassword = s.password
}
log.Debugf("starting with password '%s'", maskedPassword)
s.rooms.Lock() s.rooms.Lock()
s.rooms.rooms = make(map[string]roomInfo) s.rooms.rooms = make(map[string]roomInfo)
s.rooms.Unlock() s.rooms.Unlock()
go s.deleteOldRooms() // delete old rooms
defer s.stopRoomDeletion() go func() {
for {
time.Sleep(timeToRoomDeletion)
var roomsToDelete []string
s.rooms.Lock()
for room := range s.rooms.rooms {
if time.Since(s.rooms.rooms[room].opened) > 3*time.Hour {
roomsToDelete = append(roomsToDelete, room)
}
}
s.rooms.Unlock()
for _, room := range roomsToDelete {
s.deleteRoom(room)
}
}
}()
err = s.run() err = s.run()
if err != nil { if err != nil {
@ -102,32 +84,10 @@ func (s *server) start() (err error) {
} }
func (s *server) run() (err error) { func (s *server) run() (err error) {
network := "tcp" log.Infof("starting TCP server on " + s.port)
addr := net.JoinHostPort(s.host, s.port) server, err := net.Listen("tcp", ":"+s.port)
if s.host != "" {
ip := net.ParseIP(s.host)
if ip == nil {
var tcpIP *net.IPAddr
tcpIP, err = net.ResolveIPAddr("ip", s.host)
if err != nil {
return err
}
ip = tcpIP.IP
}
addr = net.JoinHostPort(ip.String(), s.port)
if s.host != "" {
if ip.To4() != nil {
network = "tcp4"
} else {
network = "tcp6"
}
}
}
addr = strings.Replace(addr, "127.0.0.1", "0.0.0.0", 1)
log.Infof("starting TCP server on " + addr)
server, err := net.Listen(network, addr)
if err != nil { if err != nil {
return fmt.Errorf("error listening on %s: %w", addr, err) return fmt.Errorf("error listening on %s: %w", s.port, err)
} }
defer server.Close() defer server.Close()
// spawn a new goroutine whenever a client connects // spawn a new goroutine whenever a client connects
@ -140,17 +100,8 @@ func (s *server) run() (err error) {
go func(port string, connection net.Conn) { go func(port string, connection net.Conn) {
c := comm.New(connection) c := comm.New(connection)
room, errCommunication := s.clientCommunication(port, c) room, errCommunication := s.clientCommunication(port, c)
log.Debugf("room: %+v", room)
log.Debugf("err: %+v", errCommunication)
if errCommunication != nil { if errCommunication != nil {
log.Debugf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error()) log.Debugf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error())
connection.Close()
return
}
if room == pingRoom {
log.Debugf("got ping")
connection.Close()
return
} }
for { for {
// check connection // check connection
@ -187,44 +138,11 @@ func (s *server) run() (err error) {
} }
} }
// deleteOldRooms checks for rooms at a regular interval and removes those that
// have exceeded their allocated TTL.
func (s *server) deleteOldRooms() {
ticker := time.NewTicker(s.roomCleanupInterval)
for {
select {
case <-ticker.C:
var roomsToDelete []string
s.rooms.Lock()
for room := range s.rooms.rooms {
if time.Since(s.rooms.rooms[room].opened) > s.roomTTL {
roomsToDelete = append(roomsToDelete, room)
}
}
s.rooms.Unlock()
for _, room := range roomsToDelete {
s.deleteRoom(room)
log.Debugf("room cleaned up: %s", room)
}
case <-s.stopRoomCleanup:
ticker.Stop()
log.Debug("room cleanup stopped")
return
}
}
}
func (s *server) stopRoomDeletion() {
log.Debug("stop room cleanup fired")
s.stopRoomCleanup <- struct{}{}
}
var weakKey = []byte{1, 2, 3} var weakKey = []byte{1, 2, 3}
func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) { func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) {
// establish secure password with PAKE for communication with relay // establish secure password with PAKE for communication with relay
B, err := pake.InitCurve(weakKey, 1, "siec") B, err := pake.InitCurve(weakKey, 1, "siec", 1*time.Microsecond)
if err != nil { if err != nil {
return return
} }
@ -232,18 +150,16 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
if err != nil { if err != nil {
return return
} }
log.Debugf("Abytes: %s", Abytes)
if bytes.Equal(Abytes, []byte("ping")) {
room = pingRoom
log.Debug("sending back pong")
c.Send([]byte("pong"))
return
}
err = B.Update(Abytes) err = B.Update(Abytes)
if err != nil { if err != nil {
return return
} }
err = c.Send(B.Bytes()) err = c.Send(B.Bytes())
Abytes, err = c.Receive()
if err != nil {
return
}
err = B.Update(Abytes)
if err != nil { if err != nil {
return return
} }
@ -255,9 +171,6 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
// receive salt // receive salt
salt, err := c.Receive() salt, err := c.Receive()
if err != nil {
return
}
strongKeyForEncryption, _, err := crypt.New(strongKey, salt) strongKeyForEncryption, _, err := crypt.New(strongKey, salt)
if err != nil { if err != nil {
return return
@ -274,8 +187,8 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
} }
if strings.TrimSpace(string(passwordBytes)) != s.password { if strings.TrimSpace(string(passwordBytes)) != s.password {
err = fmt.Errorf("bad password") err = fmt.Errorf("bad password")
enc, _ := crypt.Encrypt([]byte(err.Error()), strongKeyForEncryption) enc, _ := crypt.Decrypt([]byte(err.Error()), strongKeyForEncryption)
if err = c.Send(enc); err != nil { if err := c.Send(enc); err != nil {
return "", fmt.Errorf("send error: %w", err) return "", fmt.Errorf("send error: %w", err)
} }
return return
@ -340,6 +253,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
err = c.Send(bSend) err = c.Send(bSend)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
s.deleteRoom(room)
return return
} }
return return
@ -398,11 +312,11 @@ func (s *server) deleteRoom(room string) {
} }
s.rooms.rooms[room] = roomInfo{first: nil, second: nil} s.rooms.rooms[room] = roomInfo{first: nil, second: nil}
delete(s.rooms.rooms, room) delete(s.rooms.rooms, room)
} }
// chanFromConn creates a channel from a Conn object, and sends everything it // chanFromConn creates a channel from a Conn object, and sends everything it
// // Read()s from the socket to the channel.
// Read()s from the socket to the channel.
func chanFromConn(conn net.Conn) chan []byte { func chanFromConn(conn net.Conn) chan []byte {
c := make(chan []byte, 1) c := make(chan []byte, 1)
if err := conn.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil { if err := conn.SetReadDeadline(time.Now().Add(3 * time.Hour)); err != nil {
@ -458,29 +372,6 @@ func pipe(conn1 net.Conn, conn2 net.Conn) {
} }
} }
func PingServer(address string) (err error) {
log.Debugf("pinging %s", address)
c, err := comm.NewConnection(address, 300*time.Millisecond)
if err != nil {
log.Debug(err)
return
}
err = c.Send([]byte("ping"))
if err != nil {
log.Debug(err)
return
}
b, err := c.Receive()
if err != nil {
log.Debug(err)
return
}
if bytes.Equal(b, []byte("pong")) {
return nil
}
return fmt.Errorf("no pong")
}
// ConnectToTCPServer will initiate a new connection // ConnectToTCPServer will initiate a new connection
// to the specified address, room with optional time limit // to the specified address, room with optional time limit
func ConnectToTCPServer(address, password, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) { func ConnectToTCPServer(address, password, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) {
@ -490,104 +381,87 @@ func ConnectToTCPServer(address, password, room string, timelimit ...time.Durati
c, err = comm.NewConnection(address) c, err = comm.NewConnection(address)
} }
if err != nil { if err != nil {
log.Debug(err)
return return
} }
// get PAKE connection with server to establish strong key to transfer info // get PAKE connection with server to establish strong key to transfer info
A, err := pake.InitCurve(weakKey, 0, "siec") A, err := pake.InitCurve(weakKey, 0, "siec", 1*time.Microsecond)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
err = c.Send(A.Bytes()) err = c.Send(A.Bytes())
if err != nil { if err != nil {
log.Debug(err)
return return
} }
Bbytes, err := c.Receive() Bbytes, err := c.Receive()
if err != nil { if err != nil {
log.Debug(err)
return return
} }
err = A.Update(Bbytes) err = A.Update(Bbytes)
if err != nil { if err != nil {
log.Debug(err) return
}
err = c.Send(A.Bytes())
if err != nil {
return return
} }
strongKey, err := A.SessionKey() strongKey, err := A.SessionKey()
if err != nil { if err != nil {
log.Debug(err)
return return
} }
log.Debugf("strong key: %x", strongKey) log.Debugf("strong key: %x", strongKey)
strongKeyForEncryption, salt, err := crypt.New(strongKey, nil) strongKeyForEncryption, salt, err := crypt.New(strongKey, nil)
if err != nil {
log.Debug(err)
return
}
// send salt // send salt
err = c.Send(salt) err = c.Send(salt)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
log.Debug("sending password") log.Debug("sending password")
bSend, err := crypt.Encrypt([]byte(password), strongKeyForEncryption) bSend, err := crypt.Encrypt([]byte(password), strongKeyForEncryption)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
err = c.Send(bSend) err = c.Send(bSend)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
log.Debug("waiting for first ok") log.Debug("waiting for first ok")
enc, err := c.Receive() enc, err := c.Receive()
if err != nil { if err != nil {
log.Debug(err)
return return
} }
data, err := crypt.Decrypt(enc, strongKeyForEncryption) data, err := crypt.Decrypt(enc, strongKeyForEncryption)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
if !strings.Contains(string(data), "|||") { if !strings.Contains(string(data), "|||") {
err = fmt.Errorf("bad response: %s", string(data)) err = fmt.Errorf("bad response: %s", string(data))
log.Debug(err)
return return
} }
banner = strings.Split(string(data), "|||")[0] banner = strings.Split(string(data), "|||")[0]
ipaddr = strings.Split(string(data), "|||")[1] ipaddr = strings.Split(string(data), "|||")[1]
log.Debugf("sending room; %s", room) log.Debug("sending room")
bSend, err = crypt.Encrypt([]byte(room), strongKeyForEncryption) bSend, err = crypt.Encrypt([]byte(room), strongKeyForEncryption)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
err = c.Send(bSend) err = c.Send(bSend)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
log.Debug("waiting for room confirmation") log.Debug("waiting for room confirmation")
enc, err = c.Receive() enc, err = c.Receive()
if err != nil { if err != nil {
log.Debug(err)
return return
} }
data, err = crypt.Decrypt(enc, strongKeyForEncryption) data, err = crypt.Decrypt(enc, strongKeyForEncryption)
if err != nil { if err != nil {
log.Debug(err)
return return
} }
if !bytes.Equal(data, []byte("ok")) { if !bytes.Equal(data, []byte("ok")) {
err = fmt.Errorf("got bad response: %s", data) err = fmt.Errorf("got bad response: %s", data)
log.Debug(err)
return return
} }
log.Debug("all set") log.Debug("all set")

View File

@ -12,34 +12,28 @@ import (
func BenchmarkConnection(b *testing.B) { func BenchmarkConnection(b *testing.B) {
log.SetLevel("trace") log.SetLevel("trace")
go Run("debug", "127.0.0.1", "8283", "pass123", "8284") go Run("debug", "8283", "pass123", "8284")
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c, _, _, _ := ConnectToTCPServer("127.0.0.1:8283", "pass123", fmt.Sprintf("testroom%d", i), 1*time.Minute) c, _, _, _ := ConnectToTCPServer("localhost:8283", "pass123", fmt.Sprintf("testroom%d", i), 1*time.Minute)
c.Close() c.Close()
} }
} }
func TestTCP(t *testing.T) { func TestTCP(t *testing.T) {
log.SetLevel("error") log.SetLevel("error")
timeToRoomDeletion := 100 * time.Millisecond timeToRoomDeletion = 100 * time.Millisecond
go RunWithOptionsAsync("127.0.0.1", "8381", "pass123", WithBanner("8382"), WithLogLevel("debug"), WithRoomTTL(timeToRoomDeletion)) go Run("debug", "8281", "pass123", "8282")
time.Sleep(timeToRoomDeletion) time.Sleep(100 * time.Millisecond)
err := PingServer("127.0.0.1:8381") c1, banner, _, err := ConnectToTCPServer("localhost:8281", "pass123", "testRoom", 1*time.Minute)
assert.Equal(t, banner, "8282")
assert.Nil(t, err) assert.Nil(t, err)
err = PingServer("127.0.0.1:8333") c2, _, _, err := ConnectToTCPServer("localhost:8281", "pass123", "testRoom")
assert.Nil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", "testRoom")
assert.NotNil(t, err) assert.NotNil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", "testRoom", 1*time.Nanosecond)
time.Sleep(timeToRoomDeletion)
c1, banner, _, err := ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom", 1*time.Minute)
assert.Equal(t, banner, "8382")
assert.Nil(t, err)
c2, _, _, err := ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom")
assert.Nil(t, err)
_, _, _, err = ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom")
assert.NotNil(t, err)
_, _, _, err = ConnectToTCPServer("127.0.0.1:8381", "pass123", "testRoom", 1*time.Nanosecond)
assert.NotNil(t, err) assert.NotNil(t, err)
// try sending data // try sending data

View File

@ -1,64 +1,26 @@
package utils package utils
import ( import (
"archive/zip"
"bufio" "bufio"
"bytes" "bytes"
"compress/flate"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"math" "math"
"math/big"
"net" "net"
"net/http" "net/http"
"os" "os"
"path"
"path/filepath"
"strings" "strings"
"time"
"unicode"
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
"github.com/kalafut/imohash" "github.com/kalafut/imohash"
"github.com/minio/highwayhash" "github.com/schollz/mnemonicode"
"github.com/schollz/croc/v10/src/mnemonicode"
log "github.com/schollz/logger"
"github.com/schollz/progressbar/v3"
) )
const NbPinNumbers = 4
const NbBytesWords = 4
// Get or create home directory
func GetConfigDir(requireValidPath bool) (homedir string, err error) {
if envHomedir, isSet := os.LookupEnv("CROC_CONFIG_DIR"); isSet {
homedir = envHomedir
} else if xdgConfigHome, isSet := os.LookupEnv("XDG_CONFIG_HOME"); isSet {
homedir = path.Join(xdgConfigHome, "croc")
} else {
homedir, err = os.UserHomeDir()
if err != nil {
if !requireValidPath {
err = nil
homedir = ""
}
return
}
homedir = path.Join(homedir, ".config", "croc")
}
if requireValidPath {
if _, err = os.Stat(homedir); os.IsNotExist(err) {
err = os.MkdirAll(homedir, 0o700)
}
}
return
}
// Exists reports whether the named file or directory exists. // Exists reports whether the named file or directory exists.
func Exists(name string) bool { func Exists(name string) bool {
if _, err := os.Stat(name); err != nil { if _, err := os.Stat(name); err != nil {
@ -77,83 +39,13 @@ func GetInput(prompt string) string {
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }
// HashFile returns the hash of a file or, in case of a symlink, the // HashFile returns the hash of a file
// SHA256 hash of its target. Takes an argument to specify the algorithm to use. func HashFile(fname string) (hash256 []byte, err error) {
func HashFile(fname string, algorithm string, showProgress ...bool) (hash256 []byte, err error) { return IMOHashFile(fname)
doShowProgress := false
if len(showProgress) > 0 {
doShowProgress = showProgress[0]
}
var fstats os.FileInfo
fstats, err = os.Lstat(fname)
if err != nil {
return nil, err
}
if fstats.Mode()&os.ModeSymlink != 0 {
var target string
target, err = os.Readlink(fname)
if err != nil {
return nil, err
}
return []byte(SHA256(target)), nil
}
switch algorithm {
case "imohash":
return IMOHashFile(fname)
case "md5":
return MD5HashFile(fname, doShowProgress)
case "xxhash":
return XXHashFile(fname, doShowProgress)
case "highway":
return HighwayHashFile(fname, doShowProgress)
}
err = fmt.Errorf("unspecified algorithm")
return
}
// HighwayHashFile returns highwayhash of a file
func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
key, err := hex.DecodeString("1553c5383fb0b86578c3310da665b4f6e0521acf22eb58a99532ffed02a6b115")
if err != nil {
return
}
h, err := highwayhash.New(key)
if err != nil {
err = fmt.Errorf("could not create highwayhash: %s", err.Error())
return
}
if doShowProgress {
stat, _ := f.Stat()
fnameShort := path.Base(fname)
if len(fnameShort) > 20 {
fnameShort = fnameShort[:20] + "..."
}
bar := progressbar.NewOptions64(stat.Size(),
progressbar.OptionSetWriter(os.Stderr),
progressbar.OptionShowBytes(true),
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
progressbar.OptionClearOnFinish(),
)
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
return
}
} else {
if _, err = io.Copy(h, f); err != nil {
return
}
}
hashHighway = h.Sum(nil)
return
} }
// MD5HashFile returns MD5 hash // MD5HashFile returns MD5 hash
func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { func MD5HashFile(fname string) (hash256 []byte, err error) {
f, err := os.Open(fname) f, err := os.Open(fname)
if err != nil { if err != nil {
return return
@ -161,50 +53,23 @@ func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error)
defer f.Close() defer f.Close()
h := md5.New() h := md5.New()
if doShowProgress { if _, err = io.Copy(h, f); err != nil {
stat, _ := f.Stat() return
fnameShort := path.Base(fname)
if len(fnameShort) > 20 {
fnameShort = fnameShort[:20] + "..."
}
bar := progressbar.NewOptions64(stat.Size(),
progressbar.OptionSetWriter(os.Stderr),
progressbar.OptionShowBytes(true),
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
progressbar.OptionClearOnFinish(),
)
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
return
}
} else {
if _, err = io.Copy(h, f); err != nil {
return
}
} }
hash256 = h.Sum(nil) hash256 = h.Sum(nil)
return return
} }
var imofull = imohash.NewCustom(0, 0)
var imopartial = imohash.NewCustom(16*16*8*1024, 128*1024)
// IMOHashFile returns imohash // IMOHashFile returns imohash
func IMOHashFile(fname string) (hash []byte, err error) { func IMOHashFile(fname string) (hash []byte, err error) {
b, err := imopartial.SumFile(fname) b, err := imohash.SumFile(fname)
hash = b[:]
return
}
// IMOHashFileFull returns imohash of full file
func IMOHashFileFull(fname string) (hash []byte, err error) {
b, err := imofull.SumFile(fname)
hash = b[:] hash = b[:]
return return
} }
// XXHashFile returns the xxhash of a file // XXHashFile returns the xxhash of a file
func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { func XXHashFile(fname string) (hash256 []byte, err error) {
f, err := os.Open(fname) f, err := os.Open(fname)
if err != nil { if err != nil {
return return
@ -212,25 +77,8 @@ func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) {
defer f.Close() defer f.Close()
h := xxhash.New() h := xxhash.New()
if doShowProgress { if _, err = io.Copy(h, f); err != nil {
stat, _ := f.Stat() return
fnameShort := path.Base(fname)
if len(fnameShort) > 20 {
fnameShort = fnameShort[:20] + "..."
}
bar := progressbar.NewOptions64(stat.Size(),
progressbar.OptionSetWriter(os.Stderr),
progressbar.OptionShowBytes(true),
progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)),
progressbar.OptionClearOnFinish(),
)
if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil {
return
}
} else {
if _, err = io.Copy(h, f); err != nil {
return
}
} }
hash256 = h.Sum(nil) hash256 = h.Sum(nil)
@ -241,26 +89,24 @@ func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) {
func SHA256(s string) string { func SHA256(s string) string {
sha := sha256.New() sha := sha256.New()
sha.Write([]byte(s)) sha.Write([]byte(s))
return hex.EncodeToString(sha.Sum(nil)) return fmt.Sprintf("%x", sha.Sum(nil))
} }
// PublicIP returns public ip address // PublicIP returns public ip address
func PublicIP() (ip string, err error) { func PublicIP() (ip string, err error) {
// ask ipv4.icanhazip.com for the public ip resp, err := http.Get("https://canhazip.com")
// by making http request
// if the request fails, return nothing
resp, err := http.Get("http://ipv4.icanhazip.com")
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
// read the body of the response if resp.StatusCode == http.StatusOK {
// and return the ip address bodyBytes, err := ioutil.ReadAll(resp.Body)
buf := new(bytes.Buffer) if err != nil {
buf.ReadFrom(resp.Body) return "", err
ip = strings.TrimSpace(buf.String()) }
ip = strings.TrimSpace(string(bodyBytes))
}
return return
} }
@ -268,8 +114,7 @@ func PublicIP() (ip string, err error) {
func LocalIP() string { func LocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80") conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil { if err != nil {
log.Error(err) log.Fatal(err)
return ""
} }
defer conn.Close() defer conn.Close()
@ -278,32 +123,18 @@ func LocalIP() string {
return localAddr.IP.String() return localAddr.IP.String()
} }
func GenerateRandomPin() string { // GetRandomName returns mnemoicoded random name
s := ""
max := new(big.Int)
max.SetInt64(9)
for i := 0; i < NbPinNumbers; i++ {
v, err := rand.Int(rand.Reader, max)
if err != nil {
panic(err)
}
s += fmt.Sprintf("%d", v)
}
return s
}
// GetRandomName returns mnemonicoded random name
func GetRandomName() string { func GetRandomName() string {
var result []string var result []string
bs := make([]byte, NbBytesWords) bs := make([]byte, 4)
rand.Read(bs) rand.Read(bs)
result = mnemonicode.EncodeWordList(result, bs) result = mnemonicode.EncodeWordList(result, bs)
return GenerateRandomPin() + "-" + strings.Join(result, "-") return strings.Join(result, "-")
} }
// ByteCountDecimal converts bytes to human readable byte string // ByteCountDecimal converts bytes to human readable byte string
func ByteCountDecimal(b int64) string { func ByteCountDecimal(b int64) string {
const unit = 1024 const unit = 1000
if b < unit { if b < unit {
return fmt.Sprintf("%d B", b) return fmt.Sprintf("%d B", b)
} }
@ -364,6 +195,7 @@ func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int6
} }
} }
chunkRanges = append(chunkRanges, int64(curCount+1)) chunkRanges = append(chunkRanges, int64(curCount+1))
chunks = chunkRanges
} }
return return
} }
@ -400,238 +232,3 @@ func GetLocalIPs() (ips []string, err error) {
} }
return return
} }
func RandomFileName() (fname string, err error) {
f, err := os.CreateTemp(".", "croc-stdin-")
if err != nil {
return
}
fname = f.Name()
_ = f.Close()
return
}
func FindOpenPorts(host string, portNumStart, numPorts int) (openPorts []int) {
openPorts = []int{}
for port := portNumStart; port-portNumStart < 200; port++ {
timeout := 100 * time.Millisecond
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprint(port)), timeout)
if conn != nil {
conn.Close()
} else if err != nil {
openPorts = append(openPorts, port)
}
if len(openPorts) >= numPorts {
return
}
}
return
}
// local ip determination
// https://stackoverflow.com/questions/41240761/check-if-ip-address-is-in-private-network-space
var privateIPBlocks []*net.IPNet
func init() {
for _, cidr := range []string{
"127.0.0.0/8", // IPv4 loopback
"10.0.0.0/8", // RFC1918
"172.16.0.0/12", // RFC1918
"192.168.0.0/16", // RFC1918
"169.254.0.0/16", // RFC3927 link-local
"::1/128", // IPv6 loopback
"fe80::/10", // IPv6 link-local
"fc00::/7", // IPv6 unique local addr
} {
_, block, err := net.ParseCIDR(cidr)
if err != nil {
panic(fmt.Errorf("parse error on %q: %v", cidr, err))
}
privateIPBlocks = append(privateIPBlocks, block)
}
}
func IsLocalIP(ipaddress string) bool {
if strings.Contains(ipaddress, "127.0.0.1") {
return true
}
host, _, _ := net.SplitHostPort(ipaddress)
ip := net.ParseIP(host)
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return true
}
for _, block := range privateIPBlocks {
if block.Contains(ip) {
return true
}
}
return false
}
func ZipDirectory(destination string, source string) (err error) {
if _, err = os.Stat(destination); err == nil {
log.Errorf("%s file already exists!\n", destination)
}
fmt.Fprintf(os.Stderr, "Zipping %s to %s\n", source, destination)
file, err := os.Create(destination)
if err != nil {
log.Error(err)
}
defer file.Close()
writer := zip.NewWriter(file)
// no compression because croc does its compression on the fly
writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
return flate.NewWriter(out, flate.NoCompression)
})
defer writer.Close()
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Error(err)
}
if info.Mode().IsRegular() {
f1, err := os.Open(path)
if err != nil {
log.Error(err)
}
defer f1.Close()
zipPath := strings.ReplaceAll(path, source, strings.TrimSuffix(destination, ".zip"))
zipPath = filepath.ToSlash(zipPath)
w1, err := writer.Create(zipPath)
if err != nil {
log.Error(err)
}
if _, err := io.Copy(w1, f1); err != nil {
log.Error(err)
}
fmt.Fprintf(os.Stderr, "\r\033[2K")
fmt.Fprintf(os.Stderr, "\rAdding %s", zipPath)
}
return nil
})
if err != nil {
log.Error(err)
}
fmt.Fprintf(os.Stderr, "\n")
return nil
}
func UnzipDirectory(destination string, source string) error {
archive, err := zip.OpenReader(source)
if err != nil {
log.Error(err)
}
defer archive.Close()
for _, f := range archive.File {
filePath := filepath.Join(destination, f.Name)
fmt.Fprintf(os.Stderr, "\r\033[2K")
fmt.Fprintf(os.Stderr, "\rUnzipping file %s", filePath)
// Issue #593 conceal path traversal vulnerability
// make sure the filepath does not have ".."
filePath = filepath.Clean(filePath)
if strings.Contains(filePath, "..") {
log.Errorf("Invalid file path %s\n", filePath)
}
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
log.Error(err)
}
// check if file exists
if _, err := os.Stat(filePath); err == nil {
prompt := fmt.Sprintf("\nOverwrite '%s'? (y/N) ", filePath)
choice := strings.ToLower(GetInput(prompt))
if choice != "y" && choice != "yes" {
fmt.Fprintf(os.Stderr, "Skipping '%s'\n", filePath)
continue
}
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
log.Error(err)
}
fileInArchive, err := f.Open()
if err != nil {
log.Error(err)
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
log.Error(err)
}
dstFile.Close()
fileInArchive.Close()
}
fmt.Fprintf(os.Stderr, "\n")
return nil
}
// ValidFileName checks if a filename is valid
// by making sure it has no invisible characters
func ValidFileName(fname string) (err error) {
// make sure it doesn't contain unicode or invisible characters
for _, r := range fname {
if !unicode.IsGraphic(r) {
err = fmt.Errorf("non-graphical unicode: %x U+%d in '%x'", string(r), r, fname)
return
}
if !unicode.IsPrint(r) {
err = fmt.Errorf("non-printable unicode: %x U+%d in '%x'", string(r), r, fname)
return
}
}
// make sure basename does not include ".." or path separators
_, basename := filepath.Split(fname)
if strings.Contains(basename, "..") {
err = fmt.Errorf("basename cannot contain '..': '%s'", basename)
return
}
if strings.Contains(basename, string(os.PathSeparator)) {
err = fmt.Errorf("basename cannot contain path separators: '%s'", basename)
return
}
// make sure the filename is not an absolute path
if filepath.IsAbs(fname) {
err = fmt.Errorf("filename cannot be an absolute path: '%s'", fname)
return
}
return
}
const crocRemovalFile = "croc-marked-files.txt"
func MarkFileForRemoval(fname string) {
// append the fname to the list of files to remove
f, err := os.OpenFile(crocRemovalFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
log.Debug(err)
return
}
defer f.Close()
_, err = f.WriteString(fname + "\n")
}
func RemoveMarkedFiles() (err error) {
// read the file and remove all the files
f, err := os.Open(crocRemovalFile)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fname := scanner.Text()
err = os.Remove(fname)
if err == nil {
log.Tracef("Removed %s", fname)
}
}
os.Remove(crocRemovalFile)
return
}

View File

@ -3,29 +3,25 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"os" "os"
"path"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const TCP_BUFFER_SIZE = 1024 * 64
var bigFileSize = 75000000
func bigFile() { func bigFile() {
os.WriteFile("bigfile.test", bytes.Repeat([]byte("z"), bigFileSize), 0o666) ioutil.WriteFile("bigfile.test", bytes.Repeat([]byte("z"), 75000000), 0666)
} }
func BenchmarkMD5(b *testing.B) { func BenchmarkMD5(b *testing.B) {
bigFile() bigFile()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
MD5HashFile("bigfile.test", false) MD5HashFile("bigfile.test")
} }
} }
@ -33,10 +29,9 @@ func BenchmarkXXHash(b *testing.B) {
bigFile() bigFile()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
XXHashFile("bigfile.test", false) XXHashFile("bigfile.test")
} }
} }
func BenchmarkImoHash(b *testing.B) { func BenchmarkImoHash(b *testing.B) {
bigFile() bigFile()
b.ResetTimer() b.ResetTimer()
@ -45,37 +40,6 @@ func BenchmarkImoHash(b *testing.B) {
} }
} }
func BenchmarkHighwayHash(b *testing.B) {
bigFile()
b.ResetTimer()
for i := 0; i < b.N; i++ {
HighwayHashFile("bigfile.test", false)
}
}
func BenchmarkImoHashFull(b *testing.B) {
bigFile()
b.ResetTimer()
for i := 0; i < b.N; i++ {
IMOHashFileFull("bigfile.test")
}
}
func BenchmarkSha256(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
SHA256("hello,world")
}
}
func BenchmarkMissingChunks(b *testing.B) {
bigFile()
b.ResetTimer()
for i := 0; i < b.N; i++ {
MissingChunks("bigfile.test", int64(bigFileSize), TCP_BUFFER_SIZE/2)
}
}
func TestExists(t *testing.T) { func TestExists(t *testing.T) {
bigFile() bigFile()
defer os.Remove("bigfile.test") defer os.Remove("bigfile.test")
@ -87,20 +51,10 @@ func TestExists(t *testing.T) {
func TestMD5HashFile(t *testing.T) { func TestMD5HashFile(t *testing.T) {
bigFile() bigFile()
defer os.Remove("bigfile.test") defer os.Remove("bigfile.test")
b, err := MD5HashFile("bigfile.test", false) b, err := MD5HashFile("bigfile.test")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "8304ff018e02baad0e3555bade29a405", fmt.Sprintf("%x", b)) assert.Equal(t, "8304ff018e02baad0e3555bade29a405", fmt.Sprintf("%x", b))
_, err = MD5HashFile("bigfile.test.nofile", false) _, err = MD5HashFile("bigfile.test.nofile")
assert.NotNil(t, err)
}
func TestHighwayHashFile(t *testing.T) {
bigFile()
defer os.Remove("bigfile.test")
b, err := HighwayHashFile("bigfile.test", false)
assert.Nil(t, err)
assert.Equal(t, "3c32999529323ed66a67aeac5720c7bf1301dcc5dca87d8d46595e85ff990329", fmt.Sprintf("%x", b))
_, err = HighwayHashFile("bigfile.test.nofile", false)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
@ -109,16 +63,16 @@ func TestIMOHashFile(t *testing.T) {
defer os.Remove("bigfile.test") defer os.Remove("bigfile.test")
b, err := IMOHashFile("bigfile.test") b, err := IMOHashFile("bigfile.test")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "c0d1e12301e6c635f6d4a8ea5c897437", fmt.Sprintf("%x", b)) assert.Equal(t, "c0d1e123ca94148ffea146137684ebb9", fmt.Sprintf("%x", b))
} }
func TestXXHashFile(t *testing.T) { func TestXXHashFile(t *testing.T) {
bigFile() bigFile()
defer os.Remove("bigfile.test") defer os.Remove("bigfile.test")
b, err := XXHashFile("bigfile.test", false) b, err := XXHashFile("bigfile.test")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "4918740eb5ccb6f7", fmt.Sprintf("%x", b)) assert.Equal(t, "4918740eb5ccb6f7", fmt.Sprintf("%x", b))
_, err = XXHashFile("nofile", false) _, err = XXHashFile("nofile")
assert.NotNil(t, err) assert.NotNil(t, err)
} }
@ -127,9 +81,9 @@ func TestSHA256(t *testing.T) {
} }
func TestByteCountDecimal(t *testing.T) { func TestByteCountDecimal(t *testing.T) {
assert.Equal(t, "10.0 kB", ByteCountDecimal(10240)) assert.Equal(t, "10.0 kB", ByteCountDecimal(10000))
assert.Equal(t, "50 B", ByteCountDecimal(50)) assert.Equal(t, "50 B", ByteCountDecimal(50))
assert.Equal(t, "12.4 MB", ByteCountDecimal(13002343)) assert.Equal(t, "12.4 MB", ByteCountDecimal(12378517))
} }
func TestMissingChunks(t *testing.T) { func TestMissingChunks(t *testing.T) {
@ -138,9 +92,9 @@ func TestMissingChunks(t *testing.T) {
rand.Seed(1) rand.Seed(1)
bigBuff := make([]byte, fileSize) bigBuff := make([]byte, fileSize)
rand.Read(bigBuff) rand.Read(bigBuff)
os.WriteFile("missing.test", bigBuff, 0o644) ioutil.WriteFile("missing.test", bigBuff, 0644)
empty := make([]byte, chunkSize) empty := make([]byte, chunkSize)
f, err := os.OpenFile("missing.test", os.O_RDWR, 0o644) f, err := os.OpenFile("missing.test", os.O_RDWR, 0644)
assert.Nil(t, err) assert.Nil(t, err)
for block := 0; block < fileSize/chunkSize; block++ { for block := 0; block < fileSize/chunkSize; block++ {
if block == 0 || block == 4 || block == 5 || block >= 7 { if block == 0 || block == 4 || block == 5 || block >= 7 {
@ -158,7 +112,7 @@ func TestMissingChunks(t *testing.T) {
os.Remove("missing.test") os.Remove("missing.test")
content := []byte("temporary file's content") content := []byte("temporary file's content")
tmpfile, err := os.CreateTemp("", "example") tmpfile, err := ioutil.TempFile("", "example")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -190,22 +144,22 @@ func TestMissingChunks(t *testing.T) {
func TestHashFile(t *testing.T) { func TestHashFile(t *testing.T) {
content := []byte("temporary file's content") content := []byte("temporary file's content")
tmpfile, err := os.CreateTemp("", "example") tmpfile, err := ioutil.TempFile("", "example")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer os.Remove(tmpfile.Name()) // clean up defer os.Remove(tmpfile.Name()) // clean up
if _, err = tmpfile.Write(content); err != nil { if _, err := tmpfile.Write(content); err != nil {
panic(err) panic(err)
} }
if err = tmpfile.Close(); err != nil { if err := tmpfile.Close(); err != nil {
panic(err) panic(err)
} }
hashed, err := HashFile(tmpfile.Name(), "xxhash") hashed, err := HashFile(tmpfile.Name())
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "e66c561610ad51e2", fmt.Sprintf("%x", hashed)) assert.Equal(t, "18c9673a4bb8325d323e7f24fda9ae1e", fmt.Sprintf("%x", hashed))
} }
func TestPublicIP(t *testing.T) { func TestPublicIP(t *testing.T) {
@ -223,45 +177,5 @@ func TestLocalIP(t *testing.T) {
func TestGetRandomName(t *testing.T) { func TestGetRandomName(t *testing.T) {
name := GetRandomName() name := GetRandomName()
fmt.Println(name)
assert.NotEmpty(t, name) assert.NotEmpty(t, name)
} }
func intSliceSame(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func TestFindOpenPorts(t *testing.T) {
openPorts := FindOpenPorts("127.0.0.1", 9009, 4)
if !intSliceSame(openPorts, []int{9009, 9010, 9011, 9012}) && !intSliceSame(openPorts, []int{9014, 9015, 9016, 9017}) {
t.Errorf("openPorts: %v", openPorts)
}
}
func TestIsLocalIP(t *testing.T) {
assert.True(t, IsLocalIP("192.168.0.14:9009"))
}
func TestValidFileName(t *testing.T) {
// contains regular characters
assert.Nil(t, ValidFileName("中文.csl"))
// contains regular characters
assert.Nil(t, ValidFileName("[something].csl"))
// contains regular characters
assert.Nil(t, ValidFileName("[(something)].csl"))
// contains invisible character
err := ValidFileName("D中文.cslouglas")
assert.NotNil(t, err)
assert.Equal(t, "non-graphical unicode: e2808b U+8203 in '44e4b8ade696872e63736c6f75676c6173e2808b'", err.Error())
assert.NotNil(t, ValidFileName("hi..txt"))
assert.NotNil(t, ValidFileName(path.Join(string(os.PathSeparator), "abs", string(os.PathSeparator), "hi.txt")))
}