Skip to content

Commit

Permalink
[1] Prompt for password on terminal
Browse files Browse the repository at this point in the history
  • Loading branch information
arashpayan authored Nov 20, 2023
2 parents fe69c9b + 058d484 commit 4460201
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 21 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,37 @@ A tool for testing connectivity to SMB shares. With it you can:

### Calculate the MD5 hash of a file
```
carnival md5 smb://user:password@address/sharename/path/to/file.txt
$ carnival -u user md5 smb://address/sharename/path/to/file.txt
Password:
```

### Copy a file from an SMB share to a local destination
```
carnival cp smb://user:password@address/sharename/file.bin .
$ carnival -u user cp smb://address/sharename/file.bin .
Password:
# OR rename it at the destination
carnival cp smb://user:password@address/sharename/file.bin file.binary
$ carnival -u user cp smb://address/sharename/file.bin file.binary
Password:
```
The `cp` command will tell you how long the transfer took and the average transfer speed.

### List the files in a directory
```
# List the files at the root of the share
carnival ls smb://user:password@address/sharename/
$ carnival -u user ls smb://address/sharename/
Password:
# List the files in the 'Games' directory of the share
carnival ls smb://user:password@address/sharename/Games
$ carnival -u user ls smb://address/sharename/Games
Password:
```

### Print the names of publicly visible SMB shares
```
carnival shares smb://user:password@address
$ carnival -u user shares smb://address
Password:
```

If a username and password are not included in the SMB url, carnival will authenticate as `guest` with an empty password.
If the username is set through the `-u/--username` flag, the password will be prompted

If no username is set, an anonymous session will be attempted
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func main() {
Name: samba.FlagMapposix,
Usage: "use the equivalent of the samba 'mapposix' option",
},
&cli.StringFlag{
Aliases: []string{"u"},
Name: samba.FlagUsername,
Usage: "use the given username, the program will prompt for password",
},
}

prog.Commands = []*cli.Command{
Expand Down
52 changes: 50 additions & 2 deletions pkg/samba/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"errors"
"fmt"
"net/url"
"os"
"path"
"strings"
"syscall"

"github.com/urfave/cli/v2"
"golang.org/x/term"
)

type Credentials struct {
Expand All @@ -22,15 +25,55 @@ type URL struct {
Credentials *Credentials
}

// credentialsFromContext gets username from cli context and if set, it prompts the password from the terminal.
// If the flag is not set, then empty credentials will be returned (for anonymous session).
func credentialsFromContext(ctx *cli.Context) (*Credentials, error) {
creds := &Credentials{}

if username := ctx.String(FlagUsername); username != "" {
password, err := promptForPassword()
if err != nil {
return nil, err
}

creds.Username = username
creds.Password = password
}

return creds, nil
}

// promptForPassword prompts password from terminal without echoing it
func promptForPassword() (string, error) {
_, err := fmt.Fprint(os.Stderr, "Password:")
if err != nil {
return "", err
}

b, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return "", err
}

_, _ = fmt.Fprintln(os.Stderr)

return string(b), nil
}

func urlFromContext(ctx *cli.Context) (URL, error) {
if ctx.NArg() == 0 {
return URL{}, errors.New("missing smb url")
}

return newURL(ctx.Args().First())
creds, err := credentialsFromContext(ctx)
if err != nil {
return URL{}, err
}

return newURL(ctx.Args().First(), creds)
}

func newURL(str string) (URL, error) {
func newURL(str string, creds *Credentials) (URL, error) {
u2 := URL{}
u, err := url.Parse(str)
if err != nil {
Expand All @@ -47,6 +90,11 @@ func newURL(str string) (URL, error) {
if u.Port() == "" {
u2.Address = u.Hostname() + ":445"
}

if creds != nil {
u2.Credentials = creds
}

if u.User != nil {
u2.Credentials = &Credentials{
Username: u.User.Username(),
Expand Down
36 changes: 30 additions & 6 deletions pkg/samba/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,43 @@ func TestNewURL(t *testing.T) {
t.Parallel()

testData := []struct {
input string
inputURL string
creds *Credentials
expected URL
}{
{
input: "smb://address/share/path",
inputURL: "smb://address/share/path",
expected: URL{
Address: "address:445",
Share: "share",
Path: "path",
},
},
{
input: "smb://host.tld:3622/share",
inputURL: "smb://address/share/path",
creds: &Credentials{
Username: "foo",
Password: "bar",
},
expected: URL{
Address: "address:445",
Share: "share",
Path: "path",
Credentials: &Credentials{
Username: "foo",
Password: "bar",
},
},
},
{
inputURL: "smb://host.tld:3622/share",
expected: URL{
Address: "host.tld:3622",
Share: "share",
},
},
{
input: "smb://user:[email protected]/myshare/path/to/file.txt",
inputURL: "smb://user:[email protected]/myshare/path/to/file.txt",
expected: URL{
Address: "address.com:445",
Share: "myshare",
Expand All @@ -48,9 +65,16 @@ func TestNewURL(t *testing.T) {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()

actual, err := newURL(td.input)
actual, err := newURL(td.inputURL, td.creds)
require.NoError(t, err)
require.Equal(t, td.expected, actual)
require.Equal(t, td.expected.Address, actual.Address)
require.Equal(t, td.expected.Share, actual.Share)
require.Equal(t, td.expected.Path, actual.Path)
if td.expected.Credentials != nil {
require.NotNil(t, actual.Credentials)
require.Equal(t, td.expected.Credentials.Username, actual.Credentials.Username)
require.Equal(t, td.expected.Credentials.Password, actual.Credentials.Password)
}
})
}
}
7 changes: 6 additions & 1 deletion pkg/samba/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,13 @@ func CopyTo(ctx *cli.Context) error {
return errors.New("2 arguments required")
}

creds, err := credentialsFromContext(ctx)
if err != nil {
return err
}

srcPath := ctx.Args().Get(0)
u, err := newURL(ctx.Args().Get(1))
u, err := newURL(ctx.Args().Get(1), creds)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/samba/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ const (
FlagDomain = "domain"
FlagMapchars = "mapchars"
FlagMapposix = "mapposix"
FlagUsername = "username"
)
5 changes: 0 additions & 5 deletions pkg/samba/mount.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package samba

import (
"fmt"
"net"

"github.com/cloudsoda/go-smb2"
Expand All @@ -20,10 +19,6 @@ func parseOptions(ctx *cli.Context) []smb2.MountOption {
}

func connect(u URL, domain string) (*smb2.Session, error) {
if u.Credentials == nil && domain == "" {
return nil, fmt.Errorf("--%s was specified but no user was specified in the URL", FlagDomain)
}

conn, err := net.Dial("tcp", u.Address)
if err != nil {
return nil, err
Expand Down

0 comments on commit 4460201

Please sign in to comment.