四、 系统数据文件和信息

四、 系统数据文件和信息

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加密方式
1MD5
2aBlowfish
5SHA-256
6SHA-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 手册中说一个函数在使用之前需要定义一个宏,那么通常你有三种办法:

  1. 在包含头文件之前定义这个宏,就像手册中写的那样。

  2. 如果在源文件中没有定义宏,那么就需要在编译的时候通过 -D 参数来指定:gcc -D_XOPEN_SOURCE,注意 -D 参数后面不要加空格,直接写宏名。当然 gcc 是这样用的,其它编译器的用法需要你去查对应编译器的手册了。

  3. 在 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值