6.系统数据文件和信息

  • 先看一个小程序:
    在这里插入图片描述
    简单说明一下这个小程序:就是验证用户是否存在,存在则验证他的密码(我们知道用户的密码当然不是明文存储的)。这篇博客大概就是讲这个程序实现的相关基础内容(文末有程序源码)

1、引言

历史原因数据文件通常是ASCII文本文件,,顺序扫描很花时间,我们需要非ASCII文本格式存放这些文件,但仍向使用其他文件格式的应用程序提供接口


2、口令文件
  • unix系统口令文件字段包含在pwd.h中的passwd结构中,历史原因,这是一个ASCII文件(/etc/passwd)
    • 通常包含root项,用户ID是0
    • 加密口令字段包含一个占位符
    • 某些字段可能为空
    • shell字段包含一个可执行程序名(登录shell),squid的是/dev/null(这阻止了任何人以squid登录)
    • 可以用/bin/false或/bin/true作为登录shell组织用户登录,有些系统提供nologin命令(可打印定制的出错信息)
    • 提供finger命令的某些系统支持注释字段中的附加信息(姓名、电话号码等,finger -p root)
    • 某些系统提供vipw命令,允许管理员编辑口令文件
    • posix.1中定义了两个获取口令文件项的函数
      struct passwd *getpwuid(uid_t uid);
      int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer,
              size_t bufsize, struct passwd **result);
      struct passwd *getpwuid(uid_t uid);
      int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer,
              size_t bufsize, struct passwd **result);
      
      • ls程序用到了getpwuid,将i节点中的数字用户ID映射为用户登录名
      • 在键入登录名时,login程序调用getpwuid函数
    • 查看整个口令文件可以用下面的函数
      struct passwd *getpwent(void);
      void setpwent(void);
      void endpwent(void);
      
      • getpwent()返回口令文件的下一个记录项
      • setpwent()反绕所使用的文件,定位到文件开始
      • endpwent()关闭口令文件

3、阴影文件
  • 通过阻止获取原始加密口令而防止试探方法猜测口令,现在,某些系统将加密口令放在另一个文件中–阴影口令
  • 仅login和passwd等少数程序可以访问
struct spwd *getspnam(const char *name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);
struct spwd *fgetspent(FILE *stream);
struct spwd *sgetspent(const char *s);
int putspent(const struct spwd *p, FILE *stream);
int lckpwdf(void);
int ulckpwdf(void);

4、组文件
  • UNIX组文件,POSIX称其为组数据库
    #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 */
    };
    
    • 函数也返回一个静态变量的指针
  • 搜索整个组文件可以下面的接口
    struct group *getgrent(void);
    void setgrent(void);
    void endgrent(void);
    
    • setgrent()打开组文件并反绕,getgrent()读取下一个,endgrent()关闭组文件

5、附属组ID
  • 我们不仅可以属于口令文件中记录项中组ID对应的组,也可以属于至多16个另外的组
  • 文件访问权限被修改为:不仅比较进程的有效组ID和文件组ID,也比较附属组ID与文件的组ID
    #include <grp.h>
    int getgroups(int size, gid_t list[]);
    int setgroups(size_t size, const gid_t *list);
    int initgroups(const char *user, gid_t group);
    
    • getgroups()将进程所属用户的附属组ID填到数组grouplist中,附属组ID数最多为gidsetsize个,函数返回实际填写的个数
    • gidsetsize为0时只返回附属组ID数,不修改list数组
    • setgroups()设置附属组ID表
    • initgroups()读整个组文件,然后对user确定组的成员关系,然后调用setgroups(),以便为用户初始化附属组ID表

6、实现区别

linux、Solaris、FreeBSD、Mac OS X对账户信息、加密口令、是否散列文件、组信息的实现不同。由于平时工作几乎不涉及linux以外的操作系统,本文就不展开了。


7、其他数据文件
  • 下表列出了一些UNIX常用的
说明数据文件头文件结构附加的键搜索函数
口令/etc/passwd<pwd.h>passwdgetpwnam、getpwuid
/etc/group<grp.h>groupgetgrnam、getgrgid
阴影/etc/shadow<shadow.h>spwdgetspnam
主机/etc/hosts<netdb.h>hostentgetnameinfo、getaddrinfo
网络/etc/networks<netdb.h>netentgetnetbyname、getnetbyaddr
协议/etc/protocols<netdb.h>protoentgetprotobyname、getprotobynumber
服务/etc/services<netdb.h>serventgetservbyname、getservbyport

8、登录账户记录
  • 大多数系统提供两个文件
    • utmp:记录当前登录到系统的各个用户
    • wtmp:跟踪各个登录和注销事件
  • who程序读取utmp文件,last读取wtmp

9、系统标识
#include <sys/utsname.h>
int uname(struct utsname *buf);
struct utsname {
   char sysname[];    /* Operating system name (e.g., "Linux") */
   char nodename[];   /* Name within "some implementation-defined
                           network" */
   char release[];    /* Operating system release (e.g., "2.6.28") */
   char version[];    /* Operating system version */
   char machine[];    /* Hardware identifier */
#ifdef _GNU_SOURCE
   char domainname[]; /* NIS or YP domain name */
#endif
};
  • 函数返回主机与操作系统有关的信息
  • utsname结构中的信息可用uname命令打印
#include <unistd.h>
int gethostname(char *name, size_t len);
int sethostname(const char *name, size_t len);
  • 函数返回设置主机名
  • 最大长度是HOST_NAME_MAX(65、64)
  • 主机名在系统自举时设置,由/etc/rc或init取自一个启动文件

10、时间和日期例程
  • time函数返回当前时间和日期
    time_t time(time_t *tloc);
    
    • 协调统一时而非本地时间
    • 可自动转换到夏令时
    • 将时间和日期作为一个量值保存
  • clock_gettime()可获取指定时钟的时间
  • clock_getres 把参数res指向的结构体初始化为clk_id参数对应的时钟精度
  • clock_settime 对特定时钟设置精度
    #include <time.h>
    int clock_getres(clockid_t clk_id, struct timespec *res);
    int clock_gettime(clockid_t clk_id, struct timespec *tp);
    int clock_settime(clockid_t clk_id, const struct timespec *tp);
    Link with -lrt (only for glibc versions before 2.17).
    struct timespec {
       time_t   tv_sec;        /* seconds */
       long     tv_nsec;       /* nanoseconds */
    };
    
    标识符(clk_id)选项说明
    CLOCK_REALTIME实时系统时间
    CLOCK_REALTIME_COARSE
    CLOCK_MONOTONIC不带负跳数的实时系统时间
    CLOCK_MONOTONIC_COARSE
    CLOCK_MONOTONIC_RAW
    CLOCK_BOOTTIME
    CLOCK_PROCESS_CPUTIME_ID调用进程的CPU时间
    CLOCK_THREAD_CPUTIME_ID调用线程的CPU时间
  • gettimeofday 提供了更高的精度
    int gettimeofday(struct timeval *tv, struct timezone *tz);
    struct timeval {
       time_t      tv_sec;     /* 秒 */
       suseconds_t tv_usec;    /* 微秒 */
    };
    struct timezone {
       int tz_minuteswest;     /* minutes west of Greenwich */
       int tz_dsttime;         /* type of DST correction */
    };
    
    • tz的唯一合法值是null,其他值将产生不确定的结果。
    • gettimeofday将协调统一时间的秒数存放到结构体tv中,
    • 除了月日字段,其他值都以0开始
    • 夏令时生效则夏令时标志为正,否则为0,不可用为负
  • 下面的函数转换日历时间
    struct tm *gmtime(const time_t *timep);
    struct tm *localtime(const time_t *timep);
    
    • localtime将日历时间转换为本地时间
    • gmtime将日历时间转换成协调统一时间的分解结构
  • mktime函数功能与上述相反
    time_t mktime(struct tm *tm);
    
  • strftime类似printf,可通过多个参数定制产生字符串
    size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
    
    • asctime和ctime已被标记为弃用(因为容易溢出缓冲区)



  • 附1:psd.c //gcc psd.c -o psd -lcrypt
#define _XOPEN_SOURCE
#include <stdio.h>
#include <string.h>
#include <shadow.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#define _GNU_SOURCE
#include <crypt.h>
#include <pwd.h>

#define STRLEN 128
# if !defined __cplusplus && __GNUC_PREREQ (3, 3)
#  define __THROW	__attribute__ ((__nothrow__ __LEAF))
#  define __THROWNL	__attribute__ ((__nothrow__))
#  define __NTH(fct)	__attribute__ ((__nothrow__ __LEAF)) fct
# else
#  if defined __cplusplus && __GNUC_PREREQ (2,8)
#   define __THROW	throw ()
#   define __THROWNL	throw ()
#   define __NTH(fct)	__LEAF_ATTR fct throw ()
#  else
#   define __THROW
#   define __THROWNL
#   define __NTH(fct)	fct
#  endif
# endif

#if __GNUC_PREREQ (3,3)
# define __nonnull(params) __attribute__ ((__nonnull__ params))
#else
# define __nonnull(params)
#endif

#if __GNUC__ >= 3
# define __glibc_unlikely(cond)	__builtin_expect ((cond), 0)
# define __glibc_likely(cond)	__builtin_expect ((cond), 1)
#else
# define __glibc_unlikely(cond)	(cond)
# define __glibc_likely(cond)	(cond)
#endif


int checkPasswd(const char *userName,const char *passwd)__THROW __nonnull ((1, 2));
int main()
{
    char userName[STRLEN];
    char passwd[STRLEN];
    fprintf(stderr,"input user name:");
    fgets(userName,STRLEN,stdin);
    userName[strlen(userName)-1]=0;
    if(__glibc_unlikely(!getpwnam(userName))){
        fprintf(stderr,"查找用户名 %s 失败,请确认用户存在\n",userName);
        exit(EXIT_FAILURE);
    }
    fprintf(stderr,"请输出用户 %s 的密码:",userName);
    fgets(passwd,STRLEN,stdin);
    passwd[strlen(passwd)-1]=0;
    checkPasswd(userName,passwd);
    exit(EXIT_SUCCESS);
}

int checkPasswd(const char *userName,const char *passwd)
{
    struct spwd *sp=NULL;
    sp = getspnam(userName);
    assert(sp != NULL);
    char *encrypted = (char *)crypt(passwd, sp->sp_pwdp);
    if(__glibc_unlikely(encrypted == NULL)){
        fprintf(stderr,"加密失败:%s\n",strerror(errno));
        goto exit;
    }
    int encryptedLen = 2;
    if(sp->sp_pwdp[0] == '$'){//使用了额外的加密算法
        if(sp->sp_pwdp[0]=='1')
            encryptedLen = 22;
        else if(sp->sp_pwdp[0]=='5')
            encryptedLen = 43;
        else if(sp->sp_pwdp[0]=='6')
            encryptedLen = 86;
        else    //其他情况比如'2a',man手册没有说明
            encryptedLen = 13;
    }
    if (memcmp(sp->sp_pwdp, encrypted,encryptedLen) == 0){
        printf("密码正确\n");
        return 0;
    }else{
        printf("密码错误\n");
    }
exit:
    return -1;
}



附2: /usr/include/x86_64-linux-gnu/sys/cdefs.h

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值