踩坑之路-用户Uid分配
问题背景
开启密码管理功能后,在多个控制台跑脚本反复创建用户并设置密码和删除用户,概率性出现用户设置密码时提示输入旧密码。
定位过程
GDB跟踪进程发现,当配置密码管理功能后,如果配置的用户的密码需要先判断是否修改的是自己的密码(根据UID判断,系统中创建设备用户时,会分配一个UID,该UID分配方式为从头开始查找第一个未使用的)。如果是修改自己的密码,为了增强安全性,防止修改其他用户的密码,需要输入老密码,在老密码验证成功后才能输入新密码。即当出现如下时序时,会出现此问题:
- 新增用户A,分配的用户UID为1,用户A登录设备,该登录的控制台中的环境变量存储的UID为1,此时将用户A删除,值为1的UID释放。
- 新增用户B,分配的用户UID为1,在A登录的控制台中修改用户B密码,此时有由于用户B的UID为1,需要先输入用户B的旧密码。
问题定位了,怎么解决这个问题呢?首先密码管理使能后的安全检查为对外的规格,不能修改。首先想到的是在判断是自己时增加条件,以增强是否是修改自己的密码,经过仔细考虑之后发现不可行,因为不管是用户名还是uid等等信息都可能是相同的。
后来考虑是否可以在删除时增加限制,当用户在线时不让删除用户的配置,由于设备用户中有用户在线数的数据,刚开始想通过这个数据是否为0进行限制,后来发现不可行,因为用户登录设备分为认证、授权、计费流程,这三个流程均可以配置是否走本地用户上线,而用户在线数只有计费走本地用户上线时才会增加,导致会有多中流程该计数不准,通过该计数判断,问题解决不了,还能有什么办法呢?
突然想到,既然是用户在线信息,linux有专门的/var/run/utmp文件存储用户在线信息,为什么还需要自己判断呢,只需要查找这个文件中是否有当前要删除的用户就行了。由于utmp文件数据访问需要使用系统函数查找,目前没有根据用户名查找记录的功能,使用这个文件数据之前,需要遍历查找是否有满足条件的数据,先看看这个文件相关的函数。
UTMP相关函数
#include <utmp.h>----头文件
int utmpname(const char file);
设置下面utmp函数访问的文件,如果没有设置,则默认为_PATH_UTMP,不过一般会手动设置一次。
void setutent(void);
将指针放到文件的开头,一般访问文件之前,先调用这个函数。
struct utmp getutent(void);
从utmp文件中读取一行,当有数据时,返回当前行的用户数据,当返回值为空时,代表没有信息。特别的,可以用这个函数不停遍历,直至遍历到最后一项数据。
struct utmp getutid(const struct utmp ut);
从utmp中的当前文件位置向前搜索,如果ut->ut_type是RUN_LVL、BOOT_TIME、NEW_TIME或OLD_TIME,该函数将返回第一个ut_type字段与ut->ut_type匹配的数据,如果ut->ut_type是INIT_PROCESS、LOGIN_PROCESS, USER_PROCESS或DEAD_PROCESS,该函数将返回第一个ut_id字段与ut->ut_id匹配的数据。
struct utmp getutline(const struct utmp ut);
从当前文件位置向前搜索utmp文件,仅搜索ut_type为USER_PROCESS或LOGIN_PROCESS的数据,并返回第一条符合要求的数据。
struct utmp pututline(const struct utmp ut);
将用户信息写入到utmp文件,根据getutid()获取文件中的合适位置,即当发现有数据时,覆盖原有数据,如果没有数据时,在文件末尾追加数据。
void endutent(void);
关闭utmp文件,当使用完上面的utmp相关的函数后,调用该函数关闭文件。
总结
修改关键代码如下:
/* 指定需要操作的utmp文件 */
(VOID) utmpname(_PATH_UTMP);
setutent();
pstEntry = getutent();
while (NULL != pstEntry)
{
if ( 0 == strcmp(username, pstEntry->ut_user))
{
/* 有用户在线 ,且uid一致 */
if ((USER_PROCESS == pstTmp->ut_type) &&(Uid == pstTmp->ut_uid))
{
/* 将f返回值置为 True*/
bIsCurUser = true;
}
}
pstEntry = getutent();
}
endutent();
当发现问题与线用户信息有关时,首先想想是不是与utmp有关,是否能通过utmp中的数据进行判断。