Linux编程入门-用户管理与安全

用户和组

getpwnam/getpwuid

从passwd文件中获取记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <pwd.h>
struct passwd {
char *pw_name; /* Username. */
char *pw_passwd; /* Hashed passphrase, if shadow database not in use (see shadow.h). */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
struct passwd *getpwnam (
const char *__name //登录名
); //返回的指针指向静态分配的内存 不可重入
struct passwd *getpwuid (
__uid_t __uid
);

使用例子:

1
2
3
4
5
6
7
8
9
10
struct passwd* pwd;
errno=0;
pwd=getpwnnam(name);
if(pwd==NULL)
if(errno==0){
//未找到
}
else{
//错误
};

getgrnam/getgrgid

从group文件中获取记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <grp.h>
struct group{
char *gr_name; /* Group name. */
char *gr_passwd; /* Password. */
__gid_t gr_gid; /* Group ID. */
char **gr_mem; /* Member list. */
};
struct group *getgrnam (
const char *__name //组名
);
struct group *getgrgid (
__gid_t __gid
);

getpwent/setpwent/endpwent

按顺序扫描密码文件记录。getpwent从passwd文件中逐条返回记录,无记录或出错时返回NULL,最后用endpwent关闭密码文件。用setpwent重返文件起始处。

1
2
3
struct passwd *getpwent (void);
void setpwent (void);
void endpwent (void);

例子:

1
2
3
4
struct passwd* pwd;
while((pwd=getpwent())!=NULL)
printf("%-8s %5ld\n",pwd->pw_name,(long)pw->pw_uid);
endpwent();

getspnam/getspent/setspent/endspent

从shadow文件中获取个别记录或扫描所有记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <shadow.h>
struct spwd {
char *sp_namp; /* Login name. */
char *sp_pwdp; /* Hashed passphrase. */
long int sp_lstchg; /* Date of last change. */
long int sp_min; /* Minimum number of days between changes. */
long int sp_max; /* Maximum number of days between changes. */
long int sp_warn; /* Number of days to warn user to change the password. */
long int sp_inact; /* Number of days the account may be inactive. */
long int sp_expire; /* Number of days since 1970-01-01 until account expires. */
unsigned long int sp_flag; /* Reserved. */
};
struct spwd *getspnam (
const char *__name
);
struct spwd *getspent (void);
void setspent (void);
void endspent (void);

用户认证

crypt

使用用户名/密码认证的加密算法,即DES。crypt返回的经加密的密码中,前两个字符是salf值。使用该函数时编译选项要加“-lcrypt”参数。

1
2
3
4
5
6
#define _XOPEN_SOURCE
#include <unistd.h>
char *crypt (
const char *__key, //8字符密钥
const char *__salt //两字符字符串
); //返回指针 指向长13字符静态分配的经加密处理后的字符串

getpass

读取用户密码。该函数先屏蔽回显功能,并停止对终端特殊字符字符的处理,如Ctrl+C等中断字符。然后打印prompt字符串,读取一行输入,返回静态分配的输入字符串。最后将终端设置还原。用sysconf的_SC_LOGIN_NAME_MAX获取用户名字符串最大长度。

1
2
3
4
5
#define _BSD_SOURCE
#include <unistd.h>
char *getpass (
const char *__prompt
);

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#if ! defined(__sun)
#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */
#endif
#endif
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include <crypt.h>
//...
int main(int argc, char* argv[]) {
char* username, * password, * encrypted, * p;
struct passwd* pwd;
struct spwd* spwd;
Boolean authOk;
size_t len;
long lnmax;
lnmax = sysconf(_SC_LOGIN_NAME_MAX); /* Determine size of buffer required for a username, and allocate it */
if (lnmax == -1) /* If limit is indeterminate */
lnmax = 256; /* make a guess */
username = malloc(lnmax);
if (username == NULL)
errExit("malloc");
printf("Username: ");
fflush(stdout);
if (fgets(username, lnmax, stdin) == NULL)
exit(EXIT_FAILURE); /* Exit on EOF */
len = strlen(username);
if (username[len - 1] == '\n')
username[len - 1] = '\0'; /* Remove trailing '\n' */
pwd = getpwnam(username); /* Look up password and shadow password records for username */
if (pwd == NULL)
fatal("couldn't get password record");
spwd = getspnam(username);
if (spwd == NULL && errno == EACCES)
fatal("no permission to read shadow password file");
if (spwd != NULL) /* If there is a shadow password record */
pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */
password = getpass("Password: ");
encrypted = crypt(password, pwd->pw_passwd); /* Encrypt password and erase cleartext version immediately */
for (p = password; *p != '\0'; )
*p++ = '\0';
if (encrypted == NULL)
errExit("crypt");
authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
if (!authOk) {
printf("Incorrect password\n");
exit(EXIT_FAILURE);
};
printf("Successfully authenticated: UID=%ld\n", (long)pwd->pw_uid);
/* Now do authenticated work... */
exit(EXIT_SUCCESS);
};

进程凭证

每个进程有一套用数字表示的用户ID和组ID,有时这些ID称为进程凭证。进程凭证具体包括实际用户ID和实际组ID、有效用户ID和有效组ID、保存用户ID和保存用户组ID、文件系统用户ID和文件系统组ID、辅助组ID。

实际用户ID和实际组ID确定进程所属的用户和组。登录shell从passwd文件中读取相应用户密码记录的第三、四字段,置为实际用户ID和实际组ID。创建新进程时,从父进程中继承这些ID。

有效用户ID为0的进程拥有超级用户的所有权限,称为特权级进程。通常有效用户ID和组ID与相应的实际ID相等,但当使用系统调用时,或执行set-user-ID和set-group-ID程序时,二者可能不同。set-user-ID程序将进程有效用户ID置为可执行文件用户ID,组ID类似,获得常规情况下不具有的权限。该权限位对shell脚本无效。有效用户ID为0的程序称为set-user-ID-root程序。

保存用户ID和保存用户组ID的值由对应的有效ID和有效组ID复制而来,允许进程的有效用户ID在实际用户ID和保存用户ID直接切换。

文件系统用户ID和组ID决定如打开文件、改变文件属主、修改文件权限等文件系统操作的操作权限。一般文件系统用户ID和组ID的值等于有效用户ID和组ID,发生变化时也跟随变化,除非用特定系统调用更改。

辅助组ID用于表示进程所属若干附加的组。新进程从其父进程处集成这些ID,登录shell从系统组文件获取辅助组ID。

getuid/getgid/setuid/setgid

getuidgetgid分别获取调用进程的实际用户ID和组ID。setuidsetgid修改有效ID,可能实际ID和保存ID也随之修改。非特权用户执行set-user-ID程序时setuid才起作用。当特权用户以非0参数调用setuid后,实际用户ID、有效用户ID和保存用户ID三者都改为uid参数的值,所有特权将丢失且不能再用setuid重置为0。

1
2
3
4
5
6
7
8
9
#include <unistd.h>
__uid_t getuid (void);
__gid_t getgid (void);
int setuid (
__uid_t __uid
); //成功0 否则-1
int setgid (
__gid_t __gid
);

geteuid/getegid/seteuid/setegid

geteuidgetegid返回有效ID,用seteduidsetegid修改有效ID。特权级进程能将其有效ID改为任意值。非特权级进程仅能将其有效ID改为相应的实际ID或保存ID。

1
2
3
4
5
6
7
8
__uid_t geteuid (void);
__gid_t getegid (void);
int seteuid (
__uid_t __uid
);
int setegid (
__gid_t __gid
);

setreuid/setregid

setreuidsetregid允许调用进程修改器实际和有效ID,当某个参数不用时置为-1。特权级用户可将其实际ID和有效ID设为任意值。非特权级进程只能将其实际ID设为有效ID值,且只能将有效ID设为当前实际ID或保存ID。当对有效ID设置的值不同余系统调用前的实际ID,无论进程是否拥有特权,都可将保存ID设为新的有效ID。

1
2
3
4
5
6
7
8
int setreuid (
__uid_t __ruid, //新实际ID
__uid_t __euid //新有效ID
);
int setregid (
__gid_t __rgid,
__gid_t __egid
);

getresuid/getresgid/setresuid/setresgid

getresuidgetresgid可获取调用进程当前实际ID、有效ID和保存ID,setresuidsetresgid可修改这些ID,无需修改的参数置-1。非特权进程能将实际ID、有效ID和保存ID中任一ID设为实际ID、有效ID或保存ID中任一当前值。特权级进程能对其实际ID、有效ID和保护ID任意设置。文件系统ID总与有效ID相同。setresuidsetresgid具有0/1效应,即对ID的修改请求要么全成功,要么全失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int getresuid (
__uid_t *__ruid,
__uid_t *__euid,
__uid_t *__suid
);
int getresgid (
__gid_t *__rgid,
__gid_t *__egid,
__gid_t *__sgid
);
int setresuid (
__uid_t __ruid,
__uid_t __euid,
__uid_t __suid
);
int setresgid (
__gid_t __rgid,
__gid_t __egid,
__gid_t __sgid
);

setfsuid/setfsgid

修改文件系统ID。非特权进程能将文件系统ID设为实际ID、有效ID或保存ID的当前值。特权级进程能将为你教案系统ID设为任意值。

1
2
3
4
5
6
7
#include <sys/fsuid.h>
int setfsuid (
__uid_t __uid
);
int setfsgid (
__gid_t __gid
);

getgroups/setgroups/initgroups

getgroups将当前进程的辅助组ID的集合返回至list参数中。size参数建议设为NGROUPS_MAX+1,其中该常量在limits.h中定义,也可用sysconf的_SC_NGROUPS_MAX获取,还可通过/proc/sys/kernel/ngroups_max文件读取。特权级进程可用setgroupsinitgroups修改辅助组ID集合。setgroups用groups参数数组替换调用进程的辅助组ID。initgroups扫描group文件,为user参数创建属组列表,初始化调用进程的辅助组ID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int getgroups (
int __size, //list长度
__gid_t __list[]
); //成功返回置于list中的组ID数量 size过小则-1 错误码EINVAL
#define _BSD_SOURCE
#include <grp.h>
int setgroups (
size_t __n, //groups数组中组ID数量
const __gid_t *__groups
); //成功0 否则-1
int initgroups (
const char *__user,
__gid_t __group
); //同上

示例

获取进程所有用户ID和组ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/fsuid.h>
#include <limits.h>
#include <pwd.h>
#include <stdlib.h>
#include <grp.h>
#include <stdio.h>
//...
#define SG_SIZE (NGROUPS_MAX + 1)
char* userNameFromId(uid_t uid) { /* Return name corresponding to 'uid', or NULL on error */
struct passwd* pwd;
pwd = getpwuid(uid);
return (pwd == NULL) ? NULL : pwd->pw_name;
};
uid_t userIdFromName(const char* name) { /* Return UID corresponding to 'name', or -1 on error */
struct passwd* pwd;
uid_t u;
char* endptr;
if (name == NULL || *name == '\0') /* On NULL or empty string */
return -1; /* return an error */
u = strtol(name, &endptr, 10); /* As a convenience to caller */
if (*endptr == '\0') /* allow a numeric string */
return u;
pwd = getpwnam(name);
if (pwd == NULL)
return -1;
return pwd->pw_uid;
};
char* groupNameFromId(gid_t gid) { /* Return name corresponding to 'gid', or NULL on error */
struct group* grp;
grp = getgrgid(gid);
return (grp == NULL) ? NULL : grp->gr_name;
};
gid_t groupIdFromName(const char* name) { /* Return GID corresponding to 'name', or -1 on error */
struct group* grp;
gid_t g;
char* endptr;
if (name == NULL || *name == '\0') /* On NULL or empty string */
return -1; /* return an error */
g = strtol(name, &endptr, 10); /* As a convenience to caller */
if (*endptr == '\0') /* allow a numeric string */
return g;
grp = getgrnam(name);
if (grp == NULL)
return -1;
return grp->gr_gid;
};
int main(int argc, char* argv[]) {
uid_t ruid, euid, suid, fsuid;
gid_t rgid, egid, sgid, fsgid;
gid_t suppGroups[SG_SIZE];
int numGroups, j;
char* p;
if (getresuid(&ruid, &euid, &suid) == -1)
errExit("getresuid");
if (getresgid(&rgid, &egid, &sgid) == -1)
errExit("getresgid");
fsuid = setfsuid(0); /* Attempts to change the file-system IDs are always ignored for unprivileged processes, but even so, the following calls return the current file-system IDs */
fsgid = setfsgid(0);
printf("UID: ");
p = userNameFromId(ruid);
printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long)ruid);
p = userNameFromId(euid);
printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long)euid);
p = userNameFromId(suid);
printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long)suid);
p = userNameFromId(fsuid);
printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long)fsuid);
printf("\n");
printf("GID: ");
p = groupNameFromId(rgid);
printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long)rgid);
p = groupNameFromId(egid);
printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long)egid);
p = groupNameFromId(sgid);
printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long)sgid);
p = groupNameFromId(fsgid);
printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long)fsgid);
printf("\n");
numGroups = getgroups(SG_SIZE, suppGroups);
if (numGroups == -1)
errExit("getgroups");
printf("Supplementary groups (%d): ", numGroups);
for (j = 0; j < numGroups; j++) {
p = groupNameFromId(suppGroups[j]);
printf("%s (%ld) ", (p == NULL) ? "???" : p, (long)suppGroups[j]);
};
printf("\n");
exit(EXIT_SUCCESS);
};