Backend server for SDL
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

645 lines
16 KiB

/*
This file implements functionality very similar to that of the xauth module in czmq.
Notable differences in here:
- domains are supported
- domains are used in AuthAllow and AuthDeny too
- usernames/passwords are read from memory, not from file
- public keys are read from memory, not from file
- an address can be a single IP address, or an IP address and mask in CIDR notation
- additional functions for configuring server or client socket with a single command
*/
package zmq4
/*
#include <zmq.h>
#include <stdlib.h>
#if ZMQ_VERSION_MINOR < 2
// Version < 4.2.x
int zmq_curve_public (char *z85_public_key, const char *z85_secret_key) { return 0; }
#endif // Version < 4.2.x
*/
import "C"
import (
"errors"
"log"
"net"
"strings"
"unsafe"
)
const CURVE_ALLOW_ANY = "*"
var (
auth_handler *Socket
auth_quit *Socket
auth_init = false
auth_verbose = false
auth_allow = make(map[string]map[string]bool)
auth_deny = make(map[string]map[string]bool)
auth_allow_net = make(map[string][]*net.IPNet)
auth_deny_net = make(map[string][]*net.IPNet)
auth_users = make(map[string]map[string]string)
auth_pubkeys = make(map[string]map[string]bool)
auth_meta_handler = auth_meta_handler_default
)
func auth_meta_handler_default(version, request_id, domain, address, identity, mechanism string, credentials ...string) (metadata map[string]string) {
return map[string]string{}
}
func auth_isIP(addr string) bool {
if net.ParseIP(addr) != nil {
return true
}
if _, _, err := net.ParseCIDR(addr); err == nil {
return true
}
return false
}
func auth_is_allowed(domain, address string) bool {
for _, d := range []string{domain, "*"} {
if a, ok := auth_allow[d]; ok {
if a[address] {
return true
}
}
}
addr := net.ParseIP(address)
if addr != nil {
for _, d := range []string{domain, "*"} {
if a, ok := auth_allow_net[d]; ok {
for _, m := range a {
if m.Contains(addr) {
return true
}
}
}
}
}
return false
}
func auth_is_denied(domain, address string) bool {
for _, d := range []string{domain, "*"} {
if a, ok := auth_deny[d]; ok {
if a[address] {
return true
}
}
}
addr := net.ParseIP(address)
if addr != nil {
for _, d := range []string{domain, "*"} {
if a, ok := auth_deny_net[d]; ok {
for _, m := range a {
if m.Contains(addr) {
return true
}
}
}
}
}
return false
}
func auth_has_allow(domain string) bool {
for _, d := range []string{domain, "*"} {
if a, ok := auth_allow[d]; ok {
if len(a) > 0 || len(auth_allow_net[d]) > 0 {
return true
}
}
}
return false
}
func auth_has_deny(domain string) bool {
for _, d := range []string{domain, "*"} {
if a, ok := auth_deny[d]; ok {
if len(a) > 0 || len(auth_deny_net[d]) > 0 {
return true
}
}
}
return false
}
func auth_do_handler() {
for {
msg, err := auth_handler.RecvMessage(0)
if err != nil {
if auth_verbose {
log.Println("AUTH: Quitting:", err)
}
break
}
if msg[0] == "QUIT" {
if auth_verbose {
log.Println("AUTH: Quitting: received QUIT message")
}
_, err := auth_handler.SendMessage("QUIT")
if err != nil && auth_verbose {
log.Println("AUTH: Quitting: bouncing QUIT message:", err)
}
break
}
version := msg[0]
if version != "1.0" {
panic("AUTH: version != 1.0")
}
request_id := msg[1]
domain := msg[2]
address := msg[3]
identity := msg[4]
mechanism := msg[5]
credentials := msg[6:]
username := ""
password := ""
client_key := ""
if mechanism == "PLAIN" {
username = msg[6]
password = msg[7]
} else if mechanism == "CURVE" {
s := msg[6]
if len(s) != 32 {
panic("AUTH: len(client_key) != 32")
}
client_key = Z85encode(s)
}
allowed := false
denied := false
if auth_has_allow(domain) {
if auth_is_allowed(domain, address) {
allowed = true
if auth_verbose {
log.Printf("AUTH: PASSED (whitelist) domain=%q address=%q\n", domain, address)
}
} else {
denied = true
if auth_verbose {
log.Printf("AUTH: DENIED (not in whitelist) domain=%q address=%q\n", domain, address)
}
}
} else if auth_has_deny(domain) {
if auth_is_denied(domain, address) {
denied = true
if auth_verbose {
log.Printf("AUTH: DENIED (blacklist) domain=%q address=%q\n", domain, address)
}
} else {
allowed = true
if auth_verbose {
log.Printf("AUTH: PASSED (not in blacklist) domain=%q address=%q\n", domain, address)
}
}
}
// Mechanism-specific checks
if !denied {
if mechanism == "NULL" && !allowed {
// For NULL, we allow if the address wasn't blacklisted
if auth_verbose {
log.Printf("AUTH: ALLOWED (NULL)\n")
}
allowed = true
} else if mechanism == "PLAIN" {
// For PLAIN, even a whitelisted address must authenticate
allowed = authenticate_plain(domain, username, password)
} else if mechanism == "CURVE" {
// For CURVE, even a whitelisted address must authenticate
allowed = authenticate_curve(domain, client_key)
}
}
if allowed {
m := auth_meta_handler(version, request_id, domain, address, identity, mechanism, credentials...)
user_id := ""
if uid, ok := m["User-Id"]; ok {
user_id = uid
delete(m, "User-Id")
}
metadata := make([]byte, 0)
for key, value := range m {
if len(key) < 256 {
metadata = append(metadata, auth_meta_blob(key, value)...)
}
}
auth_handler.SendMessage(version, request_id, "200", "OK", user_id, metadata)
} else {
auth_handler.SendMessage(version, request_id, "400", "NO ACCESS", "", "")
}
}
err := auth_handler.Close()
if err != nil && auth_verbose {
log.Println("AUTH: Quitting: Close:", err)
}
if auth_verbose {
log.Println("AUTH: Quit")
}
}
func authenticate_plain(domain, username, password string) bool {
for _, dom := range []string{domain, "*"} {
if m, ok := auth_users[dom]; ok {
if m[username] == password {
if auth_verbose {
log.Printf("AUTH: ALLOWED (PLAIN) domain=%q username=%q password=%q\n", dom, username, password)
}
return true
}
}
}
if auth_verbose {
log.Printf("AUTH: DENIED (PLAIN) domain=%q username=%q password=%q\n", domain, username, password)
}
return false
}
func authenticate_curve(domain, client_key string) bool {
for _, dom := range []string{domain, "*"} {
if m, ok := auth_pubkeys[dom]; ok {
if m[CURVE_ALLOW_ANY] {
if auth_verbose {
log.Printf("AUTH: ALLOWED (CURVE any client) domain=%q\n", dom)
}
return true
}
if m[client_key] {
if auth_verbose {
log.Printf("AUTH: ALLOWED (CURVE) domain=%q client_key=%q\n", dom, client_key)
}
return true
}
}
}
if auth_verbose {
log.Printf("AUTH: DENIED (CURVE) domain=%q client_key=%q\n", domain, client_key)
}
return false
}
// Start authentication.
//
// Note that until you add policies, all incoming NULL connections are allowed
// (classic ZeroMQ behaviour), and all PLAIN and CURVE connections are denied.
func AuthStart() (err error) {
if auth_init {
if auth_verbose {
log.Println("AUTH: Already running")
}
return errors.New("Auth is already running")
}
auth_handler, err = NewSocket(REP)
if err != nil {
return
}
auth_handler.SetLinger(0)
err = auth_handler.Bind("inproc://zeromq.zap.01")
if err != nil {
auth_handler.Close()
return
}
auth_quit, err = NewSocket(REQ)
if err != nil {
auth_handler.Close()
return
}
auth_quit.SetLinger(0)
err = auth_quit.Connect("inproc://zeromq.zap.01")
if err != nil {
auth_handler.Close()
auth_quit.Close()
return
}
go auth_do_handler()
if auth_verbose {
log.Println("AUTH: Starting")
}
auth_init = true
return
}
// Stop authentication.
func AuthStop() {
if !auth_init {
if auth_verbose {
log.Println("AUTH: Not running, can't stop")
}
return
}
if auth_verbose {
log.Println("AUTH: Stopping")
}
_, err := auth_quit.SendMessageDontwait("QUIT")
if err != nil && auth_verbose {
log.Println("AUTH: Stopping: SendMessageDontwait(\"QUIT\"):", err)
}
_, err = auth_quit.RecvMessage(0)
if err != nil && auth_verbose {
log.Println("AUTH: Stopping: RecvMessage:", err)
}
err = auth_quit.Close()
if err != nil && auth_verbose {
log.Println("AUTH: Stopping: Close:", err)
}
if auth_verbose {
log.Println("AUTH: Stopped")
}
auth_init = false
}
// Allow (whitelist) some addresses for a domain.
//
// An address can be a single IP address, or an IP address and mask in CIDR notation.
//
// For NULL, all clients from these addresses will be accepted.
//
// For PLAIN and CURVE, they will be allowed to continue with authentication.
//
// You can call this method multiple times to whitelist multiple IP addresses.
//
// If you whitelist a single address for a domain, any non-whitelisted addresses
// for that domain are treated as blacklisted.
//
// Use domain "*" for all domains.
//
// For backward compatibility: if domain can be parsed as an IP address, it will be
// interpreted as another address, and it and all remaining addresses will be added
// to all domains.
func AuthAllow(domain string, addresses ...string) {
if auth_isIP(domain) {
auth_allow_for_domain("*", domain)
auth_allow_for_domain("*", addresses...)
} else {
auth_allow_for_domain(domain, addresses...)
}
}
func auth_allow_for_domain(domain string, addresses ...string) {
if _, ok := auth_allow[domain]; !ok {
auth_allow[domain] = make(map[string]bool)
auth_allow_net[domain] = make([]*net.IPNet, 0)
}
for _, address := range addresses {
if _, ipnet, err := net.ParseCIDR(address); err == nil {
auth_allow_net[domain] = append(auth_allow_net[domain], ipnet)
} else if net.ParseIP(address) != nil {
auth_allow[domain][address] = true
} else {
if auth_verbose {
log.Printf("AUTH: Allow for domain %q: %q is not a valid address or network\n", domain, address)
}
}
}
}
// Deny (blacklist) some addresses for a domain.
//
// An address can be a single IP address, or an IP address and mask in CIDR notation.
//
// For all security mechanisms, this rejects the connection without any further authentication.
//
// Use either a whitelist for a domain, or a blacklist for a domain, not both.
// If you define both a whitelist and a blacklist for a domain, only the whitelist takes effect.
//
// Use domain "*" for all domains.
//
// For backward compatibility: if domain can be parsed as an IP address, it will be
// interpreted as another address, and it and all remaining addresses will be added
// to all domains.
func AuthDeny(domain string, addresses ...string) {
if auth_isIP(domain) {
auth_deny_for_domain("*", domain)
auth_deny_for_domain("*", addresses...)
} else {
auth_deny_for_domain(domain, addresses...)
}
}
func auth_deny_for_domain(domain string, addresses ...string) {
if _, ok := auth_deny[domain]; !ok {
auth_deny[domain] = make(map[string]bool)
auth_deny_net[domain] = make([]*net.IPNet, 0)
}
for _, address := range addresses {
if _, ipnet, err := net.ParseCIDR(address); err == nil {
auth_deny_net[domain] = append(auth_deny_net[domain], ipnet)
} else if net.ParseIP(address) != nil {
auth_deny[domain][address] = true
} else {
if auth_verbose {
log.Printf("AUTH: Deny for domain %q: %q is not a valid address or network\n", domain, address)
}
}
}
}
// Add a user for PLAIN authentication for a given domain.
//
// Set `domain` to "*" to apply to all domains.
func AuthPlainAdd(domain, username, password string) {
if _, ok := auth_users[domain]; !ok {
auth_users[domain] = make(map[string]string)
}
auth_users[domain][username] = password
}
// Remove users from PLAIN authentication for a given domain.
func AuthPlainRemove(domain string, usernames ...string) {
if u, ok := auth_users[domain]; ok {
for _, username := range usernames {
delete(u, username)
}
}
}
// Remove all users from PLAIN authentication for a given domain.
func AuthPlainRemoveAll(domain string) {
delete(auth_users, domain)
}
// Add public user keys for CURVE authentication for a given domain.
//
// To cover all domains, use "*".
//
// Public keys are in Z85 printable text format.
//
// To allow all client keys without checking, specify CURVE_ALLOW_ANY for the key.
func AuthCurveAdd(domain string, pubkeys ...string) {
if _, ok := auth_pubkeys[domain]; !ok {
auth_pubkeys[domain] = make(map[string]bool)
}
for _, key := range pubkeys {
auth_pubkeys[domain][key] = true
}
}
// Remove user keys from CURVE authentication for a given domain.
func AuthCurveRemove(domain string, pubkeys ...string) {
if p, ok := auth_pubkeys[domain]; ok {
for _, pubkey := range pubkeys {
delete(p, pubkey)
}
}
}
// Remove all user keys from CURVE authentication for a given domain.
func AuthCurveRemoveAll(domain string) {
delete(auth_pubkeys, domain)
}
// Enable verbose tracing of commands and activity.
func AuthSetVerbose(verbose bool) {
auth_verbose = verbose
}
/*
This function sets the metadata handler that is called by the ZAP
handler to retrieve key/value properties that should be set on reply
messages in case of a status code "200" (succes).
Default properties are `Socket-Type`, which is already set, and
`Identity` and `User-Id` that are empty by default. The last two can be
set, and more properties can be added.
The `User-Id` property is used for the `user id` frame of the reply
message. All other properties are stored in the `metadata` frame of the
reply message.
The default handler returns an empty map.
For the meaning of the handler arguments, and other details, see:
http://rfc.zeromq.org/spec:27#toc10
*/
func AuthSetMetadataHandler(
handler func(
version, request_id, domain, address, identity, mechanism string, credentials ...string) (metadata map[string]string)) {
auth_meta_handler = handler
}
/*
This encodes a key/value pair into the format used by a ZAP handler.
Returns an error if key is more then 255 characters long.
*/
func AuthMetaBlob(key, value string) (blob []byte, err error) {
if len(key) > 255 {
return []byte{}, errors.New("Key too long")
}
return auth_meta_blob(key, value), nil
}
func auth_meta_blob(name, value string) []byte {
l1 := len(name)
l2 := len(value)
b := make([]byte, l1+l2+5)
b[0] = byte(l1)
b[l1+1] = byte(l2 >> 24 & 255)
b[l1+2] = byte(l2 >> 16 & 255)
b[l1+3] = byte(l2 >> 8 & 255)
b[l1+4] = byte(l2 & 255)
copy(b[1:], []byte(name))
copy(b[5+l1:], []byte(value))
return b
}
//. Additional functions for configuring server or client socket with a single command
// Set NULL server role.
func (server *Socket) ServerAuthNull(domain string) error {
err := server.SetPlainServer(0)
if err == nil {
err = server.SetZapDomain(domain)
}
return err
}
// Set PLAIN server role.
func (server *Socket) ServerAuthPlain(domain string) error {
err := server.SetPlainServer(1)
if err == nil {
err = server.SetZapDomain(domain)
}
return err
}
// Set CURVE server role.
func (server *Socket) ServerAuthCurve(domain, secret_key string) error {
err := server.SetCurveServer(1)
if err == nil {
err = server.SetCurveSecretkey(secret_key)
}
if err == nil {
err = server.SetZapDomain(domain)
}
return err
}
// Set PLAIN client role.
func (client *Socket) ClientAuthPlain(username, password string) error {
err := client.SetPlainUsername(username)
if err == nil {
err = client.SetPlainPassword(password)
}
return err
}
// Set CURVE client role.
func (client *Socket) ClientAuthCurve(server_public_key, client_public_key, client_secret_key string) error {
err := client.SetCurveServerkey(server_public_key)
if err == nil {
err = client.SetCurvePublickey(client_public_key)
}
if err == nil {
client.SetCurveSecretkey(client_secret_key)
}
return err
}
// Helper function to derive z85 public key from secret key
//
// Returns ErrorNotImplemented42 with ZeroMQ version < 4.2
func AuthCurvePublic(z85SecretKey string) (z85PublicKey string, err error) {
if minor < 2 {
return "", ErrorNotImplemented42
}
secret := C.CString(z85SecretKey)
defer C.free(unsafe.Pointer(secret))
public := C.CString(strings.Repeat(" ", 41))
defer C.free(unsafe.Pointer(public))
if i, err := C.zmq_curve_public(public, secret); int(i) != 0 {
return "", errget(err)
}
z85PublicKey = C.GoString(public)
return z85PublicKey, nil
}