| // Copyright (C) MongoDB, Inc. 2017-present. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may |
| // not use this file except in compliance with the License. You may obtain |
| // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| |
| //+build gssapi |
| //+build linux darwin |
| |
| package gssapi |
| |
| /* |
| #cgo linux CFLAGS: -DGOOS_linux |
| #cgo linux LDFLAGS: -lgssapi_krb5 -lkrb5 |
| #cgo darwin CFLAGS: -DGOOS_darwin |
| #cgo darwin LDFLAGS: -framework GSS |
| #include "gss_wrapper.h" |
| */ |
| import "C" |
| import ( |
| "fmt" |
| "net" |
| "runtime" |
| "strings" |
| "unsafe" |
| ) |
| |
| // New creates a new SaslClient. |
| func New(target, username, password string, passwordSet bool, props map[string]string) (*SaslClient, error) { |
| serviceName := "mongodb" |
| |
| for key, value := range props { |
| switch strings.ToUpper(key) { |
| case "CANONICALIZE_HOST_NAME": |
| return nil, fmt.Errorf("CANONICALIZE_HOST_NAME is not supported when using gssapi on %s", runtime.GOOS) |
| case "SERVICE_REALM": |
| return nil, fmt.Errorf("SERVICE_REALM is not supported when using gssapi on %s", runtime.GOOS) |
| case "SERVICE_NAME": |
| serviceName = value |
| default: |
| return nil, fmt.Errorf("unknown mechanism property %s", key) |
| } |
| } |
| |
| hostname, _, err := net.SplitHostPort(target) |
| if err != nil { |
| return nil, fmt.Errorf("invalid endpoint (%s) specified: %s", target, err) |
| } |
| |
| servicePrincipalName := fmt.Sprintf("%s@%s", serviceName, hostname) |
| |
| return &SaslClient{ |
| servicePrincipalName: servicePrincipalName, |
| username: username, |
| password: password, |
| passwordSet: passwordSet, |
| }, nil |
| } |
| |
| type SaslClient struct { |
| servicePrincipalName string |
| username string |
| password string |
| passwordSet bool |
| |
| // state |
| state C.gssapi_client_state |
| contextComplete bool |
| done bool |
| } |
| |
| func (sc *SaslClient) Close() { |
| C.gssapi_client_destroy(&sc.state) |
| } |
| |
| func (sc *SaslClient) Start() (string, []byte, error) { |
| const mechName = "GSSAPI" |
| |
| cservicePrincipalName := C.CString(sc.servicePrincipalName) |
| defer C.free(unsafe.Pointer(cservicePrincipalName)) |
| var cusername *C.char |
| var cpassword *C.char |
| if sc.username != "" { |
| cusername = C.CString(sc.username) |
| defer C.free(unsafe.Pointer(cusername)) |
| if sc.passwordSet { |
| cpassword = C.CString(sc.password) |
| defer C.free(unsafe.Pointer(cpassword)) |
| } |
| } |
| status := C.gssapi_client_init(&sc.state, cservicePrincipalName, cusername, cpassword) |
| |
| if status != C.GSSAPI_OK { |
| return mechName, nil, sc.getError("unable to initialize client") |
| } |
| |
| return mechName, nil, nil |
| } |
| |
| func (sc *SaslClient) Next(challenge []byte) ([]byte, error) { |
| |
| var buf unsafe.Pointer |
| var bufLen C.size_t |
| var outBuf unsafe.Pointer |
| var outBufLen C.size_t |
| |
| if sc.contextComplete { |
| if sc.username == "" { |
| var cusername *C.char |
| status := C.gssapi_client_username(&sc.state, &cusername) |
| if status != C.GSSAPI_OK { |
| return nil, sc.getError("unable to acquire username") |
| } |
| defer C.free(unsafe.Pointer(cusername)) |
| sc.username = C.GoString((*C.char)(unsafe.Pointer(cusername))) |
| } |
| |
| bytes := append([]byte{1, 0, 0, 0}, []byte(sc.username)...) |
| buf = unsafe.Pointer(&bytes[0]) |
| bufLen = C.size_t(len(bytes)) |
| status := C.gssapi_client_wrap_msg(&sc.state, buf, bufLen, &outBuf, &outBufLen) |
| if status != C.GSSAPI_OK { |
| return nil, sc.getError("unable to wrap authz") |
| } |
| |
| sc.done = true |
| } else { |
| if len(challenge) > 0 { |
| buf = unsafe.Pointer(&challenge[0]) |
| bufLen = C.size_t(len(challenge)) |
| } |
| |
| status := C.gssapi_client_negotiate(&sc.state, buf, bufLen, &outBuf, &outBufLen) |
| switch status { |
| case C.GSSAPI_OK: |
| sc.contextComplete = true |
| case C.GSSAPI_CONTINUE: |
| default: |
| return nil, sc.getError("unable to negotiate with server") |
| } |
| } |
| |
| if outBuf != nil { |
| defer C.free(outBuf) |
| } |
| |
| return C.GoBytes(outBuf, C.int(outBufLen)), nil |
| } |
| |
| func (sc *SaslClient) Completed() bool { |
| return sc.done |
| } |
| |
| func (sc *SaslClient) getError(prefix string) error { |
| var desc *C.char |
| |
| status := C.gssapi_error_desc(sc.state.maj_stat, sc.state.min_stat, &desc) |
| if status != C.GSSAPI_OK { |
| if desc != nil { |
| C.free(unsafe.Pointer(desc)) |
| } |
| |
| return fmt.Errorf("%s: (%v, %v)", prefix, sc.state.maj_stat, sc.state.min_stat) |
| } |
| defer C.free(unsafe.Pointer(desc)) |
| |
| return fmt.Errorf("%s: %v(%v,%v)", prefix, C.GoString(desc), int32(sc.state.maj_stat), int32(sc.state.min_stat)) |
| } |