四、 系统数据文件和信息
getpwnam(3)、getpwuid(3)
getpwnam, getpwuid - get password file entry
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
在/etc/passwd 文件中保存了系统中每个用户的用户名、UID 和 GID 等信息。
但是这个文件在不同的系统中保存的格式是不一样的,如果一个程序直接用文件流去读取里面的内容,那么这个程序的可移植性就被降低了。
老版本的BSD 使用 BDB (BSDDB) 数据库保存用户信息;
HPUnix 使用文件系统 hash 方式保存用户信息;
POSIX.1、FreeBSD 8.0、Linux 3.2.0、Mac OS X 10.6.8、Solaris 10 等系统使用 /etc/passwd 文件保存用户信息。(详见 APUE 第三版 P142 图6-1)
正是由于操作系统之间的这种实现方式不统一,无论使用哪种方式保存用户信息,通过这两个函数都可以获得到用户的信息,保存在 struct passwd 中。
getpwnam(3) 的作用是根据用户名查找用户信息。
getpwuid(3) 的作用是根据用户 ID 查找用户信息。
struct passwd 定义在 pwd.h 头文件中,具体内容如下:
struct passwd {
char *pw_name; /* 用户名 */
char *pw_passwd; /* 用户口令 */
uid_t pw_uid; /* 用户 ID */
gid_t pw_gid; /* 用户组 ID */
char *pw_gecos; /* user information */
char *pw_dir; /* 用户的家目录 */
char *pw_shell; /* 用户登录 shell */
};
getgrnam(3)、getgrgid(3)
getgrnam, getgrgid - get group file entry
#include <sys/types.h>
#include <grp.h>
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
这两个函数的作用和上面那两个函数的作用类似,只不过这次获取的是用户组的数据。
getgrnam(3) 根据用户组名称获得用户组信息。
getgrgid(3) 根据用户组 ID 获得用户组信息。
struct group 定义在 grp.h 头文件中,下面是这个结构体中的内容
struct group {
char *gr_name; /* 用户组名称 */
char *gr_passwd; /* 用户组密码?什么鬼 */
gid_t gr_gid; /* 用户组 ID */
char **gr_mem; /* 用户组中的用户列表 */
};
解析一下/etc/shadow文件
tom:$y$j9T$k9hD614nCdi1b2f00t1oQ/$wp26/JVsNYxHwc1CBJ/lhweVP28T0kDsIPEZ9/XkOR2:19548:0:99999:7:::
文件中每一行是一个用户的信息,每一部分用冒号隔开,最终要的就是前两个冒号隔开的内容:用户名和密码。
而第二部分,也就是密码部分又用 $ 分隔成了3部分,第一部分为加密方式 ID,含义见表1;第二部分是加盐值,也就是密码中的一个杂字串,用于增加加密强度;第三部分就是明文密码 + 杂字串根据第一部分指定的加密方式计算出来的哈希散列密文。
加密方式 ID 对应的加密方式如下:
ID | 加密方式 |
---|---|
1 | MD5 |
2a | Blowfish |
5 | SHA-256 |
6 | SHA-512 |
getspnam(3)
函数可以根据用户名来获得用户的密码等信息。其实就是在读取 /etc/shadow 文件,所以请注意,使用这个函数的进程必须具有 root 权限。
getspnam - get shadow password file entry
#include <shadow.h>
struct spwd *getspnam(const char *name);
getspnam返回了一个结构体 struct spwd,我们来看下这个结构体里如下
struct spwd {
char *sp_namp; /* 登录用户名 */
char *sp_pwdp; /* 加密的密码,格式为 $ID$Salt$Pwd */
long sp_lstchg; /* Date of last change
(measured in days since
1970-01-01 00:00:00 +0000 (UTC)) */
long sp_min; /* Min # of days between changes */
long sp_max; /* Max # of days between changes */
long sp_warn; /* # of days before password expires
to warn user to change it */
long sp_inact; /* # of days after password expires
until account is disabled */
long sp_expire; /* Date when account expires
(measured in days since
1970-01-01 00:00:00 +0000 (UTC)) */
unsigned long sp_flag; /* 保留标志 */
};
crypt(3)
Linux 系统中也为我们提供了一个简便的加密函数:crypt(3)
crypt - password and data encryption
#define _XOPEN_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
char *crypt(const char *key, const char *salt);
Link with -lcrypt.
当然,用这个函数稍微有点麻烦,看见上面的宏定义了吧,前面我们遇到过类似的函数。我们再来说一次有关这种宏定义的用法,下次再遇到就不再赘述了。
如果 man 手册中说一个函数在使用之前需要定义一个宏,那么通常你有三种办法:
-
在包含头文件之前定义这个宏,就像手册中写的那样。
-
如果在源文件中没有定义宏,那么就需要在编译的时候通过 -D 参数来指定:gcc -D_XOPEN_SOURCE,注意 -D 参数后面不要加空格,直接写宏名。当然 gcc 是这样用的,其它编译器的用法需要你去查对应编译器的手册了。
-
在 Makefile 的 CFLAGS 中指定编译选项:CFLAGS += -D_XOPEN_SOURCE,这种方式也是针对 gcc 的,其它编译器的用法需要你去查对应的编译器手册。
还要注意的是,链接的时候要加上 -lcrypt 链接选项才行。
参数列表:
key: 加密前的明文。
salt: 用来指定加密算法和加盐值,格式为 $加密算法ID$Salt$被忽略,加密算法ID 可以从上面 表1 中选择。有木有觉得跟 /etc/shadow 文件中的密码部分很像?该函数只能看见第三个 $ 之前的部分,后面的内容将被忽略。
返回值就跟 /etc/shadow 文件中的密码部分一样了: 加密算法 I D 加密算法ID 加密算法IDSalt$密文。
getpass(3)
我们平时见到的 Shell 中要求输入口令的时候都是关闭回显的,输入完成后再恢复回显。当然可以通过手动设置参数的方式实现,但是比较麻烦,系统中已经提供了一个现成的函数专门用于获取口令:getpass(3)
getpass - get a password
#include <unistd.h>
char *getpass( const char *prompt);
参数是显示在 shell 中的提示性文字,返回的就是用户从控制台上输入的字符串。
用这getspnam、crypt 两个函数我们可以写一个程序模仿 shell 用户登录例子
#include <stdio.h>
#include <shadow.h>
#include <unistd.h>
#include <string.h>
int main (int argc, char **argv)
{
char name[32] = "", *pwd;
struct spwd *p;
size_t namelen = 0;
printf("请输入用户名:");
fgets(name, 32, stdin);
pwd = getpass("请输入密码:");
namelen = strlen(name);
name[namelen - 1] = 0; // 去掉读入的'\n'
p = getspnam(name);
if (!p) {
fprintf(stderr, "用户名或密码错误!\n");
return -1;
}
// 由于 getspnam(3) 返回的 sp_pwdp 部分正好符合 crpyt(3) 要求的 salt 参数的规则,所以可以直接作为参数使用,反正 crpyt(3) 会忽略第三个 $ 之后的内容
if (!strcmp(crypt(pwd, p->sp_pwdp), p->sp_pwdp)) {
printf("密码正确!\n");
} else {
fprintf(stderr, "用户名或密码错误!\n");
}
return 0;
}
uname(2)
uname - get name and information about current kernel
#include <sys/utsname.h>
int uname(struct utsname *buf);
uname(1) 命令大家都使用过吧,它就是通过 uname(2) 函数封装的,可以获取到一些内核中的信息。
时间和日期例程
时间格式通常分为三种:
第一种是人类喜欢的格式化字符串,例如:2015年 04月 19日 星期日 22:21:29 CST;
第二种是程序猿喜欢的格式:分解时间(struct tm)。其实就是将时间的各个部分分开,保存到一个结构体中,这样在使用的时候灵活性更高。
第三种是计算机喜欢的格式:日历时间(time_t),也就是大整数,硬件处理起来更方便。例如:1429455918。
time(3)
time - get time2
#include <time.h>
time_t time(time_t *t);
time(3) 函数的作用就是从内核中获取一个日历时间(time_t,大整数),参数传入 NULL 则可以获取到从 1970-01-01 00:00:00(UTC) 到现在的秒数。
localtime
localtime - transform date and time to broken-down time or ASCII
#include <time.h>
struct tm *localtime(const time_t *timep);
localtime(3) 函数的作用是将 time_t 大整数转换为程序员喜欢的 tm 结构体,并且是将日历时间转换为本地时间。
struct tm 成员包括:
struct tm {
int tm_sec; /* 秒,支持润秒 [0 - 60] */
int tm_min; /* 分钟 [0 - 59] */
int tm_hour; /* 小时 [0 - 23] */
int tm_mday; /* 一个月中的第几天 [1 - 31] */
int tm_mon; /* 月份 [0 - 11] */
int tm_year; /* 年,从 1900 开始 */
int tm_wday; /* 一星期中的第几天 [0 - 6] */
int tm_yday; /* 一年中的第几天 [0 - 365] */
int tm_isdst; /* 夏令时调整,基本不用,如果怕有影响可以设置为 0 */
};
gmtime
gmtime - transform date and time to broken-down time or ASCII
#include <time.h>
struct tm *gmtime(const time_t *timep);
gmtime(3) 函数与 localtime(3) 函数相同,作用也是将 time_t 大整数转换为程序员喜欢的 tm 结构体,但是它将日历时间转换为 UTC 时间而不是转换为本地时间。
mktime
mktime - transform date and time to broken-down time or ASCII
#include <time.h>
time_t mktime(struct tm *tm);
mktime(3) 函数的作用与上面两个函数的作用正好是相反的,将程序猿喜欢的 struct tm 转换为计算机喜欢的 time_t 类型。
注意到参数 tm 没有加 const 关键字修饰了吗?这说明函数的内部可能会修改入参的值。
它在转换之前会先调整入参的每一个成员,发现有越界的情况会将其调整为合法的状态
strftime
strftime - format date and time
#include <time.h>
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
这个函数很复杂,使用起来就像 printf(3) 一样,可以通过格式化字符串来控制返回的字符串格式。
参数列表:
s:转换完成后的字符串保存在s所指向的空间;
max:s 的最大长度
format:格式化字符串;用法跟 printf(3) 的 format 是一样的,但是具体格式化参数是不同的,详细的内容请查阅 man 手册。
tm:转换的数据来源;
求取100天后是哪天
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TIMESTRINGSIZE 1024
int main()
{
time_t stamp;
struct tm *tm;
char timestr[TIMESTRINGSIZE] = "";
stamp = time(NULL); //获取time时间
tm = localtime(&stamp);//将time时间转化为本地时间结构体
strftime(timestr,TIMESTRINGSIZE,"NOW:%Y-%m-%d",tm);//格式化输出时间格式
puts(timestr);
tm->tm_mday += 100;
(void)mktime(tm);// 时间结构体转化为time时间
strftime(timestr,TIMESTRINGSIZE,"100 days later:%Y-%m-%d",tm);
puts(timestr);
exit(0);
}