/*
* This file is part of Cockpit.
*
* Copyright (C) 2014 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
#include "config.h"
#include "cockpitauthorize.h"
#include "cockpitbase64.h"
#include "cockpitmemory.h"
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* ----------------------------------------------------------------------------
* Tools
*/
#ifndef debug
#define debug(format, ...) \
do { if (logger_verbose) \
message ("debug: " format, ##__VA_ARGS__); \
} while (0)
#endif
static int logger_verbose = 0;
static void (* logger) (const char *data);
#ifndef message
#if __GNUC__ > 2
static void
message (const char *format, ...)
__attribute__((__format__(__printf__, 1, 2)));
#endif
static void
message (const char *format, ...)
{
va_list va;
char *data;
int res;
if (!logger)
return;
/* Fast path for simple messages */
if (!strchr (format, '%'))
{
logger (format);
return;
}
va_start (va, format);
res = vasprintf (&data, format, va);
va_end (va);
if (res < 0)
{
logger ("out of memory printing message");
return;
}
logger (data);
free (data);
}
#endif
void
cockpit_authorize_logger (void (* func) (const char *data),
int verbose)
{
logger_verbose = verbose;
logger = func;
}
void *
cockpit_authorize_nonce (size_t length)
{
unsigned char *key;
int errn = 0;
int fd;
ssize_t read_bytes;
ssize_t read_result;
fd = open ("/dev/urandom", O_RDONLY, 0);
if (fd < 0)
return NULL;
key = malloc (length);
if (!key)
{
close (fd);
errno = ENOMEM;
return NULL;
}
read_bytes = 0;
do
{
errno = 0;
read_result = read (fd, key + read_bytes, length - read_bytes);
if (read_result <= 0)
{
if (errno == EAGAIN || errno == EINTR)
continue;
errn = errno;
break;
}
read_bytes += read_result;
}
while (read_bytes < length);
close (fd);
if (read_bytes < length)
{
free (key);
key = NULL;
errno = errn;
}
return key;
}
const char *
cockpit_authorize_type (const char *challenge,
char **type)
{
size_t i, len = 0;
/*
* Either a space or a colon is the delimiter
* that splits the type from the remainder
* of the content.
*/
if (challenge)
len = strcspn (challenge, ": ");
if (len == 0)
{
debug ("invalid \"authorize\" message");
errno = EINVAL;
return NULL;
}
if (type)
{
*type = strndup (challenge, len);
if (*type == NULL)
{
message ("couldn't allocate memory for \"authorize\" challenge");
errno = ENOMEM;
return NULL;
}
for (i = 0; i < len; i++)
(*type)[i] = tolower ((*type)[i]);
}
if (challenge[len])
len++;
while (challenge[len] == ' ')
len++;
return challenge + len;
}
const char *
cockpit_authorize_subject (const char *challenge,
char **subject)
{
size_t len;
challenge = cockpit_authorize_type (challenge, NULL);
if (!challenge)
return NULL;
len = strcspn (challenge, ": ");
if (len == 0)
{
message ("invalid \"authorize\" message \"challenge\": no subject");
errno = EINVAL;
return NULL;
}
if (subject)
{
*subject = strndup (challenge, len);
if (!*subject)
{
message ("couldn't allocate memory for \"authorize\" message \"challenge\"");
errno = ENOMEM;
return NULL;
}
}
if (challenge[len])
len++;
while (challenge[len] == ' ')
len++;
return challenge + len;
}
char *
cockpit_authorize_parse_basic (const char *challenge,
char **user)
{
unsigned char *buf = NULL;
char *type = NULL;
size_t len;
ssize_t res;
int errn = 0;
size_t off;
challenge = cockpit_authorize_type (challenge, &type);
if (!challenge)
return NULL;
if (strcmp (type, "basic") != 0)
{
message ("invalid prefix in Basic header");
errn = EINVAL;
goto out;
}
len = strcspn (challenge, " ");
buf = malloc (len + 1);
if (!buf)
{
message ("couldn't allocate memory for Basic header");
errn = ENOMEM;
goto out;
}
/* No value */
if (len == 0)
{
buf[0] = 0;
if (user)
*user = NULL;
goto out;
}
/* Decode and find split point */
res = cockpit_base64_pton (challenge, len, buf, len);
if (res < 0)
{
message ("invalid base64 data in Basic header");
errn = EINVAL;
goto out;
}
assert (res <= len);
buf[res] = 0;
off = strcspn ((char *)buf, ":");
if (off == res)
{
message ("invalid base64 data in Basic header");
errn = EINVAL;
goto out;
}
if (user)
{
*user = strndup ((char *)buf, off);
if (!*user)
{
message ("couldn't allocate memory for user name");
errn = ENOMEM;
goto out;
}
}
memmove (buf, buf + off + 1, res - off);
out:
free (type);
if (errn != 0)
{
errno = errn;
free (buf);
buf = NULL;
}
return (char *)buf;
}
char *
cockpit_authorize_build_basic (const char *user,
const char *password)
{
char *content = NULL;
char *encoded = NULL;
char *response = NULL;
size_t elen, clen;
int errn = 0;
if (!user)
user = "";
if (!password)
password = "";
if (asprintf (&content, "%s:%s", user, password) < 0)
{
errn = errno;
message ("could not build basic response");
goto out;
}
clen = strlen (content);
elen = cockpit_base64_size (clen);
encoded = malloc (elen + 1);
if (!encoded)
{
errn = ENOMEM;
message ("could not allocate memory for basic response");
goto out;
}
if (cockpit_base64_ntop ((unsigned char *)content, clen, encoded, elen) < 0)
{
errn = errno;
message ("could not encode basic response");
goto out;
}
if (asprintf (&response, "Basic %s", encoded) < 0)
{
errn = errno;
message ("could not build basic response");
response = NULL;
goto out;
}
out:
free (encoded);
if (content)
cockpit_memory_clear (content, -1);
free (content);
if (!response)
errno = errn;
return response;
}
void *
cockpit_authorize_parse_negotiate (const char *challenge,
size_t *length)
{
unsigned char *buf = NULL;
size_t len;
ssize_t res;
int negotiate;
char *type;
challenge = cockpit_authorize_type (challenge, &type);
if (!challenge)
return NULL;
negotiate = strcmp (type, "negotiate") == 0;
free (type);
if (!negotiate)
{
message ("invalid prefix in Negotiate header");
errno = EINVAL;
return NULL;
}
len = strcspn (challenge, " ");
buf = malloc (len + 1);
if (!buf)
{
message ("couldn't allocate memory for Negotiate header");
errno = ENOMEM;
return NULL;
}
/* Decode data */
res = cockpit_base64_pton (challenge, len, buf, len);
if (res < 0)
{
message ("invalid base64 data in Negotiate header");
free (buf);
errno = EINVAL;
return NULL;
}
if (length)
*length = res;
return buf;
}
char *
cockpit_authorize_build_negotiate (const void *input,
size_t length)
{
char *encoded = NULL;
char *response = NULL;
size_t elen;
int errn = 0;
if (!input)
length = 0;
if (length > 0)
{
elen = cockpit_base64_size (length);
encoded = malloc (elen);
if (!encoded)
{
errn = ENOMEM;
message ("could not allocate memory for negotiate challenge");
goto out;
}
if (cockpit_base64_ntop ((unsigned char *)input, length, encoded, elen) < 0)
{
errn = errno;
message ("could not encode negotiate prompt");
goto out;
}
}
if (asprintf (&response, "Negotiate%s%s", encoded ? " " : "", encoded ? encoded : "") < 0)
{
errn = errno;
message ("could not build negotiate challenge");
response = NULL;
goto out;
}
out:
free (encoded);
if (!response)
errno = errn;
return response;
}
char *
cockpit_authorize_parse_x_conversation (const char *challenge,
char **conversation)
{
unsigned char *buf = NULL;
int x_conversation;
char *type;
size_t len;
ssize_t res;
if (!cockpit_authorize_type (challenge, &type))
return NULL;
x_conversation = strcmp (type, "x-conversation") == 0;
free (type);
if (!x_conversation)
{
message ("invalid prefix in X-Conversation header");
errno = EINVAL;
return NULL;
}
challenge = cockpit_authorize_subject (challenge, conversation);
if (!challenge)
return NULL;
len = strcspn (challenge, " ");
buf = malloc (len + 1);
if (!buf)
{
message ("couldn't allocate memory for X-Conversation header");
errno = ENOMEM;
return NULL;
}
/* Decode and find split point */
res = cockpit_base64_pton (challenge, len, buf, len);
if (res < 0)
{
message ("invalid base64 data in X-Conversation header");
free (buf);
errno = EINVAL;
return NULL;
}
/* Null terminate the thing */
buf[res] = '\0';
return (char *)buf;
}
char *
cockpit_authorize_build_x_conversation (const char *prompt,
char **conversation)
{
const size_t nlen = 128;
unsigned char *nonce = NULL;
char *encoded = NULL;
char *response = NULL;
char *conv = NULL;
size_t plen, elen, clen;
char *alloc = NULL;
int errn = 0;
if (!prompt)
prompt = "";
/* Reuse a conversation we get */
if (conversation)
conv = *conversation;
if (!conv)
{
nonce = cockpit_authorize_nonce (nlen);
if (!nonce)
{
errn = errno;
message ("could not generate nonce");
goto out;
}
clen = cockpit_base64_size (nlen);
conv = alloc = malloc (clen);
if (!conv)
{
errn = ENOMEM;
message ("could not allocate memory for conversation");
goto out;
}
if (cockpit_base64_ntop (nonce, nlen, conv, clen) < 0)
{
errn = errno;
message ("could not encode conversation nonce");
goto out;
}
}
if (strlen (conv) == 0)
{
message ("invalid conversation nonce");
errn = EINVAL;
goto out;
}
plen = strlen (prompt);
if (plen > 0)
{
elen = cockpit_base64_size (plen);
encoded = malloc (elen);
if (!encoded)
{
errn = ENOMEM;
message ("could not allocate memory for conversation");
goto out;
}
if (cockpit_base64_ntop ((unsigned char *)prompt, plen, encoded, elen) < 0)
{
errn = errno;
message ("could not encode conversation prompt");
goto out;
}
}
if (asprintf (&response, "X-Conversation %s%s%s", conv, encoded ? " " : "", encoded ? encoded : "") < 0)
{
errn = errno;
message ("could not build conversation challenge");
response = NULL;
goto out;
}
if (conversation)
{
*conversation = conv;
conv = alloc = NULL;
}
out:
free (nonce);
free (alloc);
free (encoded);
if (!response)
errno = errn;
return response;
}