APUE笔记-第六章系统数据文件和信息

其他章节

第三章 文件IO
第四章 文件和目录
第五章 标准IO

第七章
第八章
第九章
第十章


提示:个人学习笔记,无参考价值


第六章 系统数据文件和信息

1. etc下的passwd、shadow、group文件

在Ubuntu系统中,/etc目录下,有三个文件:passwd shadow group。 这三个配置文件用于系统帐号管理,都是文本文件,可用vim等文本编辑器打开。

(1) /etc/passwd

在 Linux 系统中,每个用户都有一个对应的 /etc/passwd 文件中的记录行。这个文件对所有用户都是可读的,它记录了每个用户的一些基本属性信息。文件的内容如下:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4::sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
......

每一行是由分号分隔的字串组成,它的格式如下:
username:password:uid:gid:gecos:homedir:shell

各域对应的中文说明如下:
用户名:密码:用户ID:组ID:用户的全称等其它详细信息:主目录:登录shell

字段名描述
用户名用户账号的字符串,通常长度不超过 8 个字符,并且由大小写字母和/或数字组成。
密码存放着加密后的用户密码字,但是由于安全隐患,现在多数 Linux 系统使用 shadow 技术将真正的加密后的用户密码字存放到 /etc/shadow 文件中。
用户标识号一个整数,系统内部用它来标识用户。一般情况下它与用户名是一一对应的。一般情况,超级用户是0,系统用户是1 ~ 499,普通用户是500 ~ 65535。
组标识号记录用户所属的用户组。它对应着 /etc/group 文件中的一条记录。
注释性描述记录着用户的一些个人情况,例如用户的真实姓名、电话、地址等,这个字段并没有什么实际的用途。
主目录用户的起始工作目录,即用户在登录到系统之后所处的目录。
登录 Shell用户登录后启动的进程,也就是用户与 Linux 系统之间的接口。

Unix系统最初是用明文保存密码的,后来由于安全的考虑,采用crypt()算法加密密码并存放在/etc/passwd文件。现在,由 于计算机处理能力的提高,使密码破解变得越来越容易。/etc/passwd文件是所有合法用户都可访问的,大家都可互相看到密码的加密字符串,这给系统 带来很大的安全威胁。现代的Unix系统使用影子密码系统,它把密码从/etc/passwd文件中分离出来,真正的密码保存在/etc/shadow文件中,shadow文件只能由超级用户访问。这样入侵者就不能获得加密密码串,用于破解。

使用shadow密码文件后,/etc/passwd文件中所有帐户的password域的内容为"x",如果password域的内容为"*",则该帐号被停用。使用passwd这个程序可修改用户的密码。


(2) /etc/shadow

/etc/shadow存放加密的口令,该文件只能由root读取和修改。下面是shadow文件的内容:

root:$1$43ZR5j08$kuduq1uH36ihQuiqUGi/E9::0::7:::
daemon:*::0::7:::
bin:*::0::7:::
sys:*::0::7:::
sync:*::0::7:::
...
shaohao:$6$.afaIAfKOfGq0Y.P$5oNj2WwjGDEJ3aNQk...qYTEKKeR9lBEX/:19612:0:99999:7:::
...

/etc/shadow文件的格式如下:
用户名:加密密码:最后一次修改时间:最小修改时间间隔:密码有效期:密码需要变更前的警告天数:密码过期后的宽限时间:账号失效时间:保留字段

  • 加密密码
    这里保存的是真正加密的密码。目前 Linux 的密码采用的是 SHA512 散列加密算法,原来采用的是 MD5 或 DES 加密算法。SHA512 散列加密算法的加密等级更高,也更加安全。

  • 最后一次修改时间
    上面用户shahao密码最后一次修改时间字段值为19612,即1970-1-1后的19612天,如下图
    在这里插入图片描述

  • 最小修改时间间隔
    最小修改间隔时间,也就是说,该字段规定了从第 3 字段(最后一次修改密码的日期)起,多长时间之内不能修改密码。如果是 0,则密码可以随时修改;如果是 10,则代表密码修改后 10 天之内不能再次修改密码。
    此字段是为了针对某些人频繁更改账户密码而设计的。

  • 密码有效期
    经常变更密码是个好习惯,为了强制要求用户变更密码,这个字段可以指定距离第 3 字段(最后一次更改密码)多长时间内需要再次变更密码,否则该账户密码进行过期阶段。
    该字段的默认值为 99999,也就是 273 年,可认为是永久生效。如果改为 90,则表示密码被修改 90 天之后必须再次修改,否则该用户即将过期。管理服务器时,通过这个字段强制用户定期修改密码。
    密码需要变更前的警告天数

    与第 5 字段相比较,当账户密码有效期快到时,系统会发出警告信息给此账户,提醒用户 “再过 n 天你的密码就要过期了,请尽快重新设置你的密码!”。
    该字段的默认值是 7,也就是说,距离密码有效期的第 7 天开始,每次登录系统都会向该账户发出 “修改密码” 的警告信息。

  • 密码过期后的宽限天数
    也称为“口令失效日”,简单理解就是,在密码过期后,用户如果还是没有修改密码,则在此字段规定的宽限天数内,用户还是可以登录系统的;如果过了宽限天数,系统将不再让此账户登陆,也不会提示账户过期,是完全禁用。
    比如说,此字段规定的宽限天数是 10,则代表密码过期 10 天后失效;如果是 0,则代表密码过期后立即失效;如果是 -1,则代表密码永远不会失效。

  • 账号失效时间
    同第 3 个字段一样,使用自 1970 年 1 月 1 日以来的总天数作为账户的失效时间。该字段表示,账号在此字段规定的时间之外,不论你的密码是否过期,都将无法使用!
    该字段通常被使用在具有收费服务的系统中。

  • 保留
    这个字段目前没有使用,等待新功能的加入。


(3) /etc/group

etc/group 文件是用户组的配置文件,内容包括用户和用户组,并且能显示出用户是归属哪个用户组或哪几个用户组,同一用户组的用户之间具有相似的特征。比如我们把某一用户加入到root用户组,那么这个用户就可以浏览root用户家目录的文件,如果root用户把某个文件的读写执行权限开放,root用户组的所有用户都可以修改此文件,如果是可执行的文件(比如脚本),root用户组的用户也是可以执行的。

用户组的特性在系统管理中为系统管理员提供了极大的方便,但安全性也是值得关注的,如某个用户下有对系统管理有最重要的内容,最好让用户拥有独立的用户 组,或者是把用户下的文件的权限设置为完全私有;另外root用户组一般不要轻易把普通用户加入进去.

/etc/group 每一行的内容包括用户组(Group)、用户组口令、GID及该用户组所包含的用户(User)。格式如下:

group_name:passwd:GID:user_list
  • 第一字段:用户组名称
  • 第二字段:用户组密码
  • 第三字段:GID
  • 第四字段:用户列表,每个用户之间用,号分割


2. /etc/passwd

getpwnam() 与 getpwuid()

#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);


struct passwd {
       char   *pw_name;       /* username */
       char   *pw_passwd;     /* user password */
       uid_t   pw_uid;        /* user ID */
       gid_t   pw_gid;        /* group ID */
       char   *pw_gecos;      /* user information */
       char   *pw_dir;        /* home directory */
       char   *pw_shell;      /* shell program */
};

案例

#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
    if (argc < 2){
        fprintf(stderr,"usage:./%s uid\n",argv[0]);
        exit(1);
    }

    uid_t uid = atoi(argv[1]);

    struct passwd * userInfo = getpwuid(uid);

    printf("user name = %s\n", userInfo->pw_name); //输出用户名

    exit(0);
}


3. /etc/group

getgrnam() 和 getgrgid()

#include <sys/types.h>
#include <grp.h>

struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);


struct group {
	char   *gr_name;        /* group name */
	char   *gr_passwd;      /* group password */
	gid_t   gr_gid;         /* group ID */
	char  **gr_mem;         /* NULL-terminated array of pointers to names of group members*/
};

案例

#include <stdlib.h>
#include <stdio.h>
#include <grp.h>

int main(int argc, char* argv[]){
    if (argc < 2){
        fprintf(stderr,"usage:./%s gid\n",argv[0]);
        exit(1);
    }

    struct group * groupInfo;
    groupInfo = getgrgid(atoi(argv[1]));


    printf("%s\n",groupInfo->gr_name);	//输出组名

    exit(0);
}


4. /etc/shadow

(1) getspnam()

//通过username 获取它在shadow中的一行内容
 #include <shadow.h>
struct spwd *getspnam(const char *name);



struct spwd {
	char *sp_namp;     /* Login name */
  	
  	char *sp_pwdp;     /* Encrypted password */
  	
  	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;  /* Reserved */
};

(2) crypt()

#include <crypt.h>
/**
* 参数一:要加密的串
* 参数二:加密的方式和盐,crypt函数只能用到第三个$符号之前的内容
*/
char * crypt(const char *key, const char *salt);
#include <unistd.h>
char *getpass(const char *prompt); //参数是显示出来的提示符,返回的值是用户的输入

在这里插入图片描述用户密码被加密,格式为$加密方式$加密使用的salt(杂串)$encrypted加密后的口令。用 $ 符号隔开

案例

#include <stdlib.h>
#include <stdio.h>
#include <crypt.h>
#include <unistd.h>
#include <shadow.h>
#include <string.h>

int main(int argc, char* argv[]){
    if (argc < 2){
        fprintf(stderr,"usage: ./%s username",argv[0]);
        exit(1);
    }

    char * input_pass;
    input_pass = getpass("input your password:");

    struct spwd * shadow_line;
    shadow_line = getspnam(argv[1]);
    //第二个参数 crypt函数指挥用到它第三个$之前的字符串数据
    char * crypted_input_pass = crypt(input_pass, shadow_line->sp_pwdp);

    if (strcmp(crypted_input_pass, shadow_line->sp_pwdp) == 0){
        puts("OK!");
    }else{
        puts("ERROR!");
    }

    exit(0);
}

编译指令

gcc crypt_test.cpp -o crypt_test -lcrypt


5. 时间戳

(1) time()

 #include <time.h>
time_t time(time_t *tloc);
  1. 函数描述
    time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

  2. 返回值
    On success, the value of time in seconds since the Epoch is returned.
    On error, ((time_t) -1) is returned, and errno is set appropriately.

    time_t res;	//拿到时间戳的两种方法
    time(&res);
    res = time(NULL);
    

(2) gmtime() 与localtime()

函数描述:都是把time_t的类型转换为结构体tm

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);



struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};

(3) mktime()

time_t mktime(struct tm *tm);

将结构体tm转换为time_t类型。
失败则返回 (time_t) -1 并且设置 errno.

【副作用】
会修改传入的参数,可以用来调节溢出的部分,如天数溢出,则进位地调整月数和年数。


(4) strftime()

strftime用来格式化时间和日期,转换成用户需要的形式。

#include <time.h>
/**
* 参数一:用于存放结果的字符串
* 参数二:用于说明存放结果的空间最多能存放多少个字节数据
* 	 %y     不显示世纪。如:2014显示14
     %Y     显示完整的年份 
     %m		月
     %d		日
     %H		时
     %M		分
     %S		秒
	。。。。。。
* 参数三:想要的格式
* 参数四:要被格式化的时间结构体tm
*/
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

案例

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define BUFSIZE 1024

int main(){
    time_t * my_time ;
    //获取当前时间
    time(my_time);

    //将time_t类型的时间转成tm类型结构体
    struct tm * tm_my_time = localtime(my_time);

    char  buf[BUFSIZE];
    memset(buf,'\0',BUFSIZE);

    //格式化输出 年-月-日 时:分:秒
    strftime(buf, BUFSIZE, "%Y-%m-%d %H:%M:%S", tm_my_time);
    puts(buf);
    exit(0);
}

案例一 每隔一秒写一次当前日期

每隔一秒,往文本文件输入一行内容,内容为:当前时间

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#define BUFSIZE 1024

int main(){
	//以追加+读的模式打开文件
    FILE * my_file = fopen("./timelog.txt","a+");
    if (my_file == NULL){
        perror("fopen()");
        exit(1);
    }

    time_t * my_time ;
    struct tm * tm_my_time ;
    char buf[BUFSIZE];
    int i = 0;
    while (i < 100){
        //获取当前时间戳
        time(my_time);

        //转成结构体类型
        tm_my_time = localtime(my_time);

        //格式化
        memset(buf,'\0',BUFSIZE);
        strftime(buf, BUFSIZE, "%Y-%m-%d %H:%M:%S\n",tm_my_time);
        
        
        fwrite(buf,20,1,my_file);
        fflush(my_file); //刷新缓存区,把缓存区里的内容输出到文件中
        sleep(1);
        
        i++;
    }

    fclose(my_file);
    exit(0);
}

案例二 输出一百天后的日期

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define BUFSIZE 1024

int main(){
    time_t my_time = time(NULL);
    struct tm * tm_my_time = localtime(&my_time);

    char buf[BUFSIZE];
    strftime(buf, BUFSIZE, "now_time: %Y-%m-%d", tm_my_time);
    puts(buf);


    tm_my_time->tm_mday += 100; //这里天数会溢出

    /**
     * 该函数原本作用:将结构体tm类型的时间转转为time_t类型的时间戳
     * 副作用:会调整传入的参数,将溢出的天数进位地调整月数和年数。
     */
    (void)mktime(tm_my_time);
    strftime(buf, BUFSIZE, "after 100 days: %Y-%m-%d", tm_my_time);
    puts(buf);
    exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值