Merge from vendor branch FILE:
[dragonfly.git] / contrib / libpam / libpamc / pamc_load.c
1 /*
2  * $Id: pamc_load.c,v 1.1.1.1 2000/06/20 22:11:26 agmorgan Exp $
3  *
4  * Copyright (c) 1999 Andrew G. Morgan <morgan@ftp.kernel.org>
5  *
6  * pamc_load
7  */
8
9 #include "libpamc.h"
10
11 static int __pamc_exec_agent(pamc_handle_t pch, pamc_agent_t *agent)
12 {
13     char *full_path;
14     int found_agent, length, reset_length, to_agent[2], from_agent[2];
15     int return_code = PAM_BPC_FAIL;
16
17     if (agent->id[agent->id_length] != '\0') {
18         PAM_BP_ASSERT("libpamc: internal error agent_id not terminated");
19     }
20
21     for (length=0; (length < agent->id_length); ++length) {
22         switch (agent->id[length]) {
23         case '/':
24             D(("ill formed agent id"));
25             return PAM_BPC_FAIL;
26         }
27     }
28     
29     /* enough memory for any path + this agent */
30     reset_length = 3 + pch->max_path + agent->id_length;
31     D(("reset_length = %d (3+%d+%d)",
32        reset_length, pch->max_path, agent->id_length));
33     full_path = malloc(reset_length);
34     if (full_path == NULL) {
35         D(("no memory for agent path"));
36         return PAM_BPC_FAIL;
37     }
38
39     found_agent = 0;
40     for (length=0; pch->agent_paths[length]; ++length) {
41         struct stat buf;
42
43         D(("path: [%s]", pch->agent_paths[length]));
44         D(("agent id: [%s]", agent->id));
45
46         sprintf(full_path, "%s/%s", pch->agent_paths[length], agent->id);
47
48         D(("looking for agent here: [%s]\n", full_path));
49         if (stat(full_path, &buf) == 0) {
50             D(("file existis"));
51             found_agent = 1;
52             break;
53         }
54     }
55
56     if (! found_agent) {
57         D(("no agent was found"));
58         goto free_and_return;
59     }
60         
61     if (pipe(to_agent)) {
62         D(("failed to open pipe to agent"));
63         goto free_and_return;
64     }
65
66     if (pipe(from_agent)) {
67         D(("failed to open pipe from agent"));
68         goto close_the_agent;
69     }
70
71     agent->pid = fork();
72     if (agent->pid == -1) {
73
74         D(("failed to fork for agent"));
75         goto close_both_pipes;
76
77     } else if (agent->pid == 0) {
78
79         int i;
80
81         dup2(from_agent[1], STDOUT_FILENO);
82         dup2(to_agent[0], STDIN_FILENO);
83
84         /* we close all of the files that have filedescriptors lower
85            and equal to twice the highest we have seen, The idea is
86            that we don't want to leak filedescriptors to agents from a
87            privileged client application.
88
89            XXX - this is a heuristic at this point. There is a growing
90             need for an extra 'set param' libpamc function, that could
91             be used to supply info like the highest fd to close etc..
92         */
93
94         if (from_agent[1] > pch->highest_fd_to_close) {
95             pch->highest_fd_to_close = 2*from_agent[1];
96         }
97
98         for (i=0; i <= pch->highest_fd_to_close; ++i) {
99             switch (i) {
100             case STDOUT_FILENO:
101             case STDERR_FILENO:
102             case STDIN_FILENO:
103                 /* only these three remain open */
104                 break;
105             default:
106                 (void) close(i); /* don't care if its not open */
107             }
108         }
109
110         /* we make no attempt to drop other privileges - this library
111            has no idea how that would be done in the general case. It
112            is up to the client application (when calling
113            pamc_converse) to make sure no privilege will leak into an
114            (untrusted) agent. */
115
116         /* we propogate no environment - future versions of this
117            library may have the ability to audit all agent
118            transactions. */
119
120         D(("exec'ing agent %s", full_path));
121         execle(full_path, "pam-agent", NULL, NULL);
122
123         D(("exec failed"));
124         exit(1);
125
126     }
127
128     close(to_agent[0]);
129     close(from_agent[1]);
130
131     agent->writer = to_agent[1];
132     agent->reader = from_agent[0];
133
134     return_code = PAM_BPC_TRUE;
135     goto free_and_return;
136
137 close_both_pipes:
138     close(from_agent[0]);
139     close(from_agent[1]);
140
141 close_the_agent:
142     close(to_agent[0]);
143     close(to_agent[1]);
144
145 free_and_return:
146     memset(full_path, 0, reset_length);
147     free(full_path);
148
149     D(("returning %d", return_code));
150
151     return return_code;
152 }
153
154 /*
155  * has the named agent been loaded?
156  */
157
158 static int __pamc_agent_is_enabled(pamc_handle_t pch, const char *agent_id)
159 {
160     pamc_agent_t *agent;
161
162     for (agent = pch->chain; agent; agent = agent->next) {
163         if (!strcmp(agent->id, agent_id)) {
164             D(("agent already loaded"));
165             return PAM_BPC_TRUE;
166         }
167     }
168
169     D(("agent is not loaded"));
170     return PAM_BPC_FALSE;
171 }
172
173 /*
174  * has the named agent been disabled?
175  */
176
177 static int __pamc_agent_is_disabled(pamc_handle_t pch, const char *agent_id)
178 {
179     pamc_blocked_t *blocked;
180
181     for (blocked=pch->blocked_agents; blocked; blocked = blocked->next) {
182         if (!strcmp(agent_id, blocked->id)) {
183             D(("agent is disabled"));
184             return PAM_BPC_TRUE;
185         }
186     }
187
188     D(("agent is not disabled"));
189     return PAM_BPC_FALSE;
190 }
191
192 /*
193  * disable an agent
194  */
195
196 int pamc_disable(pamc_handle_t pch, const char *agent_id)
197 {
198     pamc_blocked_t *block;
199
200     if (pch == NULL) {
201         D(("pch is NULL"));
202         return PAM_BPC_FALSE;
203     }
204
205     if (agent_id == NULL) {
206         D(("agent_id is NULL"));
207         return PAM_BPC_FALSE;
208     }
209
210     if (__pamc_agent_is_enabled(pch, agent_id) != PAM_BPC_FALSE) {
211         D(("agent is already loaded"));
212         return PAM_BPC_FALSE;
213     }
214
215     if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) {
216         D(("agent is already disabled"));
217         return PAM_BPC_TRUE;
218     }
219
220     block = calloc(1, sizeof(pamc_blocked_t));
221     if (block == NULL) {
222         D(("no memory for new blocking structure"));
223         return PAM_BPC_FALSE;
224     }
225
226     block->id = malloc(1 + strlen(agent_id));
227     if (block->id == NULL) {
228         D(("no memory for agent id"));
229         free(block);
230         return PAM_BPC_FALSE;
231     }
232
233     strcpy(block->id, agent_id);
234     block->next = pch->blocked_agents;
235     pch->blocked_agents = block;
236
237     return PAM_BPC_TRUE;
238 }
239
240 /*
241  * force the loading of a particular agent
242  */
243
244 int pamc_load(pamc_handle_t pch, const char *agent_id)
245 {
246     pamc_agent_t *agent;
247     int length;
248
249     /* santity checking */
250
251     if (pch == NULL) {
252         D(("pch is NULL"));
253         return PAM_BPC_FALSE;
254     }
255
256     if (agent_id == NULL) {
257         D(("agent_id is NULL"));
258         return PAM_BPC_FALSE;
259     }
260
261     if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) {
262         D(("sorry agent is disabled"));
263         return PAM_BPC_FALSE;
264     }
265     
266     length = strlen(agent_id);
267
268     /* scan list to see if agent is loaded */
269
270     if (__pamc_agent_is_enabled(pch, agent_id) == PAM_BPC_TRUE) {
271         D(("no need to load an already loaded agent (%s)", agent_id));
272         return PAM_BPC_TRUE;
273     }
274
275     /* not in the list, so we need to load it and add it to the head
276        of the chain */
277
278     agent = calloc(1, sizeof(pamc_agent_t));
279     if (agent == NULL) {
280         D(("no memory for new agent"));
281         return PAM_BPC_FALSE;
282     }
283     agent->id = calloc(1, 1+length);
284     if (agent->id == NULL) {
285         D(("no memory for new agent's id"));
286         goto fail_free_agent;
287     }
288     memcpy(agent->id, agent_id, length);
289     agent->id[length] = '\0';
290     agent->id_length = length;
291
292     if (__pamc_exec_agent(pch, agent) != PAM_BPC_TRUE) {
293         D(("unable to exec agent"));
294         goto fail_free_agent_id;
295     }
296
297     agent->next = pch->chain;
298     pch->chain = agent;
299     
300     return PAM_BPC_TRUE;
301
302 fail_free_agent_id:
303
304     memset(agent->id, 0, agent->id_length);
305     free(agent->id);
306
307     memset(agent, 0, sizeof(*agent));
308
309 fail_free_agent:
310
311     free(agent);
312     return PAM_BPC_FALSE;
313 }
314
315 /*
316  * what's a valid agent name?
317  */
318
319 int __pamc_valid_agent_id(int id_length, const char *id)
320 {
321     int post, i;
322
323     for (i=post=0 ; i < id_length; ++i) {
324         int ch = id[i++];
325
326         if (isalpha(ch) || isdigit(ch) || (ch == '_')) {
327             continue;
328         } else if (post && (ch == '.')) {
329             continue;
330         } else if ((i > 1) && (!post) && (ch == '@')) {
331             post = 1;
332         } else {
333             D(("id=%s contains '%c' which is illegal", id, ch));
334             return 0;
335         }
336     }
337
338     if (!i) {
339         D(("length of id is 0"));
340         return 0;
341     } else {
342         return 1;                       /* id is valid */
343     }
344 }
345
346 /*
347  * building a tree of available agent names
348  */
349
350 static pamc_id_node_t *__pamc_add_node(pamc_id_node_t *root, const char *id,
351                                        int *counter)
352 {
353     if (root) {
354
355         int cmp;
356
357         if ((cmp = strcmp(id, root->agent_id))) {
358             if (cmp > 0) {
359                 root->right = __pamc_add_node(root->right, id,
360                                               &(root->child_count));
361             } else {
362                 root->left = __pamc_add_node(root->left, id,
363                                              &(root->child_count));
364             }
365         }
366
367         return root;
368
369     } else {
370
371         pamc_id_node_t *node = calloc(1, sizeof(pamc_id_node_t));
372
373         if (node) {
374             node->agent_id = malloc(1+strlen(id));
375             if (node->agent_id) {
376                 strcpy(node->agent_id, id);
377             } else {
378                 free(node);
379                 node = NULL;
380             }
381         }
382
383         (*counter)++;
384         return node;
385     }
386 }
387
388 /*
389  * drop all of the tree and any remaining ids
390  */
391
392 static pamc_id_node_t *__pamc_liberate_nodes(pamc_id_node_t *tree)
393 {
394     if (tree) {
395         if (tree->agent_id) {
396             free(tree->agent_id);
397             tree->agent_id = NULL;
398         }
399
400         tree->left = __pamc_liberate_nodes(tree->left);
401         tree->right = __pamc_liberate_nodes(tree->right);
402
403         tree->child_count = 0;
404         free(tree);
405     }
406
407     return NULL;
408 }
409
410 /*
411  * fill a list with the contents of the tree (in ascii order)
412  */
413
414 static void __pamc_fill_list_from_tree(pamc_id_node_t *tree, char **agent_list,
415                                        int *counter)
416 {
417     if (tree) {
418         __pamc_fill_list_from_tree(tree->left, agent_list, counter);
419         agent_list[(*counter)++] = tree->agent_id;
420         tree->agent_id = NULL;
421         __pamc_fill_list_from_tree(tree->right, agent_list, counter);
422     }
423 }
424
425 /*
426  * get a list of the available agents
427  */
428
429 char **pamc_list_agents(pamc_handle_t pch)
430 {
431     int i, total_agent_count=0;
432     pamc_id_node_t *tree = NULL;
433     char **agent_list;
434
435     /* loop over agent paths */
436
437     for (i=0; pch->agent_paths[i]; ++i) {
438         DIR *dir;
439
440         dir = opendir(pch->agent_paths[i]);
441         if (dir) {
442             struct dirent *item;
443
444             while ((item = readdir(dir))) {
445
446                 /* this is a cheat on recognizing agent_ids */
447                 if (!__pamc_valid_agent_id(strlen(item->d_name),
448                                            item->d_name)) {
449                     continue;
450                 }
451
452                 tree = __pamc_add_node(tree, item->d_name, &total_agent_count);
453             }
454
455             closedir(dir);
456         }
457     }
458
459     /* now, we build a list of ids */
460     D(("total of %d available agents\n", total_agent_count));
461
462     agent_list = calloc(total_agent_count+1, sizeof(char *));
463     if (agent_list) {
464         int counter=0;
465
466         __pamc_fill_list_from_tree(tree, agent_list, &counter);
467         if (counter != total_agent_count) {
468             PAM_BP_ASSERT("libpamc: internal error transcribing tree");
469         }
470     } else {
471         D(("no memory for agent list"));
472     }
473
474     __pamc_liberate_nodes(tree);
475
476     return agent_list;
477 }