网站主体负责人查询,球场 技术支持 东莞网站建设,无极城市在线招聘,seo诊断方案问题由来
客户需求计划列入支持第三方帐号系统#xff0c;包括Web账号。需求来源是用户想要用它们的帐号直接登录Linux Deepin操作系统。一个失败的实现方案是用户以较小的成本改造帐号管理系统发布HTTP服务#xff0c;我们开发一个PAM模块与Web服务器交互#xff0c;数据格…问题由来
客户需求计划列入支持第三方帐号系统包括Web账号。需求来源是用户想要用它们的帐号直接登录Linux Deepin操作系统。一个失败的实现方案是用户以较小的成本改造帐号管理系统发布HTTP服务我们开发一个PAM模块与Web服务器交互数据格式化采用JSON。结果遇到su提示帐号不存在的问题。在Linux Deepin系统登录界面、通过技术手段进入桌面后发现锁屏界面无法解锁等诸多问题。通过程序验证和su源代码分析验证此方案最大的局限性是su在识别到用户的信息之前不会执行PAM模块。此方案只能用程序调用pam_authenticate触发PAM模块的执行。
Linux Name Service Switch
Linux NSS (Name Service Switch) 是一种在 Linux 系统中实现域名解析用户认证和授权等功能的模块化系统。它提供了一种灵活的方式来配置系统如何查找用户组密码和其他网络资源的信息。
NSS 的核心概念是所谓的 “Switch”这是一组可插入的模块它们根据一定的顺序来处理不同类型的请求。用户可以通过编辑 /etc/nsswitch.conf 文件来配置这些模块的加载顺序以达到满足自己特定需求的目的。例如您可以通过 NSS 配置更改系统的验证源以便使用 LDAP 或 Kerberos 等网络身份验证服务而不是使用本地 /etc/passwd 文件。
Linux NSS 可以支持多种不同的数据源包括本地文件、NIS、LDAP、Kerberos 等。这些模块还可以针对特定服务或应用程序进行定制化以实现更高效的查询和更安全的身份验证和授权。使用 Linux NSS系统管理员可以更灵活地管理 Linux 系统满足不同用户对系统资源和服务的需求。
本文将详细讲解开发过程实现Web账号登录Linux桌面系统。
关于getpwnam函数
NSS的作用是识别身份信息。传统的用户名和密码验证方式身份信息是一个字符串。NSS通过调用这个方法寻找这个字符串对应到系统中的用户获取它关联的UID和GID从而对它进行管理。Oracle官方文档原话
The name service switch is a file named nsswitch.conf. It controls how a client machine or application obtains network information. It is used by client applications that call any of the getXbyY() interfaces such as the following.gethostbyname()
getpwuid()
getpwnam()
getipnodebyname()getpwnam不是直接读取读取/etc/passwd或者/etc/shadow文本文件它取决于nsswitch.conf中passwd这一行的配置。对于Linux Deepin操作系统如果配置了files或者compat那么最终由libnss_files-2.28.so或者libnss_compat-2.28.so读取这几个配置文件程序从getpwnam运行到了_nss_files_getpwnam_r或者_nss_compat_getpwnam_r。
NSS释义
这里的NSS指的是Linux Name Service Switch不是Linux Network Security Service。两者对Linux都很重要资料都很稀缺。我们可以理解为名字解析服务它实现把外部输入的用户信息与系统中的用户信息关联。它按照/etc/nsswitch.conf指定的顺序逐个模块调用如果找到了就立即返回libcsu根据返回的用户信息启动PAM流程进行身份验证。
nsswitch配置文件格式
一行写一种类型的配置每行以类型名称加冒开头以NSS模块名称列表结尾多个NSS模块名称以空格隔开。NSS总共支持16种类型的配置。常见的有passwd、group、shadow、gshadow、hosts、networks、protocols、rpc、netgroup等等。其中passwd类别实现用户身份识别。模块与类别不是一对一的关系libc给每个类型别都定义了一套接口这个接口函数名通常以_r结尾比如getpwuid_r。NSS模块的实现是动态链接库文件名必须以libnss_为前缀以.so或.so.2为后缀中间部分就是模块名称比如libnss_mjaw.so它的模块名称就是mjaw。因它实现了passwd、group、shadow、gshadow、hosts、networks、rpc、protocols这些类别的接口所以配置文件中这些类型对应的行都可以加上mjaw这个模块名称。
开发
引用Petzold Charles先生的一句名言 Do not call me, I will call you 。NSS程序设计须深刻理解这句话下面的每一个函数都不是开发者要调用的函数而是系统用户态边界一定会调用你的函数。
NSS模块要求开发者采用C语言Qt代码无法在PAM和NSS模块的上下文环境中运行。在PAM和NSS模块编程中采用C与C混合编程的方式对于Qt来说有很多问题需要解决其它的框架暂未尝试。对于HTTP可以用cURL。对于Json解析可采用cJSON对于密码加密可采用mHash。
认证数据的存储
NSS模块须自行管理认证数据。因此首先建立一个链表.
重要的头文件
#include passwd_list.h
#include malloc.h
#include pwd.h
#include string.h#include cjson/cJSON.h
#include mhash.h
#include curl/curl.h创建链表
MJAW_INTERNAL pmjaw_passwd_list_t passwd_create()
{pmjaw_passwd_list_t node (pmjaw_passwd_list_t)calloc(sizeof(mjaw_passwd_list_t), 1);return node;
}创建认证账号
MJAW_INTERNAL passwd_t passwd_create2()
{passwd_t pwd (passwd_t)calloc(sizeof(struct passwd), 1);pwd-pw_name (char *)calloc(UINT8_MAX, 1);pwd-pw_gecos (char *)calloc(UINT8_MAX, 1);pwd-pw_shell (char *)calloc(UINT8_MAX, 1);pwd-pw_dir (char *)calloc(UINT8_MAX, 1);pwd-pw_passwd (char *)calloc(UINT8_MAX, 1);
}释放链表
MJAW_INTERNAL void passwd_free(pmjaw_passwd_list_t head)
{pmjaw_passwd_list_t each head-next;while (each) {pmjaw_passwd_list_t del each;each each-next;passwd_free2(del-data);free(del);}free(head);
}释放认证账号
MJAW_INTERNAL void passwd_free2(passwd_t data)
{free(data-pw_name);free(data-pw_gecos);free(data-pw_shell);free(data-pw_dir);free(data-pw_passwd);free(data);
}
获取指定索引位置的用户对象
/*** brief 获取指定索引位置的用户对象** param head* param nindex* return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_at(pmjaw_passwd_list_t head, int nindex)
{if (nindex passwd_size(head)) {return NULL;}pmjaw_passwd_list_t each head-next;int neach 0;while (each neach nindex) {each each-next;neach;}return each-data;
}根据用户名查找用户对象
/*** brief 根据用户名查找用户对象** param head* param username* return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_find_id_by_username(pmjaw_passwd_list_t head, const char *username)
{pmjaw_passwd_list_t find head-next;while (find) {if (strcmp(find-data-pw_name, username) 0) {return find-data;}}return NULL;
}根据用户ID查找用户对象
/*** brief 根据用户ID查找用户对象** param head* param uid* return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_find_username_by_id(pmjaw_passwd_list_t head, uint32_t uid)
{pmjaw_passwd_list_t find head-next;while (find) {if (find-data-pw_uid uid) {return find-data;}}return NULL;
}
获取指定用户ID的索引
/*** brief 获取指定用户ID的索引** param head* param uid* return MJAW_INTERNAL*/
MJAW_INTERNAL int passwd_indexof(pmjaw_passwd_list_t head, uint32_t uid)
{pmjaw_passwd_list_t each head-next;int nindex 0;int bfind 0;while (each) {if (each-data-pw_uid uid) {bfind 1;break;}nindex;}if (bfind) {return nindex;}return -1;
}在队列末尾增加一个用户对象 /*** brief 在队列末尾一个用户对象** param head* param data* return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_append(pmjaw_passwd_list_t head, passwd_t data)
{// mjaw_log0(__FILE__, __LINE__, __func__, enter);pmjaw_passwd_list_t end head;while (end-next) {end end-next;}pmjaw_passwd_list_t node passwd_create();node-data passwd_create2();node-data-pw_uid data-pw_uid;node-data-pw_gid data-pw_gid;strncpy(node-data-pw_name, data-pw_name, UINT8_MAX);strncpy(node-data-pw_gecos, data-pw_gecos, UINT8_MAX);strncpy(node-data-pw_shell, data-pw_shell, UINT8_MAX);strncpy(node-data-pw_dir, data-pw_dir, UINT8_MAX);strncpy(node-data-pw_passwd, data-pw_passwd, UINT8_MAX);node-previous end;end-next node;// mjaw_log0(__FILE__, __LINE__, __func__, leave);
}
移除指定索引位置的用户对象
/*** brief 移除指定索引位置的用户对象** param head* param nindex* return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_remove(pmjaw_passwd_list_t head, int nindex)
{if (nindex passwd_size(head)) {return;}pmjaw_passwd_list_t remove head-next;int neach 0;while (remove neach nindex) {remove remove-next;neach;}remove-next-previous remove-previous;remove-previous-next remove-next;passwd_free2(remove-data);free(remove);
}
获取队列大小
/*** brief 获取队列大小HEAD本身不参与计算** param head* return MJAW_INTERNAL*/
MJAW_INTERNAL uint32_t passwd_size(pmjaw_passwd_list_t head)
{uint32_t nsize 0;while (head head-next) {nsize;}return nsize;
}
账号克隆
/*** brief 用户信息复制** param from* param to* return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_copy(const passwd_t from, passwd_t to)
{memset(to, 0, sizeof(struct passwd));to-pw_name (char *)calloc(UINT8_MAX, 1);to-pw_gecos (char *)calloc(UINT8_MAX, 1);to-pw_shell (char *)calloc(UINT8_MAX, 1);to-pw_dir (char *)calloc(UINT8_MAX, 1);to-pw_passwd (char *)calloc(UINT8_MAX, 1);to-pw_name from-pw_name;to-pw_passwd from-pw_passwd;to-pw_uid from-pw_uid;to-pw_gid from-pw_gid;to-pw_gecos from-pw_gecos;to-pw_dir from-pw_dir;to-pw_shell from-pw_shell;strncpy(to-pw_name, from-pw_name, UINT8_MAX);strncpy(to-pw_gecos, from-pw_gecos, UINT8_MAX);strncpy(to-pw_shell, from-pw_shell, UINT8_MAX);strncpy(to-pw_dir, from-pw_dir, UINT8_MAX);strncpy(to-pw_passwd, from-pw_passwd, UINT8_MAX);
}Web互通
Web账号登录Linux桌面的通讯环节假定存在一个RESTful服务http://127.0.0.1:1081提供json数据接口。
cURL发起请求
兼顾处理网络错误。
/*** brief curl回调收集数据** param buffer* param size* param nmemb* param user_p* return size_t*/
static size_t mjaw_curl_login_data(void *buffer, size_t size, size_t nmemb, void *user_p)
{char *wrapper (char *)user_p;// mjaw_logv(__FILE__, __LINE__, __func__, buffer size: %d, size: %d, nmemb: %d, strlen((char *)buffer), size, nmemb);strncat(wrapper, buffer, nmemb);return nmemb;
}static bool network_has_error(cJSON **httpContent)
{//执行网络请求查询所有用户char buffer[UINT16_MAX] {0};CURL *pcurl curl_easy_init();curl_easy_setopt(pcurl, CURLOPT_URL, http://127.0.0.1:1081/users);curl_easy_setopt(pcurl, CURLOPT_TIMEOUT, 3);curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, mjaw_curl_login_data);curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, buffer);CURLcode icode curl_easy_perform(pcurl);//如果网络请求失败if (icode ! CURLE_OK) {mjaw_log1i(__FILE__, __LINE__, __func__, nss_cw can not access website. curl error code: %d., icode);return true;}//把HTTP返回转换成json对象cJSON *res cJSON_Parse(buffer);//json对象必须是数组if (!cJSON_IsArray(res)) {// mjaw_logv(__FILE__, __LINE__, __func__, parse http response error: %s, buffer);cJSON_Delete(res);return true;}*httpContent res;return false;
}cJSON库解析账号信息
/*** brief 从指定的配置读取Web服务器上的用户列表并保存到列表** param head* return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_init(pmjaw_passwd_list_t head)
{cJSON *res NULL;if (network_has_error(res)) {return;}int iSize cJSON_GetArraySize(res);for (int i 0; i iSize; i) {cJSON *obj cJSON_GetArrayItem(res, i);bool badmin cJSON_IsTrue(cJSON_GetObjectItem(obj, admin));passwd_t data passwd_create2();data-pw_uid (uint32_t)cJSON_GetObjectItem(obj, uid)-valueint;if (badmin) {data-pw_gid 0;} else {data-pw_gid data-pw_uid;}strncpy(data-pw_name, cJSON_GetObjectItem(obj, username)-valuestring, UINT8_MAX);// strcpy(data-pw_shell, /bin/bash);strcpy(data-pw_dir, /home/http/);strncat(data-pw_dir, data-pw_name, UINT8_MAX);strncpy(data-pw_gecos, data-pw_name, UINT8_MAX);//这里是约定strcpy(data-pw_passwd, !);passwd_append(head, data);}cJSON_Delete(res);
}
NSS核心身份识别
NSS的概念可以推广开来应用到人脸、指纹等生物特征识别。
重要的头文件
#ifdef __cplusplus
#include iostream
#include string
#else
#include stdio.h
#include string.h
#endif//c run time
#include stdint.h
#include stdlib.h
#include malloc.h
#include time.h
#include stdarg.h//linux
#include unistd.h
#include nss.h
#include grp.h
#include pwd.h
#include dlfcn.h
#include sys/time.h
#include sys/types.h
#include syslog.h//external reference
#include cjson/cJSON.h
#include mhash.h
#include curl/curl.h#include nss_passwd.h
#include common.h
下文以NSS接口名称为标题举例说明NSS接口的实现过程。
getpwnam //all over record
static int i_record_index 0;
static pmjaw_passwd_list_t head_passwd NULL;static enum nss_status local_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errop)
{// dlopen(/usr/lib/%s-linux-gnu/libnss_files.so.2,)return NSS_STATUS_SUCCESS;
}/*** brief 解析用户对象给用户赋权** param name* param result* param buffer* param buflen* param errop* return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errop)
{mjaw_log0(__FILE__, __LINE__, __func__, enter);if (!head_passwd) {head_passwd passwd_create();passwd_init(head_passwd);}//打开系统日志// openlog(nss_cw, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);//buffer重置为空memset(buffer, 0, buflen);//result结构体本身的内存由调用者传入if (!result) {mjaw_log0(__FILE__, __LINE__, __func__, result can not be empty.);return NSS_STATUS_NOTFOUND;}//输出开始处理的日志mjaw_logv(__FILE__, __LINE__, __func__, begin receive name: %s, uid: %d, gid: %d, name, result-pw_uid, result-pw_gid);//获取web对用户id和组id的定义passwd_t user passwd_find_id_by_username(head_passwd, name);if (user NULL) {passwd_free(head_passwd);return NSS_STATUS_NOTFOUND;}passwd_copy(user, result);//输出完成处理的日志mjaw_logv(__FILE__, __LINE__, __func__, end : %s, uid: %d, gid: %d, name, result-pw_uid, result-pw_gid);//按约定返回0非零被su判定用户不存在*errop 0;passwd_free(head_passwd);//关闭系统日志// closelog();return NSS_STATUS_SUCCESS;
}
setpwent
这个接口的含义是系统通知NSS模块清理内存并准备一个新的账号列表。此时是Linux Deepin本地同步远端Web账号的机会。
/*** brief init data list cache** return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_setpwent()
{mjaw_log0(__FILE__, __LINE__, __func__, enter);if (!head_passwd || !passwd_size(head_passwd)) {head_passwd passwd_create();passwd_init(head_passwd);}mjaw_logv(__FILE__, __LINE__, __func__, data size: %d, passwd_size(head_passwd));i_record_index 0;return NSS_STATUS_SUCCESS;
}MJAW_EXPORT nss_status _nss_mjaw_init()
{mjaw_log0(__FILE__, __LINE__, __func__, end);return NSS_STATUS_SUCCESS;
}endpwent
这个接口的含义是系统通知NSS模块清理内存结束了。
/*** brief clean data list cache** return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_endpwent()
{mjaw_log0(__FILE__, __LINE__, __func__, enter);if (head_passwd passwd_size(head_passwd) 0)passwd_free(head_passwd);return NSS_STATUS_SUCCESS;
}### getpwent
这个接口一般紧接着setpwent调用用于获取账号的详细信息。此接口返回NSS_NOT_FOUND后系统会立即调用endpwent接口。
/*** brief iterator data list** param __resultbuf* return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_getpwent_r(struct passwd *resultbuf, char *buffer, size_t buflen, int *errop)
{if (!head_passwd)return NSS_STATUS_NOTFOUND;// memset(buffer, 0, buflen);passwd_t user passwd_at(head_passwd, i_record_index);i_record_index;if (user NULL) {return NSS_STATUS_NOTFOUND;}mjaw_logv(__FILE__, __LINE__, __func__, enter. uid: %d, cursor: %d, user-pw_uid, i_record_index);passwd_copy(user, resultbuf);*errop 0;return NSS_STATUS_SUCCESS;
}getpwuid
此接口在用户鉴权时调用调用频率远高于getpwnam。用户进入桌面后每次鉴权都会调用这个接口即使用户选择使用其它账号鉴权此接口也先于getpwnam调用。
MJAW_EXPORT nss_status _nss_mjaw_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errop)
{mjaw_log0(__FILE__, __LINE__, __func__, enter);if (!head_passwd) {head_passwd passwd_create();passwd_init(head_passwd);}//打开系统日志// openlog(nss_cw, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);//buffer重置为空memset(buffer, 0, buflen);//result结构体本身的内存由调用者传入if (!result) {mjaw_log0(__FILE__, __LINE__, __func__, result can not be empty.);return NSS_STATUS_NOTFOUND;}//输出开始处理的日志mjaw_logv(__FILE__, __LINE__, __func__, begin original uid: %d, result buffer uid: %d, gid: %d, uid, result-pw_uid, result-pw_gid);//获取web对用户id和组id的定义bool bok passwd_find_username_by_id(head_passwd, uid);if (!bok) {passwd_free(head_passwd);return NSS_STATUS_NOTFOUND;}passwd_copy(result, result);//输出完成处理的日志mjaw_logv(__FILE__, __LINE__, __func__, end original uid: %d, result buffer uid: %d, gid: %d, uid, result-pw_uid, result-pw_gid);//按约定返回0非零被su判定用户不存在*errop 0;passwd_free(head_passwd);//关闭系统日志// closelog();return NSS_STATUS_SUCCESS;
}
调试
因NSS模块很接近系统内核底层稍有不慎开机或者重启系统黑屏变成了这样
原因是Linux系统内核的1号进程崩溃了。有关Linux 1号进程的资料可查阅https://man7.org/linux/man-pages/man1/init.1.html。因为root这个字符串与uid为0的这个系统用户身份没对应上程序的上下文环境没有相应的权限功能自然无法正常运转虽然给人的感觉已经是root身份。但开发过程中总是有所难免因此有些必要的调试设置在这里说明一下。
开始调试前多打开几个终端并登录开 如果环境已被NSS破坏可直接在这个终端上操作。但记住不要切换用户这些操作都会失败。也不会已经损坏的时候才开终端因为这个时候大部分图形程序都已经无法启动了。修改grub引导选项 这里建议修改/boot/grub/grub.cfg在所有的linux命令行后面加上参数systemd.debug_shell1。这样还可以直接切换到TTY9免登录。
其它事项
如何伪造信息登录桌面
调用useradd增加一个同名本地用户设置密码与否都不重要PAM模块可以无视本地存储的密码只要让系统的验证流程进入到自行开发的的PAM模块在PAM中对帐号进行认证即可。PAM允许开发者自由操作比如可以不验证登录凭据的正确性而直接进行进入系统。lightdm的自动登录就是这样实现的。
TTY登录成功图形界面登录黑屏
图形界面涉及到NSS模块与FreeDesktop的AcountsService交互问题此问题影响到LightDM并最终导致其崩溃。关于LightDM问题的处理我将继续编写文章说明问题的根本原因以及解决办法。
参考文档
https://docs.oracle.com/cd/E19683-01/806-4077/6jd6blbbb/index.htmlhttp://linux-pam.org/Linux-PAM-html/mwg-expected-of-module-auth.htmlhttps://www.openmjaw.org/doc/admin24/quickstart.htmlhttps://tools.ietf.org/html/rfc4511http://stefanfrings.de/qtwebapp/index-en.htmlhttps://docs.microsoft.com/en-us/previous-versions/windows/desktop/mjaw/lightweight-directory-access-protocol-mjaw-api 作者岬淢箫声日期2020年12月15日版本1.0博客http://caowei.blog.csdn.net创作不易请大家多多支持关注、转发。转发请注明来源。