这章主要讲了存放密码相关的文件和组相关的文件以及编程获取此类信息相关的函数,和时间日期相关的函数。
1. /etc/passwd 和 /etc/shadow 文件
/etc/passwd
历史上用户信息和密码是存储在/etc/passwd 中,但是这个文件任何用户都是可读不太安全,所以就把加密后的密码放到了/etc/shadow文件中,这个文件对其他人来说是没有任何权限的。
先回顾一下/etc/passwd 中的字段是怎么一个形式
用户名: 密码:uid : gid : 注释 :起始目录 : 默认shell
root : x: 0 : 0 : root : /root : /bin/sh
nobody : x : 65534 : 65534 : nobody : / home :/bin/sh
squid : x : 23 : 23 : : / var/spool/squid :/dev/null
从这三列我们可以看到squid 的shell 是/dev/null,这阻止了任何用户的登录,这个作为系统的服务使用。
nobody 的uid 和gid 都为65534,目的是任何人都能登录到系统,但此用户可存取的文件只是大家都可读、写的文件。
——————————————————————————————————————
//获取 /etc/passwd项的函数
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t u i d) ; //由ls(1)函数使用,将i节点的数值和用户ID映射为用户登录名
struct passwd *getpwnam(const char * n a m e) ;//由login(1)函数使用,当登录时键入用户名
//查看这个passwd文件函数
struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);
struct passwd*函数返回:若成功则为指针,若出错则为 NULL
——————————————————————————————————————
看一下struct passwd 结构:
struct passwd{
char *pw_name; //用户名
char *pw_passwd; //加密口令(单向密码算法处理)
uid_t pw_uid; //数值用户ID
gid_t pw_gid; //数值组ID
char *pw_gecos; //注释字段
char *pw_dir; //初始工作目录
char *pw_shell; //初始shell(用户程序)
}
前两个函数只能获取一个与uid或者name 想对应的passwd结构,对于遍历整个文件,我们需要使用下面的三个函数,看一下例子就能明白。
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
int main(){
struct passwd *pwd;
pwd = getpwnam("root");
printf("username is: %s\n",pwd->pw_name);
printf("userid is: %d\n",pwd->pw_uid);
printf("groupid is: %d\n",pwd->pw_gid);
printf("usergecos is: %s\n",pwd->pw_gecos);
printf("usershell is: %s\n",pwd->pw_shell);
printf("userhome is: %s\n",pwd->pw_dir);
printf("userpasswd is: %s\n",pwd->pw_passwd);
//遍历文件
setpwent(); //反绕它使用的文件,就类似于数据的操作,将游标置为最顶端,第一条记录的上面
while( (pwd = getpwent())!= NULL ){ //返回下一条记录,即游标下移
{
printf("username is: %s\n",pwd->pw_name);
printf("userid is: %d\n",pwd->pw_uid);
printf("groupid is: %d\n",pwd->pw_gid);
printf("usergecos is: %s\n",pwd->pw_gecos);
printf("usershell is: %s\n",pwd->pw_shell);
printf("userhome is: %s\n",pwd->pw_dir);
printf("userpasswd is: %s\n",pwd->pw_passwd);
}
}
endpwent(); //关闭passwd文件
return 0;
}
/etc/shadow
这个文件的用途当然是保存加密的密码了,为了标示是哪个用户的密码,当然必须有一位 这里用的是用户名。
然后我们系统的看看,究竟还有些其他的什么字段.
u:$6$EDjnGJ7f$H6Q27GTL3TY84UDJxHWcldHRtFD8xCGX4sU.bxz6GTTJz0Z8P33mE3O9ZU3PgiJpAnn81EwoRcz3M/04ZZxfk.:15775:0:99999:7:::
字段间以冒号分隔,
第一个字段是账户名;
第二个字段是加密后的密码;
第三个是最近一次修改密码的日期,15775是修改密码的日期与1970年1月1日的相距的天数;
第四个字段是密码不可以被改动的日期,若为0则表示可以随时更改;
第五个字段是密码需要重新更改的天数,99999就表示99999天后强制修改密码;
第六个字段是密码需要修改的提前警告的天数,7就表示在强制修改密码的日期到的前7天提醒用户
第七个字段是密码过期后可以宽限的天数
第八个字段是帐号的失效日期
第九个字段保留
这里通过第七和第八个字段的联合使用就能让一个帐号失效。
当然密码字段比较重要:我们分析一下,很久以前的加密算法比较简单,经过时间的演变就成了这个样子。
格式是 $ID$SALT(8位)$加密密码,SALT是随机产生的一个8位的字符串
ID | Method
─────────────────────────────────────────
1 | MD5
2a | Blowfish (not in mainline glibc; added in some
| Linux distributions)
5 | SHA-256 (since glibc 2.7)
6 | SHA-512 (since glibc 2.7)
//加密函数
char*crypt(const char *key, const char *salt);
#define XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//这个是加密的一个例子
int main(int argc, char *argv[]) {
if ( argc < 2 || (int) strlen(argv[2]) > 16 ) {
printf("usage: %s password salt\n", argv[0]);
printf("--salt must not larger than 16 characters\n");
return;
}
char salt[21];
sprintf(salt, "$6$%s$", argv[2]);
printf("%s\n", crypt((char*) argv[1], (char*) salt));
return;
}
shadow文件的读写权限是0600,需要存取加密口令文件的程序login、passwd等常常设置-用户-ID为root,这样才有权限读写shadow文件
同样shadow提供了类似passwd的函数用来取密码和遍历用。
Description | struct spwd member |
---|---|
user login name | char *sp_namp |
encrypted password | char *sp_pwdp |
days since Epoch of last password change | int sp_lstchg |
days until change allowed | int sp_min |
days before change required | int sp_max |
days warning for expiration | int sp_warn |
days before account inactive | int sp_inact |
days since Epoch when account expires | int sp_expire. |
reserved | unsigned int sp_flag |
——————————————————————————————————————
#include <shadow.h>
struct spwd *getspnam(const char * n a m e) ;
struct spwd*getpwent(void);
void setspent(void);
void endspent(void);
struct spwd*函数返回:若成功则为指针,若出错则为 NULL
——————————————————————————————————————
2.组文件
接下来的有了前面的铺垫,就直接按照惯例,先给出结构,然后给出相应的函数。
Description | struct group member |
|
| |||
---|---|---|---|---|---|---|
group name | char *gr_name |
|
| |||
encrypted password | char *gr_passwd |
|
|
| ||
numerical group ID | int gr_gid |
|
|
| ||
array of pointers to individual user names | char **gr_mem //指针数组,其中每个指针各指向一个属于改组的用户名 |
|
|
|
函数和对passwd文件操作一模一样,换了个袈裟,就不解释了。
#include <grp.h>
struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);
3.附加组
这个的存在是为了避免如果一个用户只能属于一个组,那么会相当麻烦,需要频繁的换组。一个用户就可以加入多个组。
#include <grp.h> /* on Linux */ int getgroups(int gidsetsize, gid_t grouplist[]); //返回附加组ID,error == -1 int setgroups(int ngroups, const gid_t grouplist[]); //ok==0,error == -1 int initgroups(const char *username, gid_t basegid); //ok==0,error == -1,从组文件读取/etc/group文件,用来初始化
4.其他数据总结
对于每个数据文件至少包含三个函数 ,set get end.
Description | Data file | Header | Structure | Additional keyed lookup functions |
---|---|---|---|---|
passwords | /etc/passwd | <pwd.h> | passwd | getpwnam, getpwuid |
groups | /etc/group | <grp.h> | group | getgrnam, getgrgid |
shadow | /etc/shadow | <shadow.h> | spwd | getspnam |
hosts | /etc/hosts | <netdb.h> | hostent | gethostbyname, gethostbyaddr |
networks | /etc/networks | <netdb.h> | netent | getnetbyname, getnetbyaddr |
protocols | /etc/protocols | <netdb.h> | protoent | getprotobyname, getprotobynumber |
services | /etc/services | <netdb.h> | servent | getservbyname, getservbyport |
5.登录账户记录
struct utmp {
char ut_line[8]; /* tty line: "ttyh0", "ttyd0", "ttyp0", ... */
char ut_name[8]; /* login name */
long ut_time; /* seconds since Epoch */
} ;
登录时, login填写这样一个结构,然后将其写入到 utmp文件中,同时也将其添写到 wtmp文件中。
注销时, init进程将utmp文件中相应的记录擦除 ( 每个字节都填以0 ),并将一个新记录添写到wtmp文件中。
读wtmp文件中的该注销记录,其ut _name字段清除为0。在系统再启动时,以及更改系统时间和日期的前后,都在 wtmp文件中添写特殊的记录项。
w h o ( 1 )程序读utmp文件,并以可读格式打印其内容。
6.系统标识
struct utsname {
char sysname[9]; /* name of the operating system */
char nodename[9]; /* name of this node */
char release[9]; /* current release of operating system */
char version[9]; /* current version of this release */
char machine[9]; /* name of hardware type */
} ;
int uname(struct utsname * name) ; //它返回与主机和操作系统有关的信息
7. 关于时间
由U N I X内核提供的基本时间服务是国际标准时间公元 1 9 7 0年1月1日0 0 : 0 0 : 0 0以来经过的秒数,用t i m e _ t表示。我们称它们为日历时间。
time_t time(time_t * calptr) ; //获得当前时间,如果参数非空,时间也存放到calptr中
函数localtime(本地时间)和gmtime(国际标准时间)将日历时间变换成以年、月、日、时、分、秒、周日表示的时间,并将这些存放在一个t m结构中。
struct tm {
/* a broken-down time */
int tm_sec; /* seconds after the minute: [0, 61] */
int tm_min; /* minutes after the hour: [0, 59] */
int tm_hour; /* hours after midnight: [0, 23] */
int tm_mday; /* day of the month: [1, 31] */
int tm_mon; /* month of the year: [0, 11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday: [0, 6] */
int tm_yday; /* days since January 1: [0, 365] */
int tm_isdst; /* daylight saving time flag: <0, 0, >0 */
} ;
struct tm *gmtime(const time_t *calptr ) ;
struct tm *localtime(const time_t *calptr) ;
time_t mktime(struct tm * tmptr) ; //将tm结构转化为秒形式
char *asctime(const struct tm *tmptr ) ; //tm 2 string
char *ctime(const time_t * calptr) ; time_t 2 string
//格式化输出时间
size_t strftime(char *buf, size_t maxsize, const char * format ,const struct tm * tmptr) ;
% a 缩写的周日名 T u e
% A 全周日名 T u e s d a y
% b 缩写的月名 J a n
% B 月全名 J a n u a r y
% c 日期和时间 Tue Jan 14 19:40:30 1992
% d 月日:[01, 31] 1 4
% H 小时(每天2 4小时) :[00, 23] 1 9
% I 小时(上、下午各 1 2小时):[01, 12] 0 7
% j 年日:[001, 366] 0 1 4
% m 月:[01, 12] 0 1
% M 分:[00, 59] 4 0
% p A M / P M P M
% S 秒:[00, 61] 3 0
% U 星期日周数: [00, 53] 0 2
% w 周日:[ 0 =星期日,6 ] 2
% W 星期一周数: [00, 53] 0 2
% x 日期 0 1 / 1 4 / 9 2
% X 时间 1 9 : 4 0 : 3 0
% y 不带公元的年:[00, 991] 9 2
% Y 带公元的年 1 9 9 2
% Z 时区名 M S T
最后是一张图,总结一下关于时间转化的操作。