分类: 系统运维
2012-04-02 17:17:40
让我们深入讨论下客户和服务器的一些被各种类型的在它们之间使用的IPC影响的属性。最简单的关系类型是让子进程fork并exec所需的服务器。 在fork前可以创建两个半双工管道来允许数据双向传输。被执行的服务器可以是一个设置用户ID程序,给予它特殊的权限。同样,服务器可以确定客户的真实 标识符,通过查看它的真实用户ID。(回想8.10节真实用户ID和组用户ID不会通过exec改变。)
随着这种排列,我们可以建立一个打 开服务器。(我们在17.5节展示这种客户-服务器的一个实现。)它为客户端打开文件而不是客户端调用open函数。这种方法,额外的权限检查可以被加 入,在普通UNIX系统用户/组/其他人权限之上。我们假定服务器是一个设置用户ID程序,给了它额外的权限(可能是根权限)。服务器使用客户的真实用户 ID来确定是否给它所请求的文件的访问权限。这种方法,我们可以建立一个服务器,允许他们通常不会有的特定用户权限。
在这个例子里,因为服 务器是父进程的一个子进程,所以这个服务器能做的所有事情就是把文件的内容传回给父进程。尽管这对于普通文件可以工作,但是它不能用在特殊设备文件。我们 想要能够让服务器打开所请求的文件并传回文件的描述符。一个父进程可以传递给一个子进程一个打开的描述符,而一个子进程不能传回一个描述符给父进程(除非 特殊的编程技术被使用,17章)。
我们在早先展示了下一种类型的服务器。这种服务器是一个守护进程,使用某种形式的IPC被所有子进程联 系。我们不能为这种类型的客户-服务器使用管道。需要某种形式的命名IPC,比如FIFO或消息队列。通过FIFO,我们看到每个客户也需要一个独立的 FIFO,如果服务器要把数据发回给客户。如果客户-服务器应用只从客户端发送数据到服务器,那么单个被熟知的FIFO就足够。(系统V行打印机假脱机程 序使用这种形式的客户-服务器布局。客户端是lp命令,而服务器是lpsched守护进程。单个FIFO被使用,因为数据只从客户端流向服务器。没有东西 被发送回给客户端。)
使用消息队列有多种可能性。
1、单个队列可以在服务器和所有客户端之间使用,使用每个消息的类型域来指 出消息的接受者。例如,客户可以用1的类型域来发送它们的请求。客户端进程ID必须包含在请求里。服务器只接受1的类型域的消息(msgrcv的第4个参 数),而客户端只接受类型域和它们进程ID相等的消息。
2、另一种方法是,每个客户端可以使用独立的消息队列。在发送第一个请求给一个服务 器之前,每个客户端创建它自己的消息队列,用IPC_PRIVATE的关键字。服务器也有它自己的队列,使用一个被所有客户知道的关键字或标识符。客户端 发送它的第一个请求给服务器的被熟知的队列,而且请求必须包含客户队列的消息队列ID。服务器发送它第一个请求到这个客户队列,而所有将来的请求和响应都 在这个队列上交换。
这个技术的一个问题是每个客户相关的队列通常只有单个消息:为服务器的一个请求或为一个客户的响应。这似乎是有限系统资源的浪费(一个消息队列),而一个FIFO可以用来代替。另一个问题是服务器必须从多个队列里读消息。select或poll都不能在消息队列上工作。
这两种使用消息队列的技术可以用共享内存段和一个同步方法(一个信号量或记录锁)来实现。
这 种客户-服务器关系(客户和服务器是无关进程)的问题是服务器要精确地标识客户。除非服务器正执行一个非特权的操作,否则服务器很需要知道客户是谁。例 如,如果服务器是一个设置用户ID程序,那么就很需要。尽管所有这些形式的IPC都经过内核,但是它们没有提供设置来让内核标识发送者。
使 用消息队列,如果单个队列在客户和服务器之间使用(例如为了同时只有单个消息在这个队列上),那么队列的msg_lspid包含另一个进程的进程ID。但 是当写服务器时,我们想要客户端的有效用户ID,而不是它的进程ID。没有可移植的通过进程ID得到有效有户ID的方法。(自然地,内核在进程表项里维护 了这两个值,但是除了翻查内核的内存,我们不能通过某个得到另一个。)
我们将使用17.3节里的以下技术来允许服务器标识客户。相同的技术 可以和FIFO、消息队列、信号量、或共享内存一起使用。为了以下的描述,假定FIFO被使用。客户必须创建它自己的FIFO并设置FIFO的文件访问权 限,以便只有用户读和用户写是开启的。我们假定服务器有超级用户权限(否则它很可能不关心客户的真实身份),所以服务器仍可以读和写这个FIFO。当服务 器在被熟知的FIFO上接收到客户的第一个请求时(它必须包含客户客户相关的FIFO的标识符),服务器在客户相关的FIFO上调用stat或 fstat。服务器假定客户的用效用户ID是FIFO的属主(stat结构体的st_uid域)。服务器验证只有用户读和用户写权限被开启。作为另一个检 查,服务器也应该查看和FIFO关联的三个时间(stat结构体里的st_atime、st_mtime、和st_ctime域)来验证它是是最近的(例 如不超过15或30秒之前)。如果一个恶意用户可以创建一个FIFO,用其它人作为属主并设置文件的权限位为用户读和用户写,那么系统有其它基本的安全问 题。
为了和XSI IPC一起使用这个技术,回想和每个消息队列、信号量、和共享内存标识符相关联的ipc_perm结构体标识了IPC结构体的创建者(cuid和cgid 域)。作为一个使用FIFO的例子,服务器应该需要客户端创建IPC结构体并让客户端设置访问权限为只有用户读和用户写。和IPC结构相关的时间同样也需 要被服务器验证为最近的(因为这些IPC结构体挂住直到显式地被删除。)
我们将在17.2.2节看到一个好的多的方式来为内核执行这个身份认证来提供客户的有效用户ID和有效组ID。这通过STREAMS子系统完成,当文件描述符在两个进程间传递时。