- 先看一个小程序:
简单说明一下这个小程序:就是验证用户是否存在,存在则验证他的密码(我们知道用户的密码当然不是明文存储的)。这篇博客大概就是讲这个程序实现的相关基础内容(文末有程序源码)
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> | passwd | getpwnam、getpwuid |
组 | /etc/group | <grp.h> | group | getgrnam、getgrgid |
阴影 | /etc/shadow | <shadow.h> | spwd | getspnam |
主机 | /etc/hosts | <netdb.h> | hostent | getnameinfo、getaddrinfo |
网络 | /etc/networks | <netdb.h> | netent | getnetbyname、getnetbyaddr |
协议 | /etc/protocols | <netdb.h> | protoent | getprotobyname、getprotobynumber |
服务 | /etc/services | <netdb.h> | servent | getservbyname、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;
}