├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── config.go ├── config └── pam.d │ └── system-auth-beacon ├── go.mod ├── go.sum ├── main.go └── pam-beacon.c /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: muesli 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | go-version: [~1.11, ^1] 9 | os: [ubuntu-latest] 10 | runs-on: ${{ matrix.os }} 11 | env: 12 | GO111MODULE: "on" 13 | steps: 14 | - name: Install Dependencies 15 | run: | 16 | sudo apt -q update 17 | sudo apt install -q -y libpam-dev 18 | - name: Install Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: ${{ matrix.go-version }} 22 | 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | 26 | - name: Download Go modules 27 | run: go mod download 28 | 29 | - name: Build 30 | run: go build -v ./... 31 | 32 | - name: Test 33 | run: go test ./... 34 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | golangci: 8 | name: lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v2 14 | with: 15 | # Optional: golangci-lint command line arguments. 16 | args: --issues-exit-code=0 17 | # Optional: working directory, useful for monorepos 18 | # working-directory: somedir 19 | # Optional: show only new issues if it's a pull request. The default value is `false`. 20 | only-new-issues: true 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | pam-beacon 15 | pam_beacon.h 16 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: false 3 | 4 | issues: 5 | include: 6 | - EXC0001 7 | - EXC0005 8 | - EXC0011 9 | - EXC0012 10 | - EXC0013 11 | 12 | max-issues-per-linter: 0 13 | max-same-issues: 0 14 | 15 | linters: 16 | enable: 17 | - bodyclose 18 | - dupl 19 | - exportloopref 20 | - goconst 21 | - godot 22 | - godox 23 | - goimports 24 | - goprintffuncname 25 | - gosec 26 | - ifshort 27 | - misspell 28 | - prealloc 29 | - revive 30 | - rowserrcheck 31 | - sqlclosecheck 32 | - unconvert 33 | - unparam 34 | - whitespace 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christian Muehlhaeuser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := 0.2.0 2 | COMMIT_SHA := $(shell git rev-parse --short HEAD) 3 | 4 | all: build 5 | 6 | build: 7 | go build -buildmode=c-shared -o pam_beacon.so 8 | 9 | install: 10 | cp pam_beacon.so /usr/lib/security/ 11 | 12 | .PHONY: deps build install test fmt 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pam-beacon 2 | 3 | [![Latest Release](https://img.shields.io/github/release/muesli/pam-beacon.svg)](https://github.com/muesli/pam-beacon/releases) 4 | [![Build Status](https://github.com/muesli/pam-beacon/workflows/build/badge.svg)](https://github.com/muesli/pam-beacon/actions) 5 | [![Go ReportCard](https://goreportcard.com/badge/muesli/pam-beacon)](https://goreportcard.com/report/muesli/pam-beacon) 6 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/muesli/pam-beacon) 7 | 8 | PAM module for (multi-factor) authentication with Bluetooth Devices & Beacons 9 | 10 | ## Installation 11 | 12 | ### Packages 13 | 14 | ArchLinux: install the AUR package `pam_beacon-git`. 15 | 16 | ### Manually 17 | 18 | Make sure you have a working Go environment (Go 1.11 or higher is required). 19 | See the [install instructions](http://golang.org/doc/install.html). 20 | `libpam` and its development headers are also required. 21 | 22 | ``` 23 | $ make 24 | $ sudo make install 25 | ``` 26 | 27 | ## Configuration 28 | 29 | Create a file named `.authorized_beacons` in your home directory. You can put 30 | the MAC addresses of as many beacons in it as you like, one per line. The auth 31 | will succeed as long as `pam-beacon` can find at least one of the beacons. 32 | 33 | Example: 34 | 35 | ``` 36 | 00:11:22:AA:BB:CC 37 | FF:EE:DD:99:88:77 38 | ``` 39 | 40 | Copy `config/pam.d/system-auth-beacon` to `/etc/pam.d`. Include this PAM module 41 | wherever you want to require it for authentication, e.g. in 42 | `/etc/pam.d/system-login`: 43 | 44 | ``` 45 | auth include system-auth-beacon 46 | ``` 47 | 48 | Careful: if your bluetooth beacon isn't discoverable, you will lock yourself out 49 | of your system! It's probably a good idea to keep a root-shell open during 50 | installation & testing of `pam-beacon`. 51 | 52 | ## Testing 53 | 54 | You can run pam-beacon as an application to test its behavior: 55 | 56 | ``` 57 | $ go build 58 | $ ./pam-beacon [mac] 59 | ``` 60 | 61 | This will return successfully if pam-beacon managed to find & connect to your 62 | bluetooth beacon, or return an error if it encounters any problems. 63 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "os/user" 7 | "path/filepath" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | const configFile = ".authorized_beacons" 13 | 14 | func homeDir(username string) (string, error) { 15 | u, err := user.Lookup(username) 16 | if err != nil { 17 | return "", err 18 | } 19 | 20 | return u.HomeDir, nil 21 | } 22 | 23 | func readAddresses(f string) ([]string, error) { 24 | log.Debugf("Parsing MAC addresses from %s", f) 25 | 26 | var ss []string 27 | file, err := os.Open(f) 28 | if err != nil { 29 | return ss, err 30 | } 31 | defer file.Close() 32 | 33 | scanner := bufio.NewScanner(file) 34 | for scanner.Scan() { 35 | ss = append(ss, scanner.Text()) 36 | } 37 | if err := scanner.Err(); err != nil { 38 | return ss, err 39 | } 40 | 41 | return ss, nil 42 | } 43 | 44 | func readUserConfig(username string) ([]string, error) { 45 | hd, err := homeDir(username) 46 | if err != nil { 47 | return []string{}, err 48 | } 49 | 50 | return readAddresses(filepath.Join(hd, configFile)) 51 | } 52 | -------------------------------------------------------------------------------- /config/pam.d/system-auth-beacon: -------------------------------------------------------------------------------- 1 | auth required pam_beacon.so 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/muesli/pam-beacon 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/muesli/go-pam v0.0.0-20180726201810-662d89573438 7 | github.com/muka/go-bluetooth v0.0.0-20210508070623-03c23c62f181 8 | github.com/sirupsen/logrus v1.6.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 5 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 6 | github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= 7 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 8 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 10 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/muesli/go-pam v0.0.0-20180726201810-662d89573438 h1:uTaGLPke0jADq0SfuHPoAFp4AmFZGQKATNW0xfowKUg= 15 | github.com/muesli/go-pam v0.0.0-20180726201810-662d89573438/go.mod h1:bUUccL+msvwFKdkxE4i87E/+rIYszyhqVZF/s86a6+8= 16 | github.com/muka/go-bluetooth v0.0.0-20210508070623-03c23c62f181 h1:2WJZHTfZO2VOzRuVeEXI8Vjy+UAIvugGa+bVhni576I= 17 | github.com/muka/go-bluetooth v0.0.0-20210508070623-03c23c62f181/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= 18 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 19 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 20 | github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= 21 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 25 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 26 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 27 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 28 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 29 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 30 | github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= 31 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 33 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 34 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 35 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 36 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 37 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 38 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE= 46 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 49 | golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 50 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 51 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 55 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 57 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 58 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #cgo LDFLAGS: -lpam 4 | // #include 5 | // #include 6 | import "C" 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "os/user" 12 | "strings" 13 | "time" 14 | "unsafe" 15 | 16 | "github.com/muesli/go-pam" 17 | "github.com/muka/go-bluetooth/api" 18 | "github.com/muka/go-bluetooth/bluez/profile/adapter" 19 | "github.com/muka/go-bluetooth/bluez/profile/device" 20 | log "github.com/sirupsen/logrus" 21 | ) 22 | 23 | var ( 24 | logLevel = log.InfoLevel 25 | beaconCh = make(chan (string)) 26 | ) 27 | 28 | const ( 29 | adapterID = "hci0" 30 | timeout = 30 * time.Second 31 | ) 32 | 33 | func logError(args ...interface{}) { 34 | log.Error(args...) 35 | time.Sleep(time.Second) 36 | } 37 | 38 | func logErrorf(s string, args ...interface{}) { 39 | log.Errorf(s, args...) 40 | time.Sleep(time.Second) 41 | } 42 | 43 | func closeBluetooth() { 44 | if err := api.Exit(); err != nil { 45 | logError(err) 46 | } 47 | } 48 | 49 | func findDevice(addresses []string) bool { 50 | log.Debugf("Looking for beacons %s...", addresses) 51 | 52 | a, err := adapter.GetAdapter(adapterID) 53 | if err != nil { 54 | logError(err) 55 | return false 56 | } 57 | defer closeBluetooth() 58 | 59 | if p, err := a.GetPowered(); err != nil { 60 | logError(err) 61 | return false 62 | } else if !p { 63 | if err := a.SetPowered(true); err != nil { 64 | logError(err) 65 | return false 66 | } 67 | } 68 | 69 | if monitorCachedDevices(a, addresses) { 70 | return true 71 | } 72 | 73 | cancel, err := discoverDevices(a, addresses) 74 | if err != nil { 75 | logError(err) 76 | return false 77 | } 78 | defer func() { 79 | log.Debug("Canceling...") 80 | cancel() 81 | }() 82 | 83 | select { 84 | case <-time.After(timeout): 85 | return false 86 | case <-beaconCh: 87 | return true 88 | } 89 | } 90 | 91 | func discoverDevices(a *adapter.Adapter1, addresses []string) (func(), error) { 92 | if err := a.FlushDevices(); err != nil { 93 | logError(err) 94 | return nil, err 95 | } 96 | 97 | discovery, cancel, err := api.Discover(a, nil) 98 | if err != nil { 99 | logError(err) 100 | return nil, err 101 | } 102 | 103 | log.Debugf("Discovered devices:") 104 | go func() { 105 | for ev := range discovery { 106 | go func(ev *adapter.DeviceDiscovered) { 107 | dev, err := device.NewDevice1(ev.Path) 108 | if err != nil { 109 | log.Errorf("%s: %s", ev.Path, err) 110 | return 111 | } 112 | if dev == nil { 113 | log.Errorf("%s: not found", ev.Path) 114 | return 115 | } 116 | 117 | if addr, ok := checkDevice(dev, addresses); ok { 118 | go func(addr string) { 119 | // we sent this in a go-routine to prevent this function 120 | // from dead-locking a bluetooth shutdown 121 | beaconCh <- addr 122 | }(addr) 123 | } 124 | }(ev) 125 | } 126 | }() 127 | 128 | return cancel, nil 129 | } 130 | 131 | func checkDevice(dev *device.Device1, addresses []string) (string, bool) { 132 | props, err := dev.GetProperties() 133 | if err != nil { 134 | logErrorf("%s: Failed to get properties: %s", dev.Path(), err.Error()) 135 | return "", false 136 | } 137 | log.Debugf("name=%s addr=%s rssi=%d trusted=%t tx=%d connected=%t", 138 | props.Name, props.Address, props.RSSI, props.Trusted, props.TxPower, props.Connected) 139 | 140 | var addr string 141 | for _, v := range addresses { 142 | if strings.EqualFold(props.Address, v) { 143 | addr = props.Address 144 | break 145 | } 146 | } 147 | if addr == "" { 148 | return "", false 149 | } 150 | 151 | if !props.Connected { 152 | // check we can actually connect to the device 153 | log.Debugf("Connecting to %s...", props.Address) 154 | err = dev.Connect() 155 | if err != nil { 156 | logErrorf("%s: Failed to connect: %s", dev.Path(), err.Error()) 157 | return "", false 158 | } 159 | 160 | log.Debugf("Disconnecting from %s...", props.Address) 161 | err = dev.Disconnect() 162 | if err != nil { 163 | logErrorf("%s: Failed to disconnect: %s", dev.Path(), err.Error()) 164 | } 165 | } 166 | 167 | log.Printf("Beacon %s found!", props.Address) 168 | return props.Address, true 169 | } 170 | 171 | func monitorCachedDevices(api *adapter.Adapter1, addresses []string) bool { 172 | devices, err := api.GetDevices() 173 | if err != nil { 174 | logError(err) 175 | return false 176 | } 177 | 178 | log.Debugf("Cached devices:") 179 | for _, dev := range devices { 180 | if _, ok := checkDevice(dev, addresses); ok { 181 | return true 182 | } 183 | } 184 | 185 | return false 186 | } 187 | 188 | //export goAuthenticate 189 | func goAuthenticate(handle *C.pam_handle_t, flags C.int, argv []string) C.int { 190 | for _, arg := range argv { 191 | if strings.ToLower(arg) == "debug" { 192 | logLevel = log.DebugLevel 193 | } 194 | } 195 | log.SetLevel(logLevel) 196 | log.Debugf("argv: %+v", argv) 197 | 198 | hdl := pam.Handle{Ptr: unsafe.Pointer(handle)} 199 | username, err := hdl.GetUser() 200 | if err != nil { 201 | return C.PAM_AUTH_ERR 202 | } 203 | addrs, err := readUserConfig(username) 204 | if err != nil { 205 | logError(err) 206 | switch err.(type) { 207 | case user.UnknownUserError: 208 | return C.PAM_USER_UNKNOWN 209 | default: 210 | return C.PAM_AUTHINFO_UNAVAIL 211 | } 212 | } 213 | 214 | if findDevice(addrs) { 215 | return C.PAM_SUCCESS 216 | } 217 | return C.PAM_AUTH_ERR 218 | } 219 | 220 | //export setCred 221 | func setCred(handle *C.pam_handle_t, flags C.int, argv []string) C.int { 222 | return C.PAM_SUCCESS 223 | } 224 | 225 | // main is for testing purposes only, the PAM module has to be built with: 226 | // go build -buildmode=c-shared 227 | func main() { 228 | logLevel = log.DebugLevel 229 | log.SetLevel(logLevel) 230 | 231 | if len(os.Args) < 2 { 232 | fmt.Println("usage: pam-beacon ") 233 | os.Exit(2) 234 | } 235 | if !findDevice(os.Args[1:]) { 236 | os.Exit(1) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /pam-beacon.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | #include "stdlib.h" 3 | 4 | #include "_cgo_export.h" 5 | 6 | #define PAM_SM_AUTH 7 | #define PAM_SM_PASSWORD 8 | #include 9 | #include 10 | 11 | GoSlice argcvToSlice(int, const char**); 12 | 13 | PAM_EXTERN int pam_sm_authenticate(pam_handle_t* pamh, int flags, int argc, const char** argv) { 14 | return goAuthenticate(pamh, flags, argcvToSlice(argc, argv)); 15 | } 16 | 17 | PAM_EXTERN int pam_sm_setcred(pam_handle_t* pamh, int flags, int argc, const char** argv) { 18 | return setCred(pamh, flags, argcvToSlice(argc, argv)); 19 | } 20 | 21 | GoSlice argcvToSlice(int argc, const char** argv) { 22 | GoString* strs = malloc(sizeof(GoString) * argc); 23 | 24 | GoSlice ret; 25 | ret.cap = argc; 26 | ret.len = argc; 27 | ret.data = (void*)strs; 28 | 29 | int i; 30 | for(i = 0; i < argc; i++) { 31 | strs[i] = *((GoString*)malloc(sizeof(GoString))); 32 | 33 | strs[i].p = (char*)argv[i]; 34 | strs[i].n = strlen(argv[i]); 35 | } 36 | 37 | return ret; 38 | } 39 | --------------------------------------------------------------------------------