PAM从入门到精通(二十二)

news2025/1/12 8:57:31

接前一篇文章:PAM从入门到精通(二十一)

本文参考:

《The Linux-PAM Application Developers' Guide》

先再来重温一下PAM系统架构:

更加形象的形式:

七、PAM-API各函数源码详解

前边的文章讲解了各PAM-API函数以及总体流程,但是也只是从接口层面介绍的,并没有深入到代码层面。从本篇文章开始,将对于各个接口函数从源码级进行讲解,以使大家不但知其然,还要知其所以然。

1. pam_start函数

上回讲到_pam_start_internal函数的第二部分,本文继续往下进行讲解。为了便于理解,再次贴出_pam_start_internal函数源码。在libpam/pam_start.c中,如下所示:

static int _pam_start_internal (
    const char *service_name,
    const char *user,
    const struct pam_conv *pam_conversation,
    const char *confdir,
    pam_handle_t **pamh)
{
    D(("called pam_start: [%s] [%s] [%p] [%p]"
       ,service_name, user, pam_conversation, pamh));
 
    if (pamh == NULL) {
	pam_syslog(NULL, LOG_CRIT,
		   "pam_start: invalid argument: pamh == NULL");
	return (PAM_SYSTEM_ERR);
    }
 
    if (service_name == NULL) {
	pam_syslog(NULL, LOG_CRIT,
		   "pam_start: invalid argument: service == NULL");
	return (PAM_SYSTEM_ERR);
    }
 
    if (pam_conversation == NULL) {
	pam_syslog(NULL, LOG_CRIT,
		   "pam_start: invalid argument: conv == NULL");
	return (PAM_SYSTEM_ERR);
    }
 
    if ((*pamh = calloc(1, sizeof(**pamh))) == NULL) {
	pam_syslog(NULL, LOG_CRIT, "pam_start: calloc failed for *pamh");
	return (PAM_BUF_ERR);
    }
 
    /* All service names should be files below /etc/pam.d and nothing
       else. Forbid paths. */
    if (strrchr(service_name, '/') != NULL)
	service_name = strrchr(service_name, '/') + 1;
 
    /* Mark the caller as the application - permission to do certain
       things is limited to a module or an application */
 
    __PAM_TO_APP(*pamh);
 
    if (((*pamh)->service_name = _pam_strdup(service_name)) == NULL) {
	pam_syslog(*pamh, LOG_CRIT,
		   "pam_start: _pam_strdup failed for service name");
	_pam_drop(*pamh);
	return (PAM_BUF_ERR);
    } else {
	char *tmp;
 
	for (tmp=(*pamh)->service_name; *tmp; ++tmp)
	    *tmp = tolower(*tmp);                   /* require lower case */
    }
 
    if (user) {
	if (((*pamh)->user = _pam_strdup(user)) == NULL) {
	    pam_syslog(*pamh, LOG_CRIT,
		       "pam_start: _pam_strdup failed for user");
	    _pam_drop((*pamh)->service_name);
	    _pam_drop(*pamh);
	    return (PAM_BUF_ERR);
	}
    } else
	(*pamh)->user = NULL;
 
    if (confdir) {
	if (((*pamh)->confdir = _pam_strdup(confdir)) == NULL) {
	    pam_syslog(*pamh, LOG_CRIT,
		       "pam_start: _pam_strdup failed for confdir");
	    _pam_drop((*pamh)->service_name);
	    _pam_drop((*pamh)->user);
	    _pam_drop(*pamh);
	    return (PAM_BUF_ERR);
	}
    } else
	(*pamh)->confdir = NULL;
 
    (*pamh)->tty = NULL;
    (*pamh)->prompt = NULL;              /* prompt for pam_get_user() */
    (*pamh)->ruser = NULL;
    (*pamh)->rhost = NULL;
    (*pamh)->authtok = NULL;
    (*pamh)->oldauthtok = NULL;
    (*pamh)->fail_delay.delay_fn_ptr = NULL;
    (*pamh)->former.choice = PAM_NOT_STACKED;
    (*pamh)->former.substates = NULL;
#ifdef HAVE_LIBAUDIT
    (*pamh)->audit_state = 0;
#endif
    (*pamh)->xdisplay = NULL;
    (*pamh)->authtok_type = NULL;
    (*pamh)->authtok_verified = 0;
    memset (&((*pamh)->xauth), 0, sizeof ((*pamh)->xauth));
 
    if (((*pamh)->pam_conversation = (struct pam_conv *)
	  malloc(sizeof(struct pam_conv))) == NULL) {
	pam_syslog(*pamh, LOG_CRIT, "pam_start: malloc failed for pam_conv");
	_pam_drop((*pamh)->service_name);
	_pam_drop((*pamh)->user);
	_pam_drop((*pamh)->confdir);
	_pam_drop(*pamh);
	return (PAM_BUF_ERR);
    } else {
	memcpy((*pamh)->pam_conversation, pam_conversation,
	       sizeof(struct pam_conv));
    }
 
    (*pamh)->data = NULL;
    if ( _pam_make_env(*pamh) != PAM_SUCCESS ) {
	pam_syslog(*pamh,LOG_ERR,"pam_start: failed to initialize environment");
	_pam_drop((*pamh)->pam_conversation);
	_pam_drop((*pamh)->service_name);
	_pam_drop((*pamh)->user);
	_pam_drop((*pamh)->confdir);
	_pam_drop(*pamh);
	return PAM_ABORT;
    }
 
    _pam_reset_timer(*pamh);         /* initialize timer support */
 
    _pam_start_handlers(*pamh);                   /* cannot fail */
 
    /* According to the SunOS man pages, loading modules and resolving
     * symbols happens on the first call from the application. */
 
    if ( _pam_init_handlers(*pamh) != PAM_SUCCESS ) {
	pam_syslog(*pamh, LOG_ERR, "pam_start: failed to initialize handlers");
	_pam_drop_env(*pamh);                 /* purge the environment */
	_pam_drop((*pamh)->pam_conversation);
	_pam_drop((*pamh)->service_name);
	_pam_drop((*pamh)->user);
	_pam_drop((*pamh)->confdir);
	_pam_drop(*pamh);
	return PAM_ABORT;
    }
 
    D(("exiting pam_start successfully"));
 
    return PAM_SUCCESS;
}

接下来来到以下代码片段:

    if (((*pamh)->service_name = _pam_strdup(service_name)) == NULL) {
	pam_syslog(*pamh, LOG_CRIT,
		   "pam_start: _pam_strdup failed for service name");
	_pam_drop(*pamh);
	return (PAM_BUF_ERR);
    } else {
	char *tmp;

	for (tmp=(*pamh)->service_name; *tmp; ++tmp)
	    *tmp = tolower(*tmp);                   /* require lower case */
    }

_pam_strdum函数在libpam/pam_misc.c中,代码如下:

/*
 * Safe duplication of character strings. "Paranoid"; don't leave
 * evidence of old token around for later stack analysis.
 */

char *_pam_strdup(const char *x)
{
     register char *new=NULL;

     if (x != NULL) {
	  register int len;

	  len = strlen (x) + 1;  /* length of string including NUL */
	  if ((new = malloc(len)) == NULL) {
	       len = 0;
	       pam_syslog(NULL, LOG_CRIT, "_pam_strdup: failed to get memory");
	  } else {
	       strcpy (new, x);
	  }
	  x = NULL;
     }

     return new;                 /* return the duplicate or NULL on error */
}

综合这两段代码,实际的作用如下:

将经过处理后保留'/'之后的service_name,以实参的形式传入_pam_strdup函数。_pam_strdup函数一上来先判断x是否为空,只有不为空的情况下才进行进一步处理,否则直接返回NULL。

接下来看不为空时的处理:len得到的是包含字符串结束符'\0'的长度;之后分配len大小的内存空间,使new指向这段空间。如果分配失败,则将len置为0,并且给出错误提示信息"_pam_strdup: failed to get memory",并返回NULL;如果分配成功,则将x拷贝给new,即拷贝了一份service_name,并返回新分配的new指向的这段内存。

回到上边的调用代码片段:

    if (((*pamh)->service_name = _pam_strdup(service_name)) == NULL) {
	pam_syslog(*pamh, LOG_CRIT,
		   "pam_start: _pam_strdup failed for service name");
	_pam_drop(*pamh);
	return (PAM_BUF_ERR);
    } else {
	char *tmp;

	for (tmp=(*pamh)->service_name; *tmp; ++tmp)
	    *tmp = tolower(*tmp);                   /* require lower case */
    }

如果_pam_strdup函数返回了NULL,则给出错误信息"pam_start: _pam_strdup failed for service name",并且调用_pam_drop函数,之后返回PM_BUF_ERR;

如果_pam_strdup函数返回非空,则说明已经复制service_name成功,此时(*pamh)->service_name的值就是复制后的service_name。接下来所做的就是将(*pamh)->service_name即复制后的字符串的各个字符都转换为小写。

再来看一下刚才没有讲的_pam_drop。_pam_drop是一个宏,在libpam/include/security/_pam_macros.h中定义,如下:

/*
 * Don't just free it, forget it too.
 */

#define _pam_drop(X) \
do {                 \
    if (X) {         \
        free(X);     \
        X=NULL;      \
    }                \
} while (0)

_pam_drop很简单,实际上就是free函数的封装,功能一目了然。

再来看一下PAM_BUF_ERR。PAM_BUF_ERR在libpam/include/security/_pam_types.h中定义,代码如下:

/* ----------------- The Linux-PAM return values ------------------ */

#define PAM_SUCCESS 0		/* Successful function return */
#define PAM_OPEN_ERR 1		/* dlopen() failure when dynamically */
				/* loading a service module */
#define PAM_SYMBOL_ERR 2	/* Symbol not found */
#define PAM_SERVICE_ERR 3	/* Error in service module */
#define PAM_SYSTEM_ERR 4	/* System error */
#define PAM_BUF_ERR 5		/* Memory buffer error */
#define PAM_PERM_DENIED 6	/* Permission denied */
#define PAM_AUTH_ERR 7		/* Authentication failure */
#define PAM_CRED_INSUFFICIENT 8	/* Can not access authentication data */
				/* due to insufficient credentials */
#define PAM_AUTHINFO_UNAVAIL 9	/* Underlying authentication service */
				/* can not retrieve authentication */
				/* information  */
#define PAM_USER_UNKNOWN 10	/* User not known to the underlying */
				/* authentication module */
……

_pam_start_internal函数的其余部分将在后续文章中继续讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1127973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何复制禁止复制的内容

今天找到一段代码,但是复制时页面提示“这个是VIP会员才有的权限”。我该怎么复制呢。 现在的平台大都是用钱说话,以便响应知识付费的主张。对错我就不说了,我认为既然我有权利看到代码,当然也有权把他复制下来。这并不涉及侵权。…

DNS、ICMP和NAT

DNS、ICMP和NAT 文章目录 DNS、ICMP和NATDNS是什么域名系统的名字空间域名空间的层次结构域名的分配和管理顶级类别域名 DNS域名解析过程递归查询迭代查询 高速缓存 ICMPICMP的定位ICMP协议的功能 ICMP的报文格式ping命令traceroute命令 NATNAT技术背景NAT IP转换过程NAPTNAT的…

autohotkey v1

autohotkey v1 文档 https://www.autohotkey.com/docs/v1/ 编辑器 https://www.autohotkey.com/scite4ahk/ https://www.autohotkey.com/

基于Python实现手写文字识别

基于Python实现手写文字识别,对学生日常作业及考试试卷中的手写内容进行自动识别,实现学生作业、考卷的线上批阅及教学数据的自动分析,提升教职人员工作效率,促进教学管理的数字化和智能化。 目录 引言背景介绍目标和意义 手写文字…

解决chrome浏览器netWork响应数据中文乱码的问题

项目中遇到返回值出现如下图所示情况: 开发起来很麻烦,可以通过添加Source code扩展程序解决。 具体操作: 1、下载 地址: https://github.com/jinliming2/Chrome-Charset/releases 或者:https://download.csdn.net/do…

循环队列c语言版

一、循环队列结构体 typedef int QueueDataType; #define CQ_MAX_SIZE 10typedef struct CircularQueue {QueueDataType data[CQ_MAX_SIZE];/**标记队列首*/QueueDataType head;/**标记队列尾部*/QueueDataType rear;} CircularQueue; 二、循环队列操作函数声明 /**创建队…

项目部署Linux一般步骤

1、最小化安装centos7-环境准备 安装epel-release 安装epel-release,因为有些rpm包在官方库中找不到。前提是保证可以联网 yum install -y epel-release 修改IP net-tools net-tool:工具包集合,包含ifconfig等命令 yum install -y net-…

【技能树笔记】网络篇——练习题解析(九)

目录 前言 一、OSPF双栈 1.1 OSPFv3 LSA 1.2 OSPFv3 二、ISIS双栈 2.1 ISISv6 2.2 ISIS高级特性 三、BGP双栈 四、PIM双栈 总结 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filot…

SpringDoc API文档工具集成SpringBoot - Swagger3

1、引言 之前在Spring Boot项目中一直使用的是SpringFox提供的Swagger库,发现已经超过3年没出新版本了!SpringDoc是一款可以结合Spring Boot使用的API文档生成工具,基于OpenAPI 3,是一款更好用的Swagger库!值得一提的是…

Playwright 配置文件/运行命令/测试标记

Playwright 配置文件/运行命令/测试标记 一、运行说明 1. 运行命令 // 运行测试集,无界面模式 npx playwright test// 运行测试集,界面模式 npx playwright test --ui// 运行单个测试文件 npx playwright test test_file.spec.ts// 以调试模式运行测试…

选购采购管理软件,首先考虑这5个功能

虽然采购已经达到了数字化的临界点,但企业在接受新的解决方案时却犹豫不决。德勤一份全球首席采购官调查显示,只有 18% 的组织制定了数字化采购战略。 自动化采购任务和优化采购到付款周期可以为企业节省大量金钱和时间。然而,通过过时的采购…

Maven依赖scope为system级别部署时Jar包缺少解决

问题 在开发springboot项目时,maven引入本地第三方jar包,在idea中运行正常。打成jar部署后报找不到类 错误如下: ERROR SpringApplication.reportFailure(834) | Application run failed org.springframework.beans.factory.UnsatisfiedDep…

【tio-websocket】8、服务配置与维护—TioConfig

场景 我们在写 TCP Server 时,都会先选好一个端口以监听客户端连接,再创建N组线程池来执行相关的任务,譬如发送消息、解码数据包、处理数据包等任务,还要维护客户端连接的各种数据,为了和业务互动,还要把这些客户端连接和各种业务数据绑定起来,譬如把某个客户端绑定到一…

仪表盘自定义标题和数值样式

仪表盘自定义标题和数值样式 fn() var myEcharts; function fn(v) {var chartDom document.getElementById(myEcharts);myEcharts&& myEcharts.dispose();myEcharts echarts.init(chartDom, walden);var option;option {series: [{type: gauge,radius: 85%,center: […

activiti7 报错Couldn‘t resolve collection expression nor variable reference

解决方法 nacos添加配置 spring:activiti: serializePOJOsInVariablesToJson: false 截图如下: 分析过程 Couldnt resolve collection expression nor variable reference报错分析是“无法解析集合表达式或变量引用” 刚开始我一直测试…

激光雷达数据为例滤波器

可以通过传感器获取障碍物的位置、速度; 但是任何测量结果都是有误差的。 因此需要在传感器测量结果的基础上, 进行跟踪,以此来保证障碍物的位置、速度等信息不会发生突变。 所谓的跟踪就是通过运动模型来递推障碍物的位置、速度等信息。 最经典的跟踪算法是卡尔曼滤波器。…

用户社交信息交互卡片

效果展示 CSS 知识点 CSS 基础知识回顾transition-delay 属性的运用 整体页面布局实现 <div class"card"><div class"user"><div class"img_box"><img src"bg.jpg" /></div><div class"cont…

积分球检测水质的原理是什么?

积分球可以用于检测水质&#xff0c;主要通过测量水体中物质的吸收光谱来实现。具体来说&#xff0c;光路经过积分球内腔&#xff0c;当水样经过球体时&#xff0c;水体中的物质会吸收一定波长的光&#xff0c;从而改变球体的透射光强。通过测量球体透射光强的改变&#xff0c;…

UE 交互草实现 不通过RT与距离场的方式

通过计算世界场景位置与玩家位置的距离&#xff0c;如果距离大于等于DistanceEffect则不做操作&#xff0c;若小于DistanceEffect则DistanceEffect减去两者之间距离除以并除以DistanceEffect&#xff0c;为什么有这个操作呢&#xff1f; 因为我们要做在DistanceEffect距离内&a…

水质分析仪器升级新功能

水质分析仪器&#xff1a;是一种适用于水质多参数测试的便携式仪器。它具有7英寸的触摸彩色屏幕&#xff0c;用户可以通过触摸屏幕进行操作和查看测试结果。 该仪器主要用于测定COD&#xff0c;氨氮&#xff0c;总磷&#xff0c;总氮等常规水质指标&#xff0c;pH值、溶解氧、…