179 lines
7.1 KiB
C
179 lines
7.1 KiB
C
#ifdef _WIN32
|
|||
|
|
#define _WIN32_WINNT 0x0600
|
||
|
|
#include <windows.h>
|
||
|
|
#include <sddl.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#else
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#endif
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
/* 创建、读取和清理仅本机提升身份可访问的认证凭据。 */
|
||
|
|
|
||
|
|
#ifdef _WIN32
|
||
|
|
/* 检查当前提升令牌的管理员成员资格。 */
|
||
|
|
int chusql_sudo_privileged(void) {
|
||
|
|
SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY;
|
||
|
|
PSID administrators = NULL;
|
||
|
|
BOOL member = FALSE;
|
||
|
|
if (!AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID,
|
||
|
|
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administrators)) return 0;
|
||
|
|
if (!CheckTokenMembership(NULL, administrators, &member)) member = FALSE;
|
||
|
|
FreeSid(administrators);
|
||
|
|
return member != FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 生成系统保护目录中的端口专属路径。 */
|
||
|
|
static int credential_path(int port, wchar_t *directory, wchar_t *file) {
|
||
|
|
wchar_t base[MAX_PATH];
|
||
|
|
UINT length = GetSystemDirectoryW(base, MAX_PATH);
|
||
|
|
if (!length || length >= MAX_PATH - 80 || port < 1 || port > 65535) return ERROR_INVALID_PARAMETER;
|
||
|
|
swprintf(directory, MAX_PATH, L"%ls\\config\\ChuSQL-Sudo-%d", base, port);
|
||
|
|
swprintf(file, MAX_PATH, L"%ls\\credential", directory);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 排除重解析目录与凭据文件。 */
|
||
|
|
static int plain_path(const wchar_t *path, int directory) {
|
||
|
|
DWORD attributes = GetFileAttributesW(path);
|
||
|
|
return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||
|
|
&& (!!(attributes & FILE_ATTRIBUTE_DIRECTORY) == directory);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 用管理员与系统专属访问列表创建凭据。 */
|
||
|
|
int chusql_sudo_create(int port, const char *secret) {
|
||
|
|
wchar_t directory[MAX_PATH], file[MAX_PATH];
|
||
|
|
PSECURITY_DESCRIPTOR descriptor = NULL;
|
||
|
|
SECURITY_ATTRIBUTES attributes = {sizeof(SECURITY_ATTRIBUTES), NULL, FALSE};
|
||
|
|
DWORD written = 0, error;
|
||
|
|
HANDLE handle;
|
||
|
|
if (!chusql_sudo_privileged()) return ERROR_ACCESS_DENIED;
|
||
|
|
error = credential_path(port, directory, file);
|
||
|
|
if (error) return (int)error;
|
||
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||
|
|
L"D:P(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)", SDDL_REVISION_1, &descriptor, NULL)) return (int)GetLastError();
|
||
|
|
attributes.lpSecurityDescriptor = descriptor;
|
||
|
|
if (!CreateDirectoryW(directory, &attributes)) {
|
||
|
|
error = GetLastError(); LocalFree(descriptor); return (int)error;
|
||
|
|
}
|
||
|
|
handle = CreateFileW(file, GENERIC_WRITE, FILE_SHARE_READ, &attributes, CREATE_NEW,
|
||
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||
|
|
LocalFree(descriptor);
|
||
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
||
|
|
error = GetLastError(); RemoveDirectoryW(directory); return (int)error;
|
||
|
|
}
|
||
|
|
error = 0;
|
||
|
|
if (!WriteFile(handle, secret, 64, &written, NULL) || written != 64 || !FlushFileBuffers(handle))
|
||
|
|
error = GetLastError() ? GetLastError() : ERROR_WRITE_FAULT;
|
||
|
|
if (!CloseHandle(handle) && !error) error = GetLastError();
|
||
|
|
if (error) { DeleteFileW(file); RemoveDirectoryW(directory); }
|
||
|
|
return (int)error;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 从系统保护目录读取本机凭据。 */
|
||
|
|
int chusql_sudo_read(int port, char *secret) {
|
||
|
|
wchar_t directory[MAX_PATH], file[MAX_PATH];
|
||
|
|
DWORD received = 0, error;
|
||
|
|
LARGE_INTEGER size;
|
||
|
|
HANDLE handle;
|
||
|
|
if (!chusql_sudo_privileged()) return ERROR_ACCESS_DENIED;
|
||
|
|
error = credential_path(port, directory, file);
|
||
|
|
if (error) return (int)error;
|
||
|
|
if (!plain_path(directory, 1) || !plain_path(file, 0)) return ERROR_ACCESS_DENIED;
|
||
|
|
handle = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||
|
|
if (handle == INVALID_HANDLE_VALUE) return (int)GetLastError();
|
||
|
|
error = 0;
|
||
|
|
if (!GetFileSizeEx(handle, &size) || size.QuadPart != 64
|
||
|
|
|| !ReadFile(handle, secret, 64, &received, NULL) || received != 64) error = ERROR_INVALID_DATA;
|
||
|
|
if (!CloseHandle(handle) && !error) error = GetLastError();
|
||
|
|
return (int)error;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 删除本次服务启动创建的凭据目录。 */
|
||
|
|
int chusql_sudo_remove(int port) {
|
||
|
|
wchar_t directory[MAX_PATH], file[MAX_PATH];
|
||
|
|
int error = credential_path(port, directory, file);
|
||
|
|
if (error) return error;
|
||
|
|
if (!chusql_sudo_privileged()) return ERROR_ACCESS_DENIED;
|
||
|
|
if (!DeleteFileW(file)) return (int)GetLastError();
|
||
|
|
return RemoveDirectoryW(directory) ? 0 : (int)GetLastError();
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
/* 检查当前进程的有效 root 身份。 */
|
||
|
|
int chusql_sudo_privileged(void) { return geteuid() == 0; }
|
||
|
|
|
||
|
|
/* 打开 root 专属目录并验证所有权与权限。 */
|
||
|
|
static int credential_directory(int port) {
|
||
|
|
char path[96];
|
||
|
|
struct stat status;
|
||
|
|
int fd;
|
||
|
|
if (port < 1 || port > 65535) { errno = EINVAL; return -1; }
|
||
|
|
snprintf(path, sizeof(path), "/var/run/chusql-sudo-%d", port);
|
||
|
|
fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||
|
|
if (fd < 0) return -1;
|
||
|
|
if (fstat(fd, &status) || status.st_uid != 0 || (status.st_mode & 0777) != 0700) {
|
||
|
|
close(fd); errno = EACCES; return -1;
|
||
|
|
}
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 在 root 专属目录中独占创建凭据。 */
|
||
|
|
int chusql_sudo_create(int port, const char *secret) {
|
||
|
|
char path[96];
|
||
|
|
int dir, fd, error = 0;
|
||
|
|
if (!chusql_sudo_privileged()) return EACCES;
|
||
|
|
if (port < 1 || port > 65535) return EINVAL;
|
||
|
|
snprintf(path, sizeof(path), "/var/run/chusql-sudo-%d", port);
|
||
|
|
if (mkdir(path, 0700)) return errno;
|
||
|
|
dir = credential_directory(port);
|
||
|
|
if (dir < 0) { error = errno; rmdir(path); return error; }
|
||
|
|
fd = openat(dir, "credential", O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||
|
|
if (fd < 0) error = errno;
|
||
|
|
else {
|
||
|
|
if (write(fd, secret, 64) != 64 || fsync(fd)) error = errno ? errno : EIO;
|
||
|
|
if (close(fd) && !error) error = errno;
|
||
|
|
}
|
||
|
|
if (error) unlinkat(dir, "credential", 0);
|
||
|
|
if (close(dir) && !error) error = errno;
|
||
|
|
if (error) rmdir(path);
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 验证 root 文件所有权后读取凭据。 */
|
||
|
|
int chusql_sudo_read(int port, char *secret) {
|
||
|
|
struct stat status;
|
||
|
|
int dir, fd, error = 0;
|
||
|
|
if (!chusql_sudo_privileged()) return EACCES;
|
||
|
|
dir = credential_directory(port);
|
||
|
|
if (dir < 0) return errno;
|
||
|
|
fd = openat(dir, "credential", O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||
|
|
if (fd < 0) error = errno;
|
||
|
|
else {
|
||
|
|
if (fstat(fd, &status) || !S_ISREG(status.st_mode) || status.st_uid != 0
|
||
|
|
|| (status.st_mode & 0777) != 0600 || status.st_size != 64 || status.st_nlink != 1) error = EACCES;
|
||
|
|
else if (read(fd, secret, 64) != 64) error = errno ? errno : EIO;
|
||
|
|
if (close(fd) && !error) error = errno;
|
||
|
|
}
|
||
|
|
if (close(dir) && !error) error = errno;
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 删除本次服务启动创建的凭据目录。 */
|
||
|
|
int chusql_sudo_remove(int port) {
|
||
|
|
char path[96];
|
||
|
|
int dir, error = 0;
|
||
|
|
if (!chusql_sudo_privileged()) return EACCES;
|
||
|
|
dir = credential_directory(port);
|
||
|
|
if (dir < 0) return errno;
|
||
|
|
if (unlinkat(dir, "credential", 0)) error = errno;
|
||
|
|
if (close(dir) && !error) error = errno;
|
||
|
|
snprintf(path, sizeof(path), "/var/run/chusql-sudo-%d", port);
|
||
|
|
if (!error && rmdir(path)) error = errno;
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
#endif
|