Linux应用程序开发学习笔记

    技术2022-05-11  71

    本文是《Linux应用程序开发》(第二版)的读书笔记。通过第10章和第28章的学习,来更明确一些概念,深入对linux的安全设置的理解。         1. pid        我们描述进程的最基本的属性的时候,使用它的进程标识符pid和其父进程的pid。pid是个唯一的识别运行进程的整数。存放在pid_t结构的的变量中。使用如下两个函数得到其数值。        pid_t getpid(void)            pid_t getppid(void) 如果想对这连个函数进一步的了解,可以man一下。                这里我们需要了解以下几种场景:        1)当一个新进程创建时,原来的进程就是其父进程,新的子进程结束时会通知该父进程。        2)当进程消亡时,它的退出状态被保留在内核的进程表中,直到父进程请求它。而僵尸进程就是为了保存退出状态而保留的进程。当僵尸进程的退出状态被收集起来后,就会将僵尸进程从进程表中删除。        3)如果父进程退出了,子进程成为孤儿进程,这样的子进程成为init的子进程。int的pid为1,负责收集父进程已经消亡的进程的退出状态。     2.信用状(不知道为什么翻译成这样怪的东西) 进程具有uid和gid用于安全。但为了解决比如不同组用户为可以访问同一个文件而必须频繁切换他们的组标识来获得访问权限的问题。在BSD4.3引用了补充组(supplemental group)的概念。即除了保持对主组标识符的安全检查外,还可以查看补充组。

    sysconf()宏的_SC_NGROUPS_MAX定义了一个进程最多属于补充组的数目。在linux2.6以后的版本中,_SC_NGROUPS_MAX是65535。

    设置进程的组列表:int setgroups(size_t num,const gid *list);

    下面是对到进程所属的补充组的列表gid_t *groupList;int numGroups;numGroups=getgroups(0,groupList);if(numGroups){   groupList=alloca(numGroups * sizeof(gid_t));//动态的分配内存   getgroups(numGroups,groupList);//调用函数返回补充组数}

    我们可以是使用系统守护进程和setuid/setgid程序来对用户标识符及组标识符进行管理。

    1)系统守护进程就是那些在系统中运行并等待外部刺激的做出动作的程序。我们可以看到ftp守护进程最初是以root运行,但当一个请求来的时候它需要切换到登陆用户的uid,但想切换回root的权限时发现很难,解决问题的方法是:使用用户标识符(当然还有组标识符)来解决问题。

        这里把用户标识符分为:真实的用户标识符(real uid)、已保存的用户标识符(saved uid)、有效用户标识符(effective uid)。linux还有第四种就是文件标识符。    有效用户标识符是用于所有的安全检查,且是进程当前唯一有效的用户标识符。        利用上面的标识符,其解决问题的过程是这样的: 任何进程可以将它的有效用户标识符改为它的已保存或真实用户标识符相同的值。也就是说当程序想改变它的有效用户标识符时候才检查已保存和真实用户的标识符。但要注意的是,只有作为root运行的程序才能将他们的有效用户标识符改为任何值。        应用到ftp的例子的解决过程就是:ftp是以root运行的,当用户登陆时,守护进程将它的有效用户标识符号设置此登陆用户的uid,保存已保存和真实用户标识符为0。当需要做root权限才能做事的时候,将有效标识符设置为0。完成动作后,重置有效用户的标识符为登陆用户的标识符。

    2)使用setuid和setgid。 用户可以改变自己的密码,但只有root才有权利写passwd文件,这是如何做到的呢?为适应这种需要,在程序的权限中设置了特殊位(也就是文件权限常量中的setuid位和setgid位)。这样,它就可以与拥有程序文件的用户相同的有效的用户标识符运行。这样的程序被称为setuid或setgid的可执行程序。这样的程序在系统中是危险的。容易被黑客利用。

    这里是一个可以找出setxid程序的perl脚本。requre "find.pl";sub wanted{     $mode-(lstat($_))[2];     print"$name/n" if($mode & 02000)||($mode & 04000);}  通过简单的修改这个脚本你可以找到任何你想得到的拥有特定特殊位的程序。

    linux支持POSIX和BSD采用的两种略微不同的途径。BSD使用如下的函数:他们在<unistd.h>中定义int setreuid(uid_t,uid_t euid)//设置当前进程真实用户标识符为ruid有效标识符为euidint setregid(gid_t,gid_t,egit)POSIX使用如下的函数:int setuid(uid_t uid)//由普通用户调用,将当前进程的有效用户标识符设置为uid.若是由有效用户标识符为0的进程所调用,则将真实,有效和已保存用户标识符设置为uid。int setgid(gid_t,gid)

    3.文件系统用户标识符我们前面提到有4种用户标识符,这里说的就是最后一种。针对linux NFS服务器当使用setreuid()切换uid后,使这时的用户可以拥有杀掉NFS服务器进程的权限这种情况。linux使用独立的用户标识符检查文件系统的访问。

    这样进程的有效标识符改变了,进程的文件标识符就设置为进程的新的有效标识符。对大部分应用程序来说是透明的。但有特殊需要的话,就要调用如下的系统调用。int setfsuid(uid_t uid);

    文件系统标识符可以设置为其他三种的任意一个。

    4.用户识别<pwd.h>和<grp.h>里分别定义了/etc/passwd和/etc/group里的数据结构。利用ID查询名字:getpwuid()和getgrgid()利用名字查询ID:getpwname()和getgrname()上面的四个函数都调用底层函数getpwent()和getgrent().

    getpwuid()的一个实现:struct passwd *getpwuid(uid_t uid){  //利用ID查询,返回指向一个passwd结构的指针     struct passwd *pw;          while(pw==getpwent()){        if(!pw)           break;//error occurred,fall through to error processing        if(pw->pw_uid==uid){             endpwent();              return(pw);            }        }   }

    5.一个例子

    /*id.c*/

    #include<grp.h>#include<pwd.h>#include<sys/types.h>#include<stdlib.h>#include<stdio.h>#include<string.h>#include<unistd.h>

    void usage(int die,char*error){    fprintf(stderr,"Usage:id [<username>]/n");    if(error)fprintf(stderr,"%s/n",error);    if(die)exit(die);}

    void die(char*error){    if(error)fprintf(stderr,"%s/n",error);    exit(3);}

    int main(int argc,const char*argv[]){    struct passwd*pw ;    struct group*gp ;    int current_user=0 ;    uid_t id ;    int i ;        if(argc>2)    usage(1,NULL);    if(argc==1)    {        //如果没有命令行参数,则查找并报告当前运行它的用户的相关信息        id=getuid();        current_user=1 ;        //获得用户ID的password记录项         if(!(pw=getpwuid(id)))usage(1,"Username does not exist");    }    else     {        //得到参数的用户ID        if(!(pw=getpwnam(argv[1])))usage(1,"Username does not exist");        id=pw->pw_uid ;    }        printf("uid=%d(%s)",id,pw->pw_name);    //打印组的数字编号和名字    if((gp=getgrgid(pw->pw_gid)))printf("gid=%d(%s)",pw-pw_gid,gp->gr_name);        if(current_user)    {        gid_t*gid_list ;        int gid_size ;                if(getuid()!=geteuid())        {            id=geteuid();            if(!pw=getpwuid(id))            usage(1,"Username does not exist");            printf("euid=%d(%s), id, pw->pw_name");        }                if(getgid()!=getegid())        {            id=getegid();            if(!(gp=getgrgid(id)))            usage(1,"Group does not exist");            printf("egid=%d(%s)",id,gp->gr_name);        }                //打印补充组        /*use getgroups interface to get current groups*/        gid_size=getgroups(0,NULL);        if(gid_size)        {            gid_list=malloc(gid_size*sizeof(gid_t));            getgroups(gid_size,gid_list);                        for(i=0;i<gid_size;i++)            {                if(!gp=getgrgid(gid_list[i]))                die("Group does not exist");                printf("%s%d(%s)",(i==0)?" groups=":",",gp->gr_gid,gp->gr_name);            }                        free(gid_list);        }    }    else     {        //get list of groups from group database        i=0 ;        while((gp=getgrent()))        {            char*c=*(gp->gr_men);                        while(c&&*c)            {                if(!strncmp(c,pw->pw_name,16))                {                    printf("%s%d(%s)",(i++==0)?" groups=":",",gp->gr_gid,gp->gr_name);                    c=NULL ;                }                else                 {                    c++;                }            }        }        endgrent();    }        printf("/n");    eixt(0);}

    6.PAMPAM即可挂接的认证模块,是专门用来配置系统身份认证过程的一个规范和库函数。它的库函数提供了进行身份认证验证、改变认证信息的的标准和简单的接口。http://www.kernel.org/pub/linux/libs/pam详细对其进行了描述。

    下面是利用linux提供的PAM的libpam和libpam mise函数库的例子,在例子的过程中体会PAM编程。

    /*pamexample.c*/

    /* The pamexample program demonstrates some simple PAM handing.You will either have to use the --service command line optionto choose a service name that is already installed ("system-auth"may work,check for/etc/pam.d/system-auth on your system),or install a system file * /etc/pam.d/pamexamplewith the following four lines:#%PAM-1.0auth       required   /lib/security/pam_unix.soaccount    required   /lib/security/pam_unix.sosession    required   /lib/security/pam_limits.so

    Note that if you run this program as a non-root user,there may be system limitations on what you can do;account management may fail,you may not be able to test other user's passwords,and session management may fail,all depending on how the service is configured.*/

    #include<security/pam_appl.h>#include<security/pam_misc.h>#include<popt.h>#include<pwd.h>#include<sys/types.h>#include<stdio.h>#include<stdlib.h>#include<unistd.h>

    /*This struct can be an automatic,but it must not go out of scope betweenpam_start() and pam_end(), so it is easier in simple programs to make it a static instead*/static struct pam_conv my_conv={    /*user TTY conversation function from libpam_misc*/    /*we have no special data to pass to misc_conf*/    misc_conv,NULL,};/*PAM模块实现的策略不直接和用户交互。系统管理员建立策略,所有的策略定义放到一个文件中。模块根据这个文件定义的策略来实现.struct pam_conv是个用于PAM会话的结构体,它说明了如何让应用程序从用户那获得信息。此结构体的定义为#include <security/pam_appl.h>struct pam_conv {    int (*conv)(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);    void *appdata_ptr;};conv()是个指向会话函数的指针,把它pam_message结构中的信息传递给用户,然后在pam_response中返回用户提供的信息。其中在<security/pam_misc.h>头文件中的libpam_misc函数提供了基于文本的控制台会话函数--misc_conv.上面就是使用的该函数。至于定义自己的会话函数。需要struct pam_message {     int msg_style;     const char *msg;};struct pam_response {     char *resp;     int resp_retcode;}; 两个结构体,关于他们的取值可以man下看看。PAM会话函数成功返回PAM_SUCCESS 出现错误返回PAM_CONV_RE*/

    void check_success(pam_handle_t*pamh,int return_code){    if(return_code!=PAM_SUCCESS)    {        fprintf(stderr,"%s/n",pam_streror(pamh,return_code));        exit(1);    }}/*注意上面的handle_t结构体,在PAM的操作过程中,所有的信息就通过这个结构体透明的传输的。它由pam维护*/

    int main(int argc,const char**argv){    pam_handle_t*pamh ;    struct passwd*pw ;    char*username=NULL,*service=NULL ;    int account=1,session=0 ;    int c ;    poptContext optCon ;   //关于popt前缀的词用man看一下。man popt    struct poptOption optionsTable[]=    {        {            "username",'u',POPT_ARG_STRING,&username,0,            "Name of user to authenticate","<username>"         }        ,        {            "service",'s',POPT_ARG_STRING,&service,0,            "Name of service to initialize as (pamsample)","<service>"         }        ,        {            "account",'a',POPT_ARG_NONE|POPT_ARGFLAG_XOR,&account,0,            "toggle whether to do account management (on)",""         }        ,        {            "session",'s',POPT_ARG_NONE|POPT_ARGFLAG_XOR,&session,0,            "toggle whether to start a session (off)",""         }        ,        POPT_AUTOHELP         POPT_TABLEEND     }    ;        optCon=poptGetContext("pamexample",argc,argv,optionsTable,0);    if((c=poptGetNextopt(optCon))<-1)    {        fprintf(stderr,"%s: %s/n",poptBadoption(option,POPT_BADOPTION_NOALIAS),poptStrerror(c));        return 1 ;    }    poptFreeContext(optCon);        if(!service)    {        /* Note that a normal application must not give this option to the user;              it exists here to make it possible to test this application               without making system changes requring root access*/        service="pamexample" ;    }        if(!username)    {        /* default to current user */        if(!(pw=getpwuid(getuid())))        {            fprintf(stderr,"Username does not exists");            exit(1);        }        username=strdup(pw->pw_name);    }        /*     PAM的操作是由pam_start()和pam_close()函数作为标识的。     include <security/pam_appl.h>     int pam_start(constr char *service_name, const char *user,                   const struct pam_conv *pam_conversation, pam_handle_t **pamth);     int pam_end(pam_handle_t *pamh, int pam_status);

         service name是于应用程序对应的唯一的名字。有相同service name的应用程序共享同样的认证机制的配置。user是需要认证的用户名,pam_conversation是会话结构。pamth是个不透明的对象,用于跟踪内部状态。     pam_end()函数用于把pamh的 状态信息清楚,然后通过他们所引用的模块的最后一次操作的状态。*/    c=pam_start(service,username,&my_conv,&pamh);    check_success(pamh,c);        c=pam_authenticate(pamh,0);/* pam_authenticate()函数,按照系统管理员对应用程序的配置(由传入pam_stat()的参数service_name来确定)来认证用户.核对当前用户(用PAM的item指针所指的user确定,而不是当前的uid。)是否当前控制台的用户。核对当前用户是否已经拥有相应等级的服务权限。整个过程是根据名字来核对的。*/    check_success(pamh,c);        if(account)    {            /*if authentication did not succed,account management is not defined*/            c=pam_acct_mgmt(pamh,0);

    /*核对用户是否可以允许所请求的访问,根据PAM_USER,PAM_TTY,PAM_RUSER,PAM_RHOST参数确定访问的方式,这些参数是依据PAM的item参数来传递的。item参数的值是由pam_set_item()函数来设置的。可以用pam_get_item()来查询:       #include <security/pam_appl.h>extern int pam_set_item(pam_handle_t *pamh, int item_tpye, const void *item);extern int pam_get_item(pam_handle_t *pamh, int item_type, const void *item);

    */        check_success(pamh,c);    }        if(session)    {        /*We would fork here if we were going to fork*/        c=pam_open_session(pamh,0);        /*它打开一个新的会话*/        check_success(pamh,c);                /*Note that this will not set uid, gid, or supplemental groups */        c=pam_setcred(pamth,0);        /*它建立了额外的信用状作为替代,使用Kerberros的ticket来赋予用户访问某些资源的权限*/                /* We would drop permissions here if we were going to*/                /*Call a shell that has been "authenticated"*/        printf("Running shell .../n");        system("exec bash -");                /*We would wait4() here if we had forked instead of calling system()*/        c=pam_colse_session(pamh,0);        check_success(pamh,c);    }        /*Real applications would report failure instead of     bailing out as we do in check_success at each stage,     so c might be something other than PAM_SUCCESS in those cases.*/    c=pam_end(pamh,c);    check_success(pamh,c);        return 0 ;

    }

     

    最新回复(0)