Repository: rclone/rclone (language: go)

Issue:
Cannot access SMB share via Kerberos
<!--

We understand you are having a problem with rclone; we want to help you with that!

**STOP and READ**
**YOUR POST WILL BE REMOVED IF IT IS LOW QUALITY**:
Please show the effort you've put into solving the problem and please be specific.
People are volunteering their time to help! Low effort posts are not likely to get good answers!

If you think you might have found a bug, try to replicate it with the latest beta (or stable).
The update instructions are available at https://rclone.org/commands/rclone_selfupdate/

If you can still replicate it or just got a question then please use the rclone forum:

    https://forum.rclone.org/

for a quick response instead of filing an issue on this repo.

If nothing else helps, then please fill in the info below which helps us help you.

**DO NOT REDACT** any information except passwords/keys/personal info.

You should use 3 backticks to begin and end your paste to make it readable.

Make sure to include a log obtained with '-vv'.

You can also use '-vv --log-file bug.log' and a service such as https://pastebin.com or https://gist.github.com/

Thank you

The Rclone Developers

-->

#### The associated forum post URL from `https://forum.rclone.org`



#### What is the problem you are having with rclone?

It seems rclone is using NTLM by default which has been disabled by our security team therefore I get a `couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.` error.

#### What is your rclone version (output from `rclone version`)
```config
# rclone version
rclone v1.61.1
- os/version: centos 7.9.2009 (64 bit)
- os/kernel: 3.10.0-1160.62.1.el7.x86_64 (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.19.4
- go/linking: static
- go/tags: none
```

#### Which OS you are using and how many bits (e.g. Windows 7, 64 bit)

CentOS Linux release 7.9.2009 , 64 bits

#### Which cloud storage system are you using? (e.g. Google Drive)

SMB Share

#### The command you were trying to run (e.g. `rclone copy /tmp remote:tmp`)

```shell
# rclone --low-level-retries 1 ls myNAS-SMB:
2024/04/24 15:45:13 ERROR : : error listing: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
2024/04/24 15:45:13 Failed to ls with 2 errors: last error was: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
```

#### A log from the command with the `-vv` flag (e.g. output from `rclone -vv copy /tmp remote:tmp`)

```shell
# rclone --low-level-retries 1 -vv ls myNAS-SMB:
2024/04/24 15:42:10 DEBUG : rclone: Version "v1.61.1" starting with parameters ["rclone" "--low-level-retries" "1" "-vv" "ls" "myNAS-SMB:"]
2024/04/24 15:42:10 DEBUG : Creating backend with remote "myNAS-SMB:"
2024/04/24 15:42:10 DEBUG : Using config file from "/root/.config/rclone/rclone.conf"
2024/04/24 15:42:10 DEBUG : pacer: low level retry 1/1 (error couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.)
2024/04/24 15:42:10 DEBUG : pacer: Rate limited, increasing sleep to 200ms
2024/04/24 15:42:10 ERROR : : error listing: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
2024/04/24 15:42:10 DEBUG : 4 go routines active
2024/04/24 15:42:10 Failed to ls with 2 errors: last error was: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
```

<!--- Please keep the note below for others who read your bug report. -->

#### How to use GitHub

* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
* Subscribe to receive notifications on status change and new comments.



Discussion / hints:
You have a really old version.

You'd want to update and re-test.
@Animosity022 Same behaviour with `rclone` v1.66.0 on another system : 
```shell
 $ rclone version
rclone v1.66.0
- os/version: ubuntu 22.04 (64 bit)
- os/kernel: 5.15.0-58-generic (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.22.1
- go/linking: static
- go/tags: none
$ rclone -vv --low-level-retries 1 ls myNAS-SMB:
2024/04/25 12:05:16 DEBUG : rclone: Version "v1.66.0" starting with parameters ["rclone" "-vv" "--low-level-retries" "1" "ls" "myNAS-SMB:"]
2024/04/25 12:05:16 DEBUG : Creating backend with remote "myNAS-SMB:"
2024/04/25 12:05:16 DEBUG : Using config file from "/home/administrateur/.config/rclone/rclone.conf"
2024/04/25 12:05:17 DEBUG : pacer: low level retry 1/1 (error couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.)
2024/04/25 12:05:17 DEBUG : pacer: Rate limited, increasing sleep to 200ms
2024/04/25 12:05:17 ERROR : : error listing: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
2024/04/25 12:05:17 DEBUG : 6 go routines active
2024/04/25 12:05:17 Failed to ls with 2 errors: last error was: couldn't connect SMB: response error: The attempted logon is invalid. This is either due to a bad username or authentication information.
$
```

I can reproduce on v1.68.2.

Fixed in the latest release of https://github.com/CloudSoda/go-smb2


Thanks to @frafra for updating go-smb2 - this should be fixed now in 1c74b994bbf612c66ec2e49d273ad981ad85da64

I've merged this to master now which means it will be in [the latest beta](https://beta.rclone.org/) in 15-30 minutes and released in v1.69 
@ncw So I can conclude `rclone` is using `go-smb2` for smb access, right ?

`rclone version` do not show the version of `go-smb2` that `rclone` is using, how can I show what version of `go-smb2` that `rclone` is using ?
>  So I can conclude `rclone` is using `go-smb2` for smb access, right ?

Yes

> `rclone version` do not show the version of `go-smb2` that `rclone` is using, how can I show what version of `go-smb2` that `rclone` is using ?

Check the go.mod file is the easiest way


> > So I can conclude `rclone` is using `go-smb2` for smb access, right ?
> 
> Yes
> 
> > `rclone version` do not show the version of `go-smb2` that `rclone` is using, how can I show what version of `go-smb2` that `rclone` is using ?
> 
> Check the go.mod file is the easiest way

My last question was about the rclone I installed on my system, there is no `go.mod` on my system : 
```shell
$ rclone version
rclone v1.68.2
- os/version: ubuntu 22.04 (64 bit)
- os/kernel: 5.15.0-78-generic (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.23.3
- go/linking: static
- go/tags: none
$ dpkg -L rclone | grep go.mod
$
```
Hi there, I'm the author of the [go-smb2 kerberos PR](https://github.com/CloudSoda/go-smb2/pull/24). Updating to the latest version of the library is not enough, some changes need to be made to the `smb` backend. I have a PR almost ready for that – I just need to update the integration tests and then I'll submit it!
Thanks @jgiannuzzi I didn't realise that. I'll reopen this issue then

@sebma you find the libraries a go binary uses with the go tools (you'll need to install go)

    go version -m rclone 

Maybe I should get rclone to do that itself...
> `go version -m rclone `

@ncw Hi, this command does not work on my system, it says : 
```shell
$ go version -m rclone
stat rclone: no such file or directory
$
```

Fix patch:
diff --git a/backend/smb/connpool.go b/backend/smb/connpool.go
index 13a8c30420ee3..2c3fb2f8099f2 100644
--- a/backend/smb/connpool.go
+++ b/backend/smb/connpool.go
@@ -31,13 +31,29 @@ func (f *Fs) dial(ctx context.Context, network, addr string) (*conn, error) {
 		}
 	}
 
-	d := &smb2.Dialer{
-		Initiator: &smb2.NTLMInitiator{
+	d := &smb2.Dialer{}
+	if f.opt.UseKerberos {
+		cl, err := getKerberosClient()
+		if err != nil {
+			return nil, err
+		}
+
+		spn := f.opt.SPN
+		if spn == "" {
+			spn = "cifs/" + f.opt.Host
+		}
+
+		d.Initiator = &smb2.Krb5Initiator{
+			Client:    cl,
+			TargetSPN: spn,
+		}
+	} else {
+		d.Initiator = &smb2.NTLMInitiator{
 			User:      f.opt.User,
 			Password:  pass,
 			Domain:    f.opt.Domain,
 			TargetSPN: f.opt.SPN,
-		},
+		}
 	}
 
 	session, err := d.DialConn(ctx, tconn, addr)
diff --git a/backend/smb/kerberos.go b/backend/smb/kerberos.go
new file mode 100644
index 0000000000000..a00608de75cba
--- /dev/null
+++ b/backend/smb/kerberos.go
@@ -0,0 +1,78 @@
+package smb
+
+import (
+	"fmt"
+	"os"
+	"os/user"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/jcmturner/gokrb5/v8/client"
+	"github.com/jcmturner/gokrb5/v8/config"
+	"github.com/jcmturner/gokrb5/v8/credentials"
+)
+
+var (
+	kerberosClient *client.Client
+	kerberosErr    error
+	kerberosOnce   sync.Once
+)
+
+// getKerberosClient returns a Kerberos client that can be used to authenticate.
+func getKerberosClient() (*client.Client, error) {
+	if kerberosClient == nil || kerberosErr == nil {
+		kerberosOnce.Do(func() {
+			kerberosClient, kerberosErr = createKerberosClient()
+		})
+	}
+
+	return kerberosClient, kerberosErr
+}
+
+// createKerberosClient creates a new Kerberos client.
+func createKerberosClient() (*client.Client, error) {
+	cfgPath := os.Getenv("KRB5_CONFIG")
+	if cfgPath == "" {
+		cfgPath = "/etc/krb5.conf"
+	}
+
+	cfg, err := config.Load(cfgPath)
+	if err != nil {
+		return nil, err
+	}
+
+	// Determine the ccache location from the environment, falling back to the
+	// default location.
+	ccachePath := os.Getenv("KRB5CCNAME")
+	switch {
+	case strings.Contains(ccachePath, ":"):
+		parts := strings.SplitN(ccachePath, ":", 2)
+		switch parts[0] {
+		case "FILE":
+			ccachePath = parts[1]
+		case "DIR":
+			primary, err := os.ReadFile(filepath.Join(parts[1], "primary"))
+			if err != nil {
+				return nil, err
+			}
+			ccachePath = filepath.Join(parts[1], strings.TrimSpace(string(primary)))
+		default:
+			return nil, fmt.Errorf("unsupported KRB5CCNAME: %s", ccachePath)
+		}
+	case ccachePath == "":
+		u, err := user.Current()
+		if err != nil {
+			return nil, err
+		}
+
+		ccachePath = "/tmp/krb5cc_" + u.Uid
+	}
+
+	ccache, err := credentials.LoadCCache(ccachePath)
+	if err != nil {
+		return nil, err
+	}
+
+	return client.NewFromCCache(ccache, cfg)
+}
diff --git a/backend/smb/smb.go b/backend/smb/smb.go
index ff8f4097690b4..0fb1df8b927a9 100644
--- a/backend/smb/smb.go
+++ b/backend/smb/smb.go
@@ -76,6 +76,16 @@ authentication, and it often needs to be set for clusters. For example:
 Leave blank if not sure.
 `,
 			Sensitive: true,
+		}, {
+			Name: "use_kerberos",
+			Help: `Use Kerberos authentication.
+
+If set, rclone will use Kerberos authentication instead of NTLM. This
+requires a valid Kerberos configuration and credentials cache to be
+available, either in the default locations or as specified by the
+KRB5_CONFIG and KRB5CCNAME environment variables.
+`,
+			Default: false,
 		}, {
 			Name:    "idle_timeout",
 			Default: fs.Duration(60 * time.Second),
@@ -126,6 +136,7 @@ type Options struct {
 	Pass            string      `config:"pass"`
 	Domain          string      `config:"domain"`
 	SPN             string      `config:"spn"`
+	UseKerberos     bool        `config:"use_kerberos"`
 	HideSpecial     bool        `config:"hide_special_share"`
 	CaseInsensitive bool        `config:"case_insensitive"`
 	IdleTimeout     fs.Duration `config:"idle_timeout"`


Test patch:
diff --git a/backend/smb/smb_test.go b/backend/smb/smb_test.go
index aa5a0e4196778..f22bdb7f9fea2 100644
--- a/backend/smb/smb_test.go
+++ b/backend/smb/smb_test.go
@@ -2,6 +2,7 @@
 package smb_test
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/rclone/rclone/backend/smb"
@@ -15,3 +16,13 @@ func TestIntegration(t *testing.T) {
 		NilObject:  (*smb.Object)(nil),
 	})
 }
+
+func TestIntegration2(t *testing.T) {
+	krb5Dir := t.TempDir()
+	t.Setenv("KRB5_CONFIG", filepath.Join(krb5Dir, "krb5.conf"))
+	t.Setenv("KRB5CCNAME", filepath.Join(krb5Dir, "ccache"))
+	fstests.Run(t, &fstests.Opt{
+		RemoteName: "TestSMBKerberos:rclone",
+		NilObject:  (*smb.Object)(nil),
+	})
+}
diff --git a/fstest/test_all/config.yaml b/fstest/test_all/config.yaml
index 624d875d5c5ce..6c5c85c31556c 100644
--- a/fstest/test_all/config.yaml
+++ b/fstest/test_all/config.yaml
@@ -484,6 +484,9 @@ backends:
  - backend:  "smb"
    remote:   "TestSMB:rclone"
    fastlist: false
+ - backend:  "smb"
+   remote:   "TestSMBKerberos:rclone"
+   fastlist: false
  - backend:  "storj"
    remote:   "TestStorj:"
    fastlist: true
diff --git a/fstest/testserver/init.d/PORTS.md b/fstest/testserver/init.d/PORTS.md
index 5d34fb612f95e..f599f6fd6128a 100644
--- a/fstest/testserver/init.d/PORTS.md
+++ b/fstest/testserver/init.d/PORTS.md
@@ -26,6 +26,8 @@ They should be bound to localhost so they are not accessible externally.
 | 28630 | TestSMB |
 | 28631 | TestFTPProftpd |
 | 28632 | TestSwiftAIOsegments |
+| 28633 | TestSMBKerberos |
+| 28634 | TestSMBKerberos |
 | 38081 | TestWebdavOwncloud |
 
 ## Non localhost tests
diff --git a/fstest/testserver/init.d/TestSMBKerberos b/fstest/testserver/init.d/TestSMBKerberos
new file mode 100755
index 0000000000000..3fa7e120c5c94
--- /dev/null
+++ b/fstest/testserver/init.d/TestSMBKerberos
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+
+set -e
+
+IMAGE=rclone/test-smb-kerberos
+NAME=smb-kerberos
+USER=rclone
+DOMAIN=RCLONE
+REALM=RCLONE.LOCAL
+SMB_PORT=28633
+KRB5_PORT=28634
+
+. $(dirname "$0")/docker.bash
+
+start() {
+    docker build -t ${IMAGE} - <<EOF
+FROM alpine:3.21
+RUN apk add --no-cache samba-dc
+RUN rm -rf /etc/samba/smb.conf /var/lib/samba \
+ && mkdir -p /var/lib/samba/private \
+ && samba-tool domain provision \
+    --use-rfc2307 \
+    --option acl_xattr:security_acl_name=user.NTACL \
+    --realm=$REALM \
+    --domain=$DOMAIN \
+    --server-role=dc \
+    --dns-backend=SAMBA_INTERNAL \
+    --host-name=localhost \
+ && samba-tool user add --random-password $USER \
+ && mkdir -m 777 /share /rclone \
+ && cat <<EOS >> /etc/samba/smb.conf
+[public]
+path = /share
+browseable = yes
+read only = yes
+guest ok = yes
+[rclone]
+path = /rclone
+browseable = yes
+read only = no
+guest ok = no
+valid users = rclone
+EOS
+CMD ["samba", "-i"]
+EOF
+ 
+    docker run --rm -d --name ${NAME} \
+        -p 127.0.0.1:${SMB_PORT}:445 \
+        -p 127.0.0.1:${SMB_PORT}:445/udp \
+        -p 127.0.0.1:${KRB5_PORT}:88 \
+        ${IMAGE}
+
+    # KRB5_CONFIG and KRB5CCNAME are set by the caller
+    cat > ${KRB5_CONFIG} <<EOF
+[libdefaults]
+    default_realm = ${REALM}
+[realms]
+${REALM} = {
+    kdc = localhost
+}
+EOF
+    docker cp ${KRB5_CONFIG} ${NAME}:/etc/krb5.conf
+    docker exec ${NAME} samba-tool user get-kerberos-ticket rclone --output-krb5-ccache=/tmp/ccache
+    docker cp ${NAME}:/tmp/ccache ${KRB5CCNAME}
+    sed -i -e "s/localhost/localhost:${KRB5_PORT}/" ${KRB5_CONFIG}
+    
+    echo type=smb
+    echo host=localhost
+    echo port=$SMB_PORT
+    echo use_kerberos=true
+    echo _connect=127.0.0.1:${SMB_PORT}
+}
+
+. $(dirname "$0")/run.bash