虽然安蒂哈帕拉已经完全
answered
这个问题,我认为一些关于该方法的评论,以及一个使安全使用变得微不足道的示例函数,可能是有用的。
xdg-open
是freedesktop.org的桌面集成实用程序的一部分,它是
Portland project
. 人们可以期望它们在运行桌面环境的任何计算机上都可用。
participating in freedesktop.org
. 这包括gnome、kde和xfce。
简单地说,
这是推荐的方法
当桌面环境正在使用时,在用户喜欢的任何应用程序中打开资源(无论是文件还是URL)。
如果没有正在使用的桌面环境,那么就没有理由期望
XDG开放
可供选择。
对于Linux,我建议使用一个专用的函数,可能是沿着以下几行。首先,几个内部助手函数:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define ULONG_BITS (CHAR_BIT * sizeof (unsigned long))
static inline int devnullfd(const int fd)
{
int tempfd;
if (fd == -1)
return errno = EINVAL;
do {
tempfd = open("/dev/null", O_RDWR | O_NOCTTY);
} while (tempfd == -1 && errno == EINTR);
if (tempfd == -1)
return errno;
if (tempfd != fd) {
if (dup2(tempfd, fd) == -1) {
const int saved_errno = errno;
close(tempfd);
return errno = saved_errno;
}
if (close(tempfd) == -1)
return errno;
}
return 0;
}
static void closeall(const unsigned long mask)
{
DIR *dir;
struct dirent *ent;
int dfd;
dir = opendir("/proc/self/fd/");
if (!dir) {
const long fd_max = sysconf(_SC_OPEN_MAX);
long fd;
for (fd = 0; fd < ULONG_BITS; fd++)
if (!(mask & (1uL << fd)))
close(fd);
for (fd = ULONG_BITS; fd <= fd_max; fd++)
close(fd);
return;
}
dfd = dirfd(dir);
while ((ent = readdir(dir)))
if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {
const char *p = &ent->d_name[1];
int fd = ent->d_name[0] - '0';
while (*p >= '0' && *p <= '9')
fd = (10 * fd) + *(p++) - '0';
if (*p)
continue;
if (fd == dfd)
continue;
if (fd < ULONG_MAX && (mask & (1uL << fd)))
continue;
close(fd);
}
closedir(dir);
}
closeall(0)
尝试关闭所有打开的文件描述符,并且
devnullfd(fd)
试图打开
fd
到
/dev/null
. 这些是用来确保即使用户欺骗
XDG开放
,没有泄漏文件描述符;只传递文件名或URL。
在非Linux posixy系统上,您可以用更合适的东西替换它们。关于BSDS的使用
closefrom()
和处理第一个
ULONG_MAX
循环中的描述符。
这个
xdg_open(file-or-url)
函数本身是沿着
int xdg_open(const char *file_or_url)
{
pid_t child, p;
int status;
if (!file_or_url || !*file_or_url)
return errno = EINVAL;
child = fork();
if (child == -1)
return errno;
else
if (!child) {
uid_t uid = getuid();
gid_t gid = getgid();
closeall(0);
devnullfd(STDIN_FILENO);
devnullfd(STDOUT_FILENO);
devnullfd(STDERR_FILENO);
if (setresgid(gid, gid, gid) == -1 ||
setresuid(uid, uid, uid) == -1)
_Exit(98);
setsid();
execlp("xdg-open", "xdg-open", file_or_url, (char *)0);
_Exit(99);
}
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1)
return errno;
if (!WIFEXITED(status)) {
return errno = EIO;
}
switch (WEXITSTATUS(status)) {
case 0:
return errno = 0;
case 1:
return errno = EINVAL;
case 2:
return errno = ENOENT;
case 3:
return errno = ENOSYS;
case 4:
return errno = EPROTO;
case 98:
return errno = EACCES;
case 99:
return errno = ENOPKG;
default:
return errno = ENOSYS;
}
}
如前所述,它试图关闭所有打开的文件描述符,将标准流重定向到
/DEV/NULL
,确保有效和真实的标识匹配(如果在setuid二进制文件中使用),并使用子进程退出状态传递成功/失败。
这个
setresuid()
和
setresgid()
只有保存了用户和组ID的操作系统才能调用。关于他人,使用
seteuid(uid)
和
setegid()
相反。
这个实现试图平衡用户的可配置性和安全性。用户可以设置
PATH
所以他们的最爱
XDG开放
得到执行,但该函数尝试确保不会向该进程泄漏敏感信息或特权。
(环境变量可以被过滤,但首先不应该包含敏感信息,而且我们不知道桌面环境使用的是哪一个。所以最好不要和他们混在一起,把用户的惊喜降到最低。)
作为最低限度的测试
main()
,请尝试以下操作:
int main(int argc, char *argv[])
{
int arg, status;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s FILE-OR-URL ...\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This example program opens each specified file or URL\n");
fprintf(stderr, "xdg-open(1), and outputs success or failure for each.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
status = EXIT_SUCCESS;
for (arg = 1; arg < argc; arg++)
if (xdg_open(argv[arg])) {
printf("%s: %s.\n", argv[arg], strerror(errno));
status = EXIT_FAILURE;
} else
printf("%s: Opened.\n", argv[arg]);
return status;
}
当SPDX许可证标识符声明时,此示例代码在
Creative Commons Zero 1.0
. 用你想用的任何方式,用你想用的任何代码。