一冤馏、 D-Bus簡介
D-Bus是一種高效践付、易用的進(jìn)程間通信方式.
D-Bus分為兩種:system bus(系統(tǒng)總線)威兜,用于系統(tǒng)(Linux)與用戶程序之間進(jìn)行通信和消息的傳遞芙粱;session bus(會(huì)話總線)狼渊,用于用戶程序之間進(jìn)行通信.
D-Feet是一個(gè)查看和調(diào)用D-Bus接口的圖形化工具.UOS下安裝命令sudo apt install d-feet.
D-Bus官網(wǎng)
D-Feet的使用
D-Feet Wiki
二. 背景介紹
1. libsysted sd-bus簡介
一個(gè)輕量級(jí)的D-Bus庫. 我們用它來演示如何開發(fā)一個(gè)簡答的系統(tǒng)D-Bus程序.
編譯依賴:pkg-config libsystemd-dev libsystemd0
編譯時(shí)頭文件和動(dòng)態(tài)庫: 運(yùn)行 `pkg-config --cflags --libs libsystemd` 獲得.
2. D-bus接口org.freedesktop.PolicyKit1.Authority
org.freedesktop.PolicyKit1 是polkit的系統(tǒng)dbus接口.
通過此dbus接口熟史,可以驗(yàn)證權(quán)限馁害,并通過與用戶交互來獲取權(quán)限.
下面是一個(gè)通過D-Feet使用org.freedesktop.PolicyKit1.Authority CheckAuthorization dbus方法來驗(yàn)證進(jìn)程deepin-devicemanager是否具備org.gnome.gparted行為的權(quán)限的例子.
(1) 通過pkaction查看權(quán)限
pkaction --action-id org.gnome.gparted --verbose
可以知道 org.gnome.gparted 需要 auth_admin,即管理員權(quán)限.
(2) 打開一個(gè)應(yīng)用程序.這里打開了deepin-devicemanager,其pid為8798.
(3) D-Feet調(diào)用org.freedesktop.PolicyKit1.Authority CheckAuthorization接口
("unix-process", {"pid":GLib.Variant('u', 4574), "start-time":GLib.Variant('t', 0)}),
"org.gnome.gparted",
{},
1,
"timeout=600"
[圖片上傳失敗...(image-d3d50b-1642718751398)]
polkit會(huì)彈出認(rèn)證窗口來與用戶交互蹂匹,獲取授權(quán).
若用戶輸入正確密碼碘菜,返回 (True, False, {}),表示進(jìn)程deepin-devicemanager擁有了執(zhí)行org.gnome.gparted action的權(quán)限.
若用戶關(guān)閉驗(yàn)證框,返回 (False, False, {'polkit.dismissed': 'true'}),標(biāo)示獲取權(quán)限失敗.
詳見org.freedesktop.PolicyKit1.Authority Interface
3. UOS系統(tǒng)白名單機(jī)制
UOS系統(tǒng)通過白名單機(jī)制放行一些系統(tǒng)級(jí)的可執(zhí)行程序忍啸,白名單中的程序擁有較高的權(quán)限仰坦,被允許直接放行.
二. 一個(gè)系統(tǒng)D-Bus示例程序,調(diào)用此D-Bus接口時(shí)會(huì)對(duì)調(diào)用者進(jìn)行鑒權(quán)
1. 自定義一個(gè)polkit action
action對(duì)應(yīng)了用戶執(zhí)行操作的polkit權(quán)限要求计雌,添加的基本步驟如下
(1)根據(jù)要開發(fā)的系統(tǒng)dbus功能悄晃,新建policy文件.
policy文件是xml格式,例如actions/net.poettering.Calculator.policy.
填入圖標(biāo)凿滤、action id妈橄,描述,polkit窗口消息等.
最主要是定義default值
allow_any: 任意的客戶端都可以
allow_inactive: 遠(yuǎn)程交互客戶端 (SSH, VNC, etc.)
allow_active: 直接登錄的TTY或X display客戶端
每個(gè)allow_***可以設(shè)置為以下的值:
表頭 | 表頭 |
---|---|
no | 不通過 |
yes | 通過 |
auth_self | 以當(dāng)前session用戶身份來驗(yàn)證 |
auth_admin | 以管理員身份來驗(yàn)證 |
auth_self_keep | 以當(dāng)前session用戶身份來驗(yàn)證鸭巴,但僅保持一定時(shí)間驗(yàn)證有效 (例如5分鐘無需在此驗(yàn)證) |
auth_admin_keep | 以管理員用戶身份來驗(yàn)證眷细,但僅保持一定時(shí)間驗(yàn)證有效 (例如5分鐘無需在此驗(yàn)證) |
(2) 復(fù)制policy文件到 /usr/share/polkit-1/actions.
(3) policy文件添加/usr/share/polkit-1/actions后使用pkaction檢查是否生效.
pkaction --action-id net.poettering.Calculator --verbose
同樣的,你可以通過D-Feet調(diào)用org.freedesktop.PolicyKit1 D-Bus接口看下添加的action效果.
2. D-Bus設(shè)置
system.d/net.poettering.Calculator.conf復(fù)制到 /etc/dbus-1/system.d/文件夾下鹃祖,允許任何程序調(diào)用net.poettering.Calculator D-Bus接口
3. 系統(tǒng)D-Bus 程序
<details>
<summary>展開查看 代碼test.c</summary>
<pre><code>
#include <stdio.h>
include <stdlib.h>
include <errno.h>
include <systemd/sd-bus.h>
include <stdio.h>
include <unistd.h>
include <stdlib.h>
include <errno.h>
include <systemd/sd-bus.h>
define MAX_BUF_SIZE 1024
define SECURITY_WHILTELIST_FILE "/var/lib/deepin/deepin_security_verify.whitelist"
/* 檢查程序是否在白名單中
讀取白名單文件中的列表并比對(duì)
*/
static int has_in_whitelist(const char *path)
{
int ret = 0;
char buf[MAX_BUF_SIZE] = {0};
FILE *fr = fopen(SECURITY_WHILTELIST_FILE, "r");
if (!fr) {
return ret;
}
while (!feof(fr)) {
memset(buf, 0, MAX_BUF_SIZE);
char* c = fgets(buf, MAX_BUF_SIZE, fr);
if (c == NULL) {
break;
}
// trim '\n'
size_t len = strlen(buf);
if (buf[len-1] == '\n') {
buf[len-1] = '\0';
}
if (strcmp(buf, path) == 0) {
ret = 1;
break;
}
}
fclose(fr);
return ret;
}
/* 根據(jù)pid 獲取 發(fā)送進(jìn)程的路徑溪椎,檢查路徑是否在白名單中
進(jìn)程路徑是/proc/<進(jìn)程id>/exe指向的路徑
/
static int get_sender_path(pid_t pid, char path)
{
int r;
sd_bus_creds *creds = NULL;
ssize_t len, buflen;
char exe_path[256] = {0};
snprintf(exe_path, 256, "/proc/%d/exe", pid);
len = readlink(exe_path, path, 256);
if( len < 1 )
{
return -1;
}
return len;
}
static int check_path(char* path)
{
return has_in_whitelist(path);
}
/* 從sender的bus message獲取pid
調(diào)用者信息中包含pid等信息,找到相關(guān)接口獲取
*/
static pid_t get_sender_pid(sd_bus_message *m)
{
int r;
pid_t pid = -1;
sd_bus_creds *creds = NULL;
r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_PID, &creds);
if (r < 0)
{
fprintf(stderr, "sd_bus_query_sender_creds failed: %s, creds = %p\n", strerror(-r), creds);
return 0;
}
r = sd_bus_creds_get_pid(creds, &pid);
if (r < 0)
{
fprintf(stderr, "sd_bus_creds_get_pid failed: %s, creds = %p\n ", strerror(-r), creds);
return 0;
}
fprintf(stderr, "sd_bus_creds_get_pid: %d\n", pid);
return pid;
}
/* 調(diào)用org.freedesktop.PolicyKit1.Authority dbus 進(jìn)行polkit鑒權(quán)
如果沒有權(quán)限 通過polkit agent與用戶交互恬口,獲取授權(quán)
*/
static int check_pid(pid_t pid)
{
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL;
sd_bus *bus = NULL;
const char *path;
int r;
unsigned is_authorized = 0;
unsigned is_challenge = 0;
int detail_size;
int a;
const char* s1;
const char* s2;
/*獲取一個(gè)系統(tǒng)bus connection */
r = sd_bus_open_system(&bus);
if (r < 0)
{
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* 調(diào)用系統(tǒng)dbus接口方法 */
r = sd_bus_call_method(
bus,
"org.freedesktop.PolicyKit1", /* service to contact */
"/org/freedesktop/PolicyKit1/Authority", /* object path */
"org.freedesktop.PolicyKit1.Authority", /* interface name */
"CheckAuthorization", /* method name */
&error, /* object to return error in */
&m, /* return message on success */
"(sa{sv})sa{ss}us",
/* 后面參數(shù)的類型
CheckAuthorization (Struct of (String, Dict of {String, Variant}) subject, String action_id, Dict of {String, String} details, UInt32 flags, String cancellation_id)
struct 就用() 擴(kuò)起來
Dict 用{} 括起來校读, 后面需要多加一個(gè)size值
Variant ‘v’,需要加一個(gè)type
可以對(duì)照著d-feet的method 參數(shù)填寫
詳見https://manpages.debian.org/testing/libsystemd-dev/sd_bus_message_read.3.en.html
*/
"unix-process",
2, // dict的個(gè)數(shù)祖能,即{sv}的個(gè)數(shù)
"pid",
"u", // Variant的type int64
pid, // Variant的value
"start-time",
"t", // glib vriant的type uint64
0,
"net.poettering.Calculator", // 權(quán)限action名稱
0, // dict的個(gè)數(shù)歉秫,即{ss}的個(gè)數(shù)
0x00000001, /*
一般設(shè)置為1,表示如果pid沒有權(quán)限养铸,則嘗試通過polkit提權(quán)
The AuthorityFeatures Flags
{
None = 0x00000000,
AllowUserInteraction = 0x00000001
}
Flags used in the CheckAuthorization() method.
None : No flags set.
AllowUserInteraction : If the Subject can obtain the authorization through authentication, and an authentication agent is available, then attempt to do so. Note, this means that the CheckAuthorization() method will block while the user is being asked to authenticate.
*/
"" //cancellation_id雁芙,一般不用,設(shè)置為空
);
if (r < 0)
{
fprintf(stderr, "Failed to Authority: %s\n", error.message);
goto finish;
}
/* 讀取dubs信息
(Struct of (Boolean, Boolean, Dict of {String, String}) result)
因?yàn)閐ict是變長的钞螟,需要循環(huán)讀取
首先進(jìn)入結(jié)構(gòu)體(直接讀取會(huì)返回失斖酶省)
systemd-241.8/src/systemd/sd-bus-protocol.h
SD_BUS_TYPE_BYTE = 'y',
SD_BUS_TYPE_BOOLEAN = 'b',
SD_BUS_TYPE_INT16 = 'n',
SD_BUS_TYPE_UINT16 = 'q',
SD_BUS_TYPE_INT32 = 'i',
SD_BUS_TYPE_UINT32 = 'u',
SD_BUS_TYPE_INT64 = 'x',
SD_BUS_TYPE_UINT64 = 't',
SD_BUS_TYPE_DOUBLE = 'd',
SD_BUS_TYPE_STRING = 's',
SD_BUS_TYPE_OBJECT_PATH = 'o',
SD_BUS_TYPE_SIGNATURE = 'g',
SD_BUS_TYPE_UNIX_FD = 'h',
SD_BUS_TYPE_ARRAY = 'a',
SD_BUS_TYPE_VARIANT = 'v',
SD_BUS_TYPE_STRUCT = 'r',
SD_BUS_TYPE_STRUCT_BEGIN = '(',
SD_BUS_TYPE_STRUCT_END = ')',
SD_BUS_TYPE_DICT_ENTRY = 'e',
SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{',
SD_BUS_TYPE_DICT_ENTRY_END = '}'
*/
/*讀取調(diào)用結(jié)果
*/
r = sd_bus_message_enter_container(m, 'r', "bba{ss}");
if (r < 0)
{
printf("sd_bus_message_enter_container failed.\n");
goto finish;
}
//讀取前兩個(gè)bool
r = sd_bus_message_read(m, "bb", &is_authorized, &is_challenge);
if( r < 0)
{
printf("sd_bus_message_read failed.\n");
goto finish;
}
printf("sd_bus_message_read is_authorized = %s, is_challenge = %s",is_authorized?"true":"false"\
,is_challenge?"true":"false");
//
r = sd_bus_message_enter_container(m, 'a', "{ss}");
// 變長數(shù)組循環(huán)讀取
while(1)
{
r = sd_bus_message_read(m, "{ss}", &s1, &s2);
if (r <= 0) {
break;
}
printf(" %s=%s", s1, s2);
}
sd_bus_message_exit_container(m);
printf(".\n");
r = sd_bus_message_exit_container(m);
finish:
/*關(guān)閉系統(tǒng)dbus連接
*/
sd_bus_error_free(&error);
sd_bus_message_unref(m);
sd_bus_unref(bus);
if( r > 0 && !is_authorized )
{
return -1;
}
return r;
}
/*
檢測sender的權(quán)限
如果sender path在白名單中 返回成功
如果不在白名單 則polkit鑒權(quán)
*/
static int check_auth(sd_bus_message *m)
{
int r;
char path[256] = {0};
pid_t pid = get_sender_pid(m);
if(pid < 0)
{
fprintf(stderr, "get_sender_pid failed. pid = %d \n", pid);
return pid;
}
r = get_sender_path(pid, path);
if(r > 0)
{
r = check_path(path);
fprintf(stderr, "check_path result. r = %d \n", r);
}
if(r > 0)
{
return r;
}
r = check_pid(pid);
return r;
}
/* dbus method調(diào)用在這里被處理
*/
static int method_multiply(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
int64_t x, y;
int r;
/*
在調(diào)用方法前進(jìn)行白名單驗(yàn)證和polkit鑒權(quán)
失敗直接返回錯(cuò)誤
*/
r = check_auth(m);
if( r < 0)
{
fprintf(stderr, "check_auth failed: %s\n", strerror(-r));
return sd_bus_reply_method_return(m, "xs", 0, &"auth failed");
}
/* 讀取傳入?yún)?shù) */
r = sd_bus_message_read(m, "xx", &x, &y);
if (r < 0)
{
fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
return r;
}
/* 返回dbus結(jié)果 */
return sd_bus_reply_method_return(m, "xs", x * y, &"success");
}
/* vtable定義dubs object的方法、信號(hào)鳞滨、屬性等
這里定義了一個(gè)乘法的method
*/
static const sd_bus_vtable calculator_vtable[] =
{
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Multiply", "xx", "xs", method_multiply, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int main(int argc, char *argv[])
{
sd_bus_slot *slot = NULL;
sd_bus *bus = NULL;
int r;
/* 1. 獲取一個(gè)系統(tǒng)bus connection
*/
r = sd_bus_open_system(&bus);
if (r < 0)
{
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* 2. 安裝bus object洞焙,設(shè)置 bus路徑和接口,綁定bus 方法
*/
r = sd_bus_add_object_vtable(bus,
&slot,
"/net/poettering/Calculator", /* object path */
"net.poettering.Calculator", /* interface name */
calculator_vtable,
NULL);
if (r < 0)
{
fprintf(stderr, "Failed to issue method call: %s\n", strerror(-r));
goto finish;
}
/* 3. 設(shè)置服務(wù)名稱 供客戶查找和調(diào)用
*/
r = sd_bus_request_name(bus, "net.poettering.Calculator", 0);
if (r < 0)
{
fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r));
goto finish;
}
/*
4. 循環(huán)等待并處理bus請(qǐng)求事件
*/
while(1)
{
/* 處理bus請(qǐng)求 實(shí)際的流程在方法中處理
*/
r = sd_bus_process(bus, NULL);
if (r < 0)
{
fprintf(stderr, "Failed to process bus: %s\n", strerror(-r));
goto finish;
}
if (r > 0)
continue;
/* 等待下一個(gè)事件 */
r = sd_bus_wait(bus, (uint64_t) -1);
if (r < 0)
{
fprintf(stderr, "Failed to wait on bus: %s\n", strerror(-r));
goto finish;
}
}
finish:
/* 服務(wù)退出時(shí) 關(guān)閉獲取到的系統(tǒng)bus連接
*/
sd_bus_slot_unref(slot);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
</code></pre>
</details>
(1) 獲取系統(tǒng)dbus
使用sd_bus_open_system接口拿到系統(tǒng)dbus.
(2) 在系統(tǒng)dbus中安裝自定義的dbus object,設(shè)置D-Bus路徑拯啦,接口澡匪,方法
這立我們創(chuàng)建了一個(gè)method_multiply乘法方法,它的路徑為/net/poettering/Calculator褒链,接口為net.poettering.Calculator.
(3) 如果調(diào)用method_multiply方法唁情,這里需要進(jìn)行鑒權(quán).
①首先獲取請(qǐng)求dbus的進(jìn)程pid
sd-dbus中的獲取方法見 test.c get_sender_pid(sd_bus_message m)函數(shù).
②接著檢查進(jìn)程的路徑是否在白名單中.
根據(jù)進(jìn)程pid獲取進(jìn)程的路徑,具體實(shí)現(xiàn)參考 test.c check_path(char path)函數(shù).
檢查進(jìn)程path是否在白名單文件中甫匹,具體參考 test.c check_path(char* path)函數(shù).
若進(jìn)程路徑在白名單中荠瘪,則驗(yàn)證成功.
③polkit鑒權(quán)
調(diào)用org.freedesktop.PolicyKit1.Authority dbus接口夯巷,對(duì)進(jìn)程進(jìn)行認(rèn)證.
sender進(jìn)程如果擁有action的權(quán)限,認(rèn)證通過.
sender進(jìn)程如果沒有action的權(quán)限哀墓,將調(diào)用polkit彈框與用戶交互獲取授權(quán).
具體實(shí)現(xiàn)參考test.c check_pid(pid_t pid)函數(shù).
sender進(jìn)程如果具備權(quán)限或交互獲取到了權(quán)限趁餐,則驗(yàn)證成功.
④未通過驗(yàn)證的dbus請(qǐng)求,返回失敗.
(4)設(shè)置服務(wù)名稱 供客戶查找和調(diào)用
sd_bus_request_name(bus, "net.poettering.Calculator", 0)
(5)循環(huán)等待并處理bus請(qǐng)求事件
(6)服務(wù)退出時(shí) 關(guān)閉獲取到的系統(tǒng)bus連接
(7)編譯 test.c 使用make或
gcc test.c -o test `pkg-config --cflags --libs libsystemd`
(8)由于是系統(tǒng)D-Bus篮绰,所以程序需要使用管理員權(quán)限運(yùn)行
sudo ./test
(9)D-Feet工具檢查net.poettering.Calculator是否啟用
你也可以使用Qt Dbus后雷、dbus-glib或其他dbus庫創(chuàng)建自己的dbus接口.
4. 客戶端調(diào)用D-Bus接口
<details>
<summary>展開查看 代碼test_client.c</summary>
<pre>
include <stdio.h>
include <stdlib.h>
include <errno.h>
include <systemd/sd-bus.h>
int main()
{
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL;
sd_bus *bus = NULL;
const char *path;
int r;
</pre>
<code>
uint64_t result;
const char* s;
/* 1. 獲取一個(gè)系統(tǒng)bus連接
如果是用戶dbus接口,使用 sd_bus_open_user 函數(shù) 獲取 用戶dbus連接
*/
r = sd_bus_open_system(&bus);
if (r < 0)
{
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* 2. 調(diào)用dbus接口方法 */
r = sd_bus_call_method(
bus,
"net.poettering.Calculator", /* dubs 名稱*/
"/net/poettering/Calculator", /* dbus結(jié)構(gòu)體路徑 */
"net.poettering.Calculator", /* 接口名稱 */
"Multiply", /* 方法 */
&error, /* 返回錯(cuò)誤信息保存 */
&m, /* return message on success */
"xx",
/* 輸入?yún)?shù)類型 ’xx‘ 表示接下來的兩個(gè)int64參數(shù)
結(jié)構(gòu)體用 ()
disct用 {}
glibvariant 用v標(biāo)示
可以對(duì)照著d-feet的method 參數(shù)填寫
詳見 https://manpages.debian.org/testing/libsystemd-dev/sd_bus_message_read.3.en.html
*/
2,
3
);
if (r < 0)
{
fprintf(stderr, "Failed to issue method call: %s\n", error.message);
goto finish;
}
/* 3.讀取結(jié)果
*/
r = sd_bus_message_read(m, "xs", &result, &s);
if( r < 0)
{
printf("sd_bus_message_read failed.\n");
goto finish;
}
printf("sd_bus_message_read result = %s, %u.\n",s, result);
finish:
/*釋放dbus連接
*/
sd_bus_error_free(&error);
sd_bus_message_unref(m);
sd_bus_unref(bus);
return r;
}
</code>
</details>
運(yùn)行此客戶端程序吠各,將彈出polkit鑒權(quán)窗口臀突,只有當(dāng)輸入正確密碼方可執(zhí)行。
三贾漏、 dbus接口使用polkit鑒權(quán)應(yīng)用場景
當(dāng)提供的服務(wù)候学、接口涉及安全,對(duì)調(diào)用者身份有驗(yàn)證要求時(shí)推薦使用這種方式.
以系統(tǒng)dbus中進(jìn)行白名單檢查纵散、polkit鑒權(quán)的方式提供服務(wù)梳码,保證了安全性.
調(diào)用者使用接口,不需要再單獨(dú)進(jìn)行鑒權(quán)等安全認(rèn)證操作.
The new sd-bus API of systemd
polkit Reference Manual
Creating a D-Bus Service with dbus-python and Polkit Authentication
sd_bus_call_method Examples
sd-bus