blob: 39aae9696362b98cc031f7d5aebb81c441161759 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed 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. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#if defined(__MINGW32__)
#include <ws2tcpip.h>
#elif defined(__MSDOS__)
#elif defined(GRUB_RUNTIME)
typedef unsigned int uid_t;
typedef unsigned int gid_t;
#else /* Unix */
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <grp.h>
#endif /* platform-specific includes */
#if !defined(AF_LOCAL) && defined(AF_UNIX)
#define AF_LOCAL AF_UNIX
#endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */
#if !defined(PF_LOCAL) && defined(PF_UNIX)
#define PF_LOCAL PF_UNIX
#endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */
#include "log.h"
#include "strfmt.h"
#include "parse.h"
#include "auth.h"
#include "async_wait.h"
/* peer credentials */
#undef CAN_CHECK_CREDENTIALS
typedef struct {
#ifdef __MINGW32__
const char *name;
#else /* __MINGW32__ */
uid_t id;
#endif /* __MINGW32__ */
} MethodDescriptor_user;
typedef struct {
#ifdef __MINGW32__
const char *name;
#else /* __MINGW32__ */
gid_t id;
#endif /* __MINGW32__ */
} MethodDescriptor_group;
#if defined(__MINGW32__)
#define CAN_CHECK_CREDENTIALS
typedef struct {
char *user;
} PeerCredentials;
static int
retrievePeerCredentials (PeerCredentials *credentials, FileDescriptor fd) {
char buffer[0X100+1];
if (GetNamedPipeHandleState(fd, NULL, NULL, NULL, NULL, buffer, sizeof(buffer))) {
buffer[sizeof(buffer) - 1] = 0;
if ((credentials->user = strdup(buffer))) {
return 1;
}
} else {
switch (GetLastError()) {
default:
logWindowsSystemError("GetNamedPipeHandleState");
case ERROR_INSUFFICIENT_BUFFER: /* buffer too small */
case ERROR_INVALID_HANDLE: /* not a named pipe */
case ERROR_CANNOT_IMPERSONATE: /* no data transferred yet */
break;
}
}
return 0;
}
static void
releasePeerCredentials (PeerCredentials *credentials) {
free(credentials->user);
}
static int
checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
return strcmp(user->name, credentials->user) == 0;
}
static int
checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
return 0;
}
#elif defined(HAVE_GETPEERUCRED)
#define CAN_CHECK_CREDENTIALS
#include <ucred.h>
#ifdef HAVE_GETZONEID
#include <zone.h>
#endif /* HAVE_GETZONEID */
typedef ucred_t *PeerCredentials;
static int
retrievePeerCredentials (PeerCredentials *credentials, int fd) {
*credentials = NULL;
if (getpeerucred(fd, credentials) == -1) {
logSystemError("getpeerucred");
return 0;
}
#ifdef HAVE_GETZONEID
if (ucred_getzoneid(*credentials) != getzoneid()) {
ucred_free(*credentials);
return 0;
}
#endif /* HAVE_GETZONEID */
return 1;
}
static void
releasePeerCredentials (PeerCredentials *credentials) {
ucred_free(*credentials);
}
static int
checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
if (user->id == ucred_geteuid(*credentials)) return 1;
return 0;
}
static int
checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
if (group->id == ucred_getegid(*credentials)) return 1;
{
const gid_t *groups;
int count = ucred_getgroups(*credentials, &groups);
while (count > 0)
if (group->id == groups[--count])
return 1;
}
return 0;
}
#elif defined(HAVE_GETPEEREID)
#define CAN_CHECK_CREDENTIALS
typedef struct {
uid_t euid;
gid_t egid;
} PeerCredentials;
static int
retrievePeerCredentials (PeerCredentials *credentials, int fd) {
if (getpeereid(fd, &credentials->euid, &credentials->egid) != -1) return 1;
logSystemError("getpeereid");
return 0;
}
static void
releasePeerCredentials (PeerCredentials *credentials) {
}
static int
checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
return user->id == credentials->euid;
}
static int
checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
return group->id == credentials->egid;
}
#elif defined(SO_PEERCRED)
#define CAN_CHECK_CREDENTIALS
typedef struct ucred PeerCredentials;
static int
retrievePeerCredentials (PeerCredentials *credentials, int fd) {
socklen_t length = sizeof(*credentials);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, credentials, &length) != -1) return 1;
logSystemError("getsockopt[SO_PEERCRED]");
return 0;
}
static void
releasePeerCredentials (PeerCredentials *credentials) {
}
static int
checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
return user->id == credentials->uid;
}
static int
checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
return group->id == credentials->gid;
}
#else /* peer credentials method */
#warning peer credentials support not available on this platform
#endif /* peer credentials method */
/* general type definitions */
typedef int (*MethodPerform) (AuthDescriptor *auth, FileDescriptor fd, void *data);
typedef struct {
const char *name;
void *(*initialize) (const char *parameter);
void (*release) (void *data);
MethodPerform client;
MethodPerform server;
} MethodDefinition;
typedef struct {
const MethodDefinition *definition;
void *data;
} MethodDescriptor;
#ifdef CAN_CHECK_CREDENTIALS
typedef enum {
PCS_NEED,
PCS_CANT,
PCS_HAVE
} PeerCredentialsState;
#endif /* CAN_CHECK_CREDENTIALS */
typedef int (*AuthPerform) (AuthDescriptor *auth, FileDescriptor fd);
struct AuthDescriptorStruct {
int count;
char **parameters;
MethodDescriptor *methods;
AuthPerform perform;
#ifdef CAN_CHECK_CREDENTIALS
PeerCredentialsState peerCredentialsState;
PeerCredentials peerCredentials;
#endif /* CAN_CHECK_CREDENTIALS */
};
/* the keyfile method */
typedef struct {
const char *path;
} MethodDescriptor_keyfile;
static void *
authKeyfile_initialize (const char *parameter) {
MethodDescriptor_keyfile *keyfile;
if ((keyfile = malloc(sizeof(*keyfile)))) {
if (*parameter) {
keyfile->path = parameter;
return keyfile;
} else {
logMessage(LOG_ERR, "path to key file not specified");
}
free(keyfile);
} else {
logMallocError();
}
return NULL;
}
static void
authKeyfile_release (void *data) {
MethodDescriptor_keyfile *keyfile = data;
free(keyfile);
}
static int
authKeyfile_client (AuthDescriptor *auth, FileDescriptor fd, void *data) {
return 1;
}
static int
authKeyfile_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
MethodDescriptor_keyfile *keyfile = data;
logMessage(LOG_CATEGORY(SERVER_EVENTS), "checking key file: %s", keyfile->path);
return 1;
}
#ifdef CAN_CHECK_CREDENTIALS
static int
getPeerCredentials (AuthDescriptor *auth, FileDescriptor fd) {
if (auth->peerCredentialsState == PCS_NEED) {
auth->peerCredentialsState = retrievePeerCredentials(&auth->peerCredentials, fd)? PCS_HAVE: PCS_CANT;
}
return auth->peerCredentialsState == PCS_HAVE;
}
/* the user method */
static void *
authUser_initialize (const char *parameter) {
MethodDescriptor_user *user;
if ((user = malloc(sizeof(*user)))) {
#ifdef __MINGW32__
user->name = parameter;
return user;
#else /* __MINGW32__ */
if (!*parameter) {
user->id = geteuid();
return user;
}
{
int value;
if (isInteger(&value, parameter)) {
user->id = value;
return user;
}
}
{
const struct passwd *p = getpwnam(parameter);
if (p) {
user->id = p->pw_uid;
return user;
}
}
logMessage(LOG_ERR, "unknown user: %s", parameter);
free(user);
#endif /* __MINGW32__ */
} else {
logMallocError();
}
return NULL;
}
static void
authUser_release (void *data) {
MethodDescriptor_user *user = data;
free(user);
}
static int
authUser_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
MethodDescriptor_user *user = data;
return getPeerCredentials(auth, fd) &&
checkPeerUser(&auth->peerCredentials, user);
}
/* the group method */
static void *
authGroup_initialize (const char *parameter) {
MethodDescriptor_group *group;
if ((group = malloc(sizeof(*group)))) {
#ifdef __MINGW32__
group->name = parameter;
return group;
#else /* __MINGW32__ */
if (!*parameter) {
group->id = getegid();
return group;
}
{
int value;
if (isInteger(&value, parameter)) {
group->id = value;
return group;
}
}
{
const struct group *g = getgrnam(parameter);
if (g) {
group->id = g->gr_gid;
return group;
}
}
logMessage(LOG_ERR, "unknown group: %s", parameter);
free(group);
#endif /* __MINGW32__ */
} else {
logMallocError();
}
return NULL;
}
static void
authGroup_release (void *data) {
MethodDescriptor_group *group = data;
free(group);
}
static int
authGroup_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
MethodDescriptor_group *group = data;
return getPeerCredentials(auth, fd) &&
checkPeerGroup(&auth->peerCredentials, group);
}
#ifdef HAVE_POLKIT
#include <polkit/polkit.h>
typedef struct {
PolkitAuthority *authority;
} MethodDescriptor_polkit;
static void *
authPolkit_initialize (const char *parameter) {
MethodDescriptor_polkit *polkit;
if ((polkit = malloc(sizeof(*polkit)))) {
memset(polkit, 0, sizeof(*polkit));
while (1) {
GError *error = NULL;
polkit->authority = polkit_authority_get_sync(NULL, &error);
if (polkit->authority) return polkit;
int wait = 0;
char message[0X100];
STR_BEGIN(message, sizeof(message));
STR_PRINTF("unable to connect to polkit");
if (error) {
GQuark domain = error->domain;
gint code = error->code;
STR_PRINTF(": %s (%d) %s (%d)",
g_quark_to_string(domain), (int)domain,
error->message, code);
g_error_free(error);
error = NULL;
if ((domain == G_IO_ERROR) || (code == G_IO_ERROR_NOT_FOUND)) wait = 1;
}
STR_END;
logMessage(LOG_WARNING, "%s", message);
if (!wait) break;
asyncWait(1000);
}
g_free(polkit);
} else {
logMallocError();
}
return NULL;
}
static void
authPolkit_release (void *data) {
MethodDescriptor_polkit *polkit = data;
g_object_unref (polkit->authority);
free(polkit);
}
static int
authPolkit_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
MethodDescriptor_polkit *polkit = data;
struct ucred cred;
socklen_t length = sizeof(cred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &length) != -1) {
logMessage(LOG_CATEGORY(SERVER_EVENTS),
"attempting to authorize client (pid %d, uid %d) via polkit",
cred.pid, cred.uid
);
if (cred.uid == -1) {
logMessage(LOG_CATEGORY(SERVER_EVENTS),
"user not specified in credentials"
);
} else {
PolkitSubject *subject = polkit_unix_process_new_for_owner(cred.pid, 0, cred.uid);
if (subject) {
GError *error_local = NULL;
PolkitAuthorizationResult *result = polkit_authority_check_authorization_sync(
polkit->authority, /* authority */
subject, /* PolkitSubject for client */
"org.a11y.brlapi.write-display", /* name of polkit action */
NULL, /* details */
POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE, /* disallow interaction */
NULL, /* GCancellable */
&error_local /* returned error */
);
if (result) {
int isAuthorized = polkit_authorization_result_get_is_authorized(result);
g_object_unref(result);
logMessage(LOG_CATEGORY(SERVER_EVENTS),
"polkit_authority_check_authorization_sync returned %d",
isAuthorized
);
return isAuthorized;
} else {
logMessage(LOG_ERR, "polkit_authority_check_authorization_sync error: %s", error_local->message);
g_error_free(error_local);
}
g_object_unref(subject);
} else {
logSystemError("polkit_unix_process_new_for_owner");
}
}
} else {
logSystemError("getsockopt[SO_PEERCRED]");
}
return 0;
}
#endif /* HAVE_POLKIT */
#endif /* CAN_CHECK_CREDENTIALS */
/* general functions */
static const MethodDefinition methodDefinitions[] = {
{ .name = "keyfile",
.initialize = authKeyfile_initialize,
.release = authKeyfile_release,
.client = authKeyfile_client,
.server = authKeyfile_server
},
#ifdef CAN_CHECK_CREDENTIALS
{ .name = "user",
.initialize = authUser_initialize,
.release = authUser_release,
.client = NULL,
.server = authUser_server
},
{ .name = "group",
.initialize = authGroup_initialize,
.release = authGroup_release,
.client = NULL,
.server = authGroup_server
},
#ifdef HAVE_POLKIT
{ .name = "polkit",
.initialize = authPolkit_initialize,
.release = authPolkit_release,
.client = NULL,
.server = authPolkit_server
},
#endif /* HAVE_POLKIT */
#endif /* CAN_CHECK_CREDENTIALS */
{.name = NULL}
};
static void
releaseMethodDescriptor (MethodDescriptor *method) {
if (method->data) {
if (method->definition->release) method->definition->release(method->data);
method->data = NULL;
}
}
static void
releaseMethodDescriptors (AuthDescriptor *auth, int count) {
while (count > 0) releaseMethodDescriptor(&auth->methods[--count]);
}
static int
initializeMethodDescriptor (MethodDescriptor *method, const char *parameter) {
const char *name;
int nameLength;
if ((parameter = strchr(name=parameter, ':'))) {
nameLength = parameter++ - name;
} else {
parameter = name + (nameLength = strlen(name));
}
{
const MethodDefinition *definition = methodDefinitions;
while (definition->name) {
if ((nameLength == strlen(definition->name)) &&
(strncmp(name, definition->name, nameLength) == 0)) {
void *data = definition->initialize(parameter);
if (!data) return 0;
method->definition = definition;
method->data = data;
return 1;
}
++definition;
}
}
logMessage(LOG_WARNING, "unknown authorization method: %.*s", nameLength, name);
return 0;
}
static int
initializeMethodDescriptors (AuthDescriptor *auth) {
int index = 0;
while (index < auth->count) {
if (!initializeMethodDescriptor(&auth->methods[index], auth->parameters[index])) {
releaseMethodDescriptors(auth, index);
return 0;
}
++index;
}
return 1;
}
static AuthDescriptor *
authBegin (const char *parameter, const char *defaultParameter, AuthPerform perform) {
AuthDescriptor *auth;
if ((auth = malloc(sizeof(*auth)))) {
auth->perform = perform;
if (!parameter) parameter = "";
if (!*parameter) {
parameter = defaultParameter;
} else if (strcmp(parameter, "none") == 0) {
parameter = "";
}
if ((auth->parameters = splitString(parameter, '+', &auth->count))) {
if (!auth->count) {
auth->methods = NULL;
return auth;
}
if ((auth->methods = malloc(ARRAY_SIZE(auth->methods, auth->count)))) {
if (initializeMethodDescriptors(auth)) return auth;
free(auth->methods);
} else {
logMallocError();
}
deallocateStrings(auth->parameters);
}
free(auth);
} else {
logMallocError();
}
return NULL;
}
static int
authPerformClient (AuthDescriptor *auth, FileDescriptor fd) {
return 1;
}
AuthDescriptor *
authBeginClient (const char *parameter) {
return authBegin(parameter, "", authPerformClient);
}
static int
authPerformServer (AuthDescriptor *auth, FileDescriptor fd) {
int ok = 0;
if (!auth->count) return 1;
#ifdef CAN_CHECK_CREDENTIALS
auth->peerCredentialsState = PCS_NEED;
#endif /* CAN_CHECK_CREDENTIALS */
{
int index;
for (index=0; index<auth->count; ++index) {
const MethodDescriptor *method = &auth->methods[index];
if (!method->definition->server) continue;
if (method->definition->client) continue;
if (method->definition->server(auth, fd, method->data)) {
ok = 1;
break;
}
}
}
#ifdef CAN_CHECK_CREDENTIALS
if (auth->peerCredentialsState != PCS_NEED) {
if (auth->peerCredentialsState == PCS_HAVE) releasePeerCredentials(&auth->peerCredentials);
if (!ok) logMessage(LOG_ERR, "no matching user or group");
}
#endif /* CAN_CHECK_CREDENTIALS */
if (!ok) {
int index;
for (index=0; index<auth->count; ++index) {
const MethodDescriptor *method = &auth->methods[index];
if (!method->definition->server) continue;
if (!method->definition->client) continue;
}
}
return ok;
}
AuthDescriptor *
authBeginServer (const char *parameter) {
return authBegin(parameter, "user", authPerformServer);
}
void
authEnd (AuthDescriptor *auth) {
releaseMethodDescriptors(auth, auth->count);
if (auth->methods) free(auth->methods);
deallocateStrings(auth->parameters);
free(auth);
}
int
authPerform (AuthDescriptor *auth, FileDescriptor fd) {
return auth->perform(auth, fd);
}
void
formatAddress (
char *buffer, size_t bufferSize,
const void *address, socklen_t addressSize
) {
#ifdef AF_INET
const struct sockaddr *sa = address;
switch (sa->sa_family) {
#ifndef __MINGW32__
case AF_LOCAL: {
const struct sockaddr_un *local = address;
if (addressSize <= sizeof(sa_family_t)) {
snprintf(buffer, bufferSize, "local <unnamed>");
} else {
snprintf(buffer, bufferSize, "local %s", local->sun_path);
}
break;
}
#endif /* __MINGW32__ */
case AF_INET: {
const struct sockaddr_in *inet = address;
snprintf(buffer, bufferSize, "inet %s:%d", inet_ntoa(inet->sin_addr), ntohs(inet->sin_port));
break;
}
default:
#if defined(HAVE_GETNAMEINFO) && !defined(WINDOWS)
{
char host[NI_MAXHOST];
char service[NI_MAXSERV];
int err;
if (!(err = getnameinfo(address, addressSize,
host, sizeof(host), service, sizeof(service),
NI_NUMERICHOST | NI_NUMERICSERV))) {
snprintf(buffer, bufferSize, "af=%d %s:%s", sa->sa_family, host, service);
break;
}
if (err != EAI_FAMILY) {
#ifdef HAVE_GAI_STRERROR
snprintf(buffer, bufferSize, "reverse lookup error for address family %d: %s",
sa->sa_family,
#ifdef EAI_SYSTEM
(err == EAI_SYSTEM)? strerror(errno):
#endif /* EAI_SYSTEM */
gai_strerror(err));
#else /* HAVE_GAI_STRERROR */
snprintf(buffer, bufferSize, "reverse lookup error %d for address family %d",
err, sa->sa_family);
#endif /* HAVE_GAI_STRERROR */
break;
}
}
#endif /* GETNAMEINFO */
{
STR_BEGIN(buffer, bufferSize);
STR_PRINTF("address family %d:", sa->sa_family);
{
const unsigned char *byte = address;
const unsigned char *end = byte + addressSize;
while (byte < end) STR_PRINTF(" %02X", *byte++);
}
STR_END;
}
break;
}
#else /* AF_INET */
snprintf(buffer, bufferSize, "unknown");
#endif /* AF_INET */
}