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