]> granicus.if.org Git - linux-pam/blob - modules/pam_selinux/pam_selinux.c
4f0100097b6de9779e9f16460bb72b3a3d018633
[linux-pam] / modules / pam_selinux / pam_selinux.c
1 /******************************************************************************
2  * A module for Linux-PAM that will set the default security context after login
3  * via PAM.
4  *
5  * Copyright (c) 2003 Red Hat, Inc.
6  * Written by Dan Walsh <dwalsh@redhat.com>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, and the entire permission notice in its entirety,
13  *    including the disclaimer of warranties.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. The name of the author may not be used to endorse or promote
18  *    products derived from this software without specific prior
19  *    written permission.
20  *
21  * ALTERNATIVELY, this product may be distributed under the terms of
22  * the GNU Public License, in which case the provisions of the GPL are
23  * required INSTEAD OF the above restrictions.  (This clause is
24  * necessary due to a potential bad interaction between the GPL and
25  * the restrictions contained in a BSD-style copyright.)
26  *
27  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
28  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
31  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
37  * OF THE POSSIBILITY OF SUCH DAMAGE.
38  *
39  */
40
41 #include "config.h"
42
43 #include <errno.h>
44 #include <limits.h>
45 #include <pwd.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53 #include <linux/limits.h>
54
55 #define PAM_SM_AUTH
56 #define PAM_SM_SESSION
57
58 #include <security/pam_modules.h>
59 #include <security/_pam_macros.h>
60 #include <security/_pam_modutil.h>
61
62 #include <libintl.h>
63 #define _(x) gettext(x)
64
65 #ifndef PAM_SELINUX_MAIN
66 #define MODULE "pam_selinux"
67
68 #include <selinux/selinux.h>
69 #include <selinux/get_context_list.h>
70 #include <selinux/flask.h>
71 #include <selinux/selinux.h>
72 #include <selinux/context.h>
73
74 static int
75 send_text (const struct pam_conv *conv, const char *text, int debug)
76 {
77   struct pam_message message;
78   const struct pam_message *messages[] = {&message};
79   struct pam_response *responses;
80   int retval;
81
82   memset(&message, 0, sizeof(message));
83   message.msg_style = PAM_TEXT_INFO;
84   message.msg = text;
85   if (debug)
86     syslog(LOG_NOTICE, MODULE ": %s", message.msg);
87   retval = conv->conv(1, messages, &responses, conv->appdata_ptr);
88   if (responses)
89     _pam_drop_reply(responses, 1);
90   return retval;
91 }
92
93 /*
94  * This function sends a message to the user and gets the response. The caller
95  * is responsible for freeing the responses.
96  */
97 static int
98 query_response (const struct pam_conv *conv, const char *text,
99                 struct pam_response **responses, int debug)
100 {
101   struct pam_message message;
102   const struct pam_message *messages[] = {&message};
103
104   memset(&message, 0, sizeof(message));
105   message.msg_style = PAM_PROMPT_ECHO_ON;
106   message.msg = text;
107
108   if (debug)
109     syslog(LOG_NOTICE, MODULE ": %s", message.msg);
110
111   return conv->conv(1, messages, responses, conv->appdata_ptr);
112 }
113
114 static security_context_t
115 select_context (pam_handle_t *pamh, security_context_t* contextlist,
116                 int debug)
117 {
118   const void *void_conv;
119   const struct pam_conv *conv;
120
121   if (pam_get_item(pamh, PAM_CONV, &void_conv) == PAM_SUCCESS &&
122       void_conv) {
123     conv = void_conv;
124     if (conv->conv != NULL) {
125       struct pam_response *responses;
126       char *text=calloc(PATH_MAX,1);
127
128       if (text == NULL)
129         return (security_context_t) strdup(contextlist[0]);
130
131       snprintf(text, PATH_MAX,
132                _("Your default context is %s. \n"), contextlist[0]);
133       send_text(conv,text,debug);
134       free(text);
135       query_response(conv,_("Do you want to choose a different one? [n]"),
136                 &responses,debug);
137       if (responses && ((responses[0].resp[0] == 'y') ||
138                         (responses[0].resp[0] == 'Y')))
139       {
140           int choice=0;
141           int i;
142           char *prompt=_("Enter number of choice: ");
143           int len=strlen(prompt);
144           char buf[PATH_MAX];
145
146           _pam_drop_reply(responses, 1);
147           for (i = 0; contextlist[i]; i++) {
148             len+=strlen(contextlist[i]) + 10;
149           }
150           text=calloc(len,1);
151           for (i = 0; contextlist[i]; i++) {
152             snprintf(buf, PATH_MAX,
153                      "[%d] %s\n", i+1, contextlist[i]);
154             strncat(text,buf,len);
155           }
156           strcat(text,prompt);
157           while ((choice < 1) || (choice > i)) {
158             query_response(conv,text,&responses,debug);
159             choice = strtol (responses[0].resp, NULL, 10);
160             _pam_drop_reply(responses, 1);
161           }
162           free(text);
163           return (security_context_t) strdup(contextlist[choice-1]);
164       }
165       else if (responses)
166         _pam_drop_reply(responses, 1);
167     } else {
168       if (debug)
169         syslog(LOG_NOTICE, _("%s: bogus conversation function"),MODULE);
170     }
171   } else {
172     if (debug)
173       syslog(LOG_NOTICE, _("%s: no conversation function"),MODULE);
174   }
175   return (security_context_t) strdup(contextlist[0]);
176 }
177
178 static security_context_t
179 manual_context (pam_handle_t *pamh, const char *user, int debug)
180 {
181   const void *void_conv;
182   const struct pam_conv *conv;
183   security_context_t newcon;
184   context_t new_context;
185   int mls_enabled = is_selinux_mls_enabled();
186
187   if (pam_get_item(pamh, PAM_CONV, &void_conv) == PAM_SUCCESS) {
188     conv = void_conv;
189     if (conv && conv->conv != NULL) {
190       struct pam_response *responses;
191
192       while (1) {
193         query_response(conv,
194                        _("Would you like to enter a security context? [y] "),
195                        &responses,debug);
196         if ((responses[0].resp[0] == 'y') || (responses[0].resp[0] == 'Y') ||
197             (responses[0].resp[0] == '\0') )
198         {
199           if (mls_enabled)
200             new_context = context_new ("user:role:type:level");
201           else
202             new_context = context_new ("user:role:type");
203           _pam_drop_reply(responses, 1);
204
205           /* Allow the user to enter each field of the context individually */
206           if (context_user_set (new_context, user))
207           {
208               context_free (new_context);
209               return NULL;
210           }
211           query_response(conv,_("role: "),&responses,debug);
212           if (context_role_set (new_context, responses[0].resp))
213           {
214               _pam_drop_reply(responses, 1);
215               context_free (new_context);
216               return NULL;
217           }
218           _pam_drop_reply(responses, 1);
219           query_response(conv,_("type: "),&responses,debug);
220           if (context_type_set (new_context, responses[0].resp))
221           {
222               _pam_drop_reply(responses, 1);
223               context_free (new_context);
224               return NULL;
225           }
226           _pam_drop_reply(responses, 1);
227           if (mls_enabled)
228             {
229               query_response(conv,_("level: "),&responses,debug);
230               if (context_range_set (new_context, responses[0].resp))
231                 {
232                   context_free (new_context);
233                   return NULL;
234                 }
235             }
236           /* Get the string value of the context and see if it is valid. */
237           if (!security_check_context(context_str(new_context))) {
238             newcon = strdup(context_str(new_context));
239             context_free (new_context);
240             return newcon;
241           }
242           else
243             send_text(conv,_("Not a valid security context"),debug);
244         }
245         else {
246           _pam_drop_reply(responses, 1);
247           return NULL;
248         }
249       } /* end while */
250     } else {
251       if (debug)
252         syslog(LOG_NOTICE, _("%s: bogus conversation function"),MODULE);
253     }
254   } else {
255     if (debug)
256       syslog(LOG_NOTICE, _("%s: no conversation function"),MODULE);
257   }
258   return NULL;
259 }
260
261 static void security_restorelabel_tty(const char *tty,
262                                       security_context_t context) {
263   char ttybuf[PATH_MAX];
264   const char *ptr;
265
266   if (context==NULL)
267     return;
268
269   if(strncmp("/dev/", tty, 5)) {
270     snprintf(ttybuf,sizeof(ttybuf),"/dev/%s",tty);
271     ptr = ttybuf;
272   }
273   else
274     ptr = tty;
275
276   if (setfilecon(ptr, context) && errno != ENOENT)
277   {
278       syslog(LOG_NOTICE,
279              _("Warning!  Could not relabel %s with %s, not relabeling.\n"),
280              ptr, context);
281   }
282 }
283
284 static security_context_t security_label_tty(char *tty,
285                                              security_context_t usercon) {
286   char ttybuf[PATH_MAX];
287   int status=0;
288   security_context_t newdev_context=NULL; /* The new context of a device */
289   security_context_t prev_context=NULL; /* The new context of a device */
290   const char *ptr;
291
292   if(strncmp("/dev/", tty, 5))
293   {
294     snprintf(ttybuf,sizeof(ttybuf),"/dev/%s",tty);
295     ptr = ttybuf;
296   }
297   else
298     ptr = tty;
299
300   if (getfilecon(ptr, &prev_context) < 0)
301   {
302       syslog(LOG_NOTICE,
303            _("Warning!  Could not get current context for %s, not relabeling."),           ptr);
304       return NULL;
305   }
306   if( security_compute_relabel(usercon,prev_context,SECCLASS_CHR_FILE,
307                                &newdev_context)!=0)
308   {
309     syslog(LOG_NOTICE,
310            _("Warning!  Could not get new context for %s, not relabeling."),
311            ptr);
312     syslog(LOG_NOTICE, "usercon=%s, prev_context=%s\n", usercon, prev_context);
313     freecon(prev_context);
314     return NULL;
315   }
316   status=setfilecon(ptr,newdev_context);
317   if (status)
318   {
319       syslog(LOG_NOTICE,
320              _("Warning!  Could not relabel %s with %s, not relabeling.%s"),
321              ptr,newdev_context,strerror(errno));
322       freecon(prev_context);
323       prev_context=NULL;
324   }
325   freecon(newdev_context);
326   return prev_context;
327 }
328
329 static security_context_t user_context=NULL;
330 static security_context_t prev_user_context=NULL;
331 static security_context_t ttyn_context=NULL;  /* The current context of ttyn device */
332 static int selinux_enabled=0;
333 static char *ttyn=NULL;
334
335 /* Tell the user that access has been granted. */
336 static void
337 verbose_message(pam_handle_t *pamh, char *msg, int debug)
338 {
339   const void *void_conv;
340   const struct pam_conv *conv;
341   struct pam_message message;
342   const struct pam_message *messages[] = {&message};
343   struct pam_response *responses;
344   if (pam_get_item(pamh, PAM_CONV, &void_conv) == PAM_SUCCESS) {
345     conv = void_conv;
346     if (conv && conv->conv != NULL) {
347       char text[PATH_MAX];
348
349       memset(&message, 0, sizeof(message));
350       message.msg_style = PAM_TEXT_INFO;
351       snprintf(text, sizeof(text), msg);
352
353       message.msg = text;
354       if (debug)
355         syslog(LOG_NOTICE, MODULE ": %s", message.msg);
356       conv->conv(1, messages, &responses, conv->appdata_ptr);
357       if (responses)
358         _pam_drop_reply(responses, 1);
359     } else {
360       if (debug)
361         syslog(LOG_NOTICE, _("%s: bogus conversation function"),MODULE);
362     }
363   } else {
364     if (debug)
365       syslog(LOG_NOTICE,_("%s: no conversation function"),MODULE);
366   }
367 }
368
369 PAM_EXTERN int
370 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
371 {
372         /* Fail by default. */
373         return PAM_AUTH_ERR;
374 }
375
376 PAM_EXTERN int
377 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
378 {
379         return PAM_SUCCESS;
380 }
381
382 PAM_EXTERN int
383 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
384 {
385   int i, debug = 0, ttys=1, has_tty=isatty(0), verbose=0, multiple=0, close_session=0;
386   int ret = 0;
387   security_context_t* contextlist = NULL;
388   int num_contexts = 0;
389   const void *username = NULL;
390   const void *tty = NULL;
391
392   /* Parse arguments. */
393   for (i = 0; i < argc; i++) {
394     if (strcmp(argv[i], "debug") == 0) {
395       debug = 1;
396     }
397     if (strcmp(argv[i], "nottys") == 0) {
398       ttys = 0;
399     }
400     if (strcmp(argv[i], "verbose") == 0) {
401       verbose = 1;
402     }
403     if (strcmp(argv[i], "multiple") == 0) {
404       multiple = 1;
405     }
406     if (strcmp(argv[i], "close") == 0) {
407       close_session = 1;
408     }
409   }
410
411   if (debug)
412     syslog(LOG_NOTICE, MODULE ": %s", "Open Session");
413
414   /* this module is only supposed to execute close_session */
415   if (close_session)
416       return PAM_SUCCESS;
417
418   if (!(selinux_enabled = is_selinux_enabled()>0) )
419       return PAM_SUCCESS;
420
421   if (pam_get_item(pamh, PAM_USER, &username) != PAM_SUCCESS ||
422                    username == NULL) {
423     return PAM_AUTH_ERR;
424   }
425   num_contexts = get_ordered_context_list(username, 0, &contextlist);
426   if (num_contexts > 0) {
427     if (multiple && (num_contexts > 1) && has_tty) {
428       user_context = select_context(pamh,contextlist, debug);
429       freeconary(contextlist);
430     } else {
431       user_context = (security_context_t) strdup(contextlist[0]);
432       freeconary(contextlist);
433     }
434   } else {
435     if (has_tty) {
436       user_context = manual_context(pamh,username,debug);
437       if (user_context == NULL) {
438         syslog (LOG_ERR, _("Unable to get valid context for %s"),
439                 (const char *)username);
440         return PAM_AUTH_ERR;
441       }
442     } else {
443         syslog (LOG_ERR,
444                 _("Unable to get valid context for %s, No valid tty"),
445                 (const char *)username);
446         return PAM_AUTH_ERR;
447     }
448   }
449   if (getexeccon(&prev_user_context)<0) {
450     prev_user_context=NULL;
451   }
452   if (ttys) {
453     /* Get the name of the terminal. */
454     if (pam_get_item(pamh, PAM_TTY, &tty) != PAM_SUCCESS) {
455       tty = NULL;
456     }
457
458     if ((tty == NULL) || (strlen(tty) == 0) ||
459         strcmp(tty, "ssh") == 0 || strncmp(tty, "NODEV", 5) == 0) {
460       tty = ttyname(STDIN_FILENO);
461       if ((tty == NULL) || (strlen(tty) == 0)) {
462         tty = ttyname(STDOUT_FILENO);
463       }
464       if ((tty == NULL) || (strlen(tty) == 0)) {
465         tty = ttyname(STDERR_FILENO);
466       }
467     }
468   }
469   if(ttys && tty ) {
470     ttyn=strdup(tty);
471     ttyn_context=security_label_tty(ttyn,user_context);
472   }
473   ret = setexeccon(user_context);
474   if (ret==0 && verbose) {
475     char msg[PATH_MAX];
476     snprintf(msg, sizeof(msg),
477              _("Security Context %s Assigned"), user_context);
478     verbose_message(pamh, msg, debug);
479   }
480   if (ret) {
481     syslog(LOG_ERR, _("Error!  Unable to set %s executable context %s."),
482            (const char *)username, user_context);
483     freecon(user_context);
484     return PAM_AUTH_ERR;
485   } else {
486     if (debug)
487       syslog(LOG_NOTICE, _("%s: set %s security context to %s"),MODULE,
488              (const char *)username, user_context);
489   }
490   freecon(user_context);
491
492   return PAM_SUCCESS;
493 }
494
495 PAM_EXTERN int
496 pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
497 {
498   int i, debug = 0,status=0, open_session=0;
499   if (! (selinux_enabled ))
500       return PAM_SUCCESS;
501
502   /* Parse arguments. */
503   for (i = 0; i < argc; i++) {
504     if (strcmp(argv[i], "debug") == 0) {
505       debug = 1;
506     }
507     if (strcmp(argv[i], "open") == 0) {
508       open_session = 1;
509     }
510   }
511
512   if (debug)
513     syslog(LOG_NOTICE, MODULE ": %s", "Close Session");
514
515   if (open_session)
516     return PAM_SUCCESS;
517
518   if (ttyn) {
519     if (debug)
520       syslog(LOG_NOTICE, MODULE ":Restore tty  %s -> %s", ttyn,ttyn_context);
521
522     security_restorelabel_tty(ttyn,ttyn_context);
523     freecon(ttyn_context);
524     free(ttyn);
525     ttyn=NULL;
526   }
527   status=setexeccon(prev_user_context);
528   freecon(prev_user_context);
529   if (status) {
530     syslog(LOG_ERR, _("Error!  Unable to set executable context %s."),
531            prev_user_context);
532     return PAM_AUTH_ERR;
533   }
534
535   if (debug)
536     syslog(LOG_NOTICE, _("%s: setcontext back to orginal"),MODULE);
537
538   return PAM_SUCCESS;
539 }
540
541 #else /* PAM_SELINUX_MAIN */
542
543 /************************************************************************
544  *
545  * All PAM code goes in this section.
546  *
547  ************************************************************************/
548
549 #include <unistd.h>               /* for getuid(), exit(), getopt() */
550 #include <signal.h>
551 #include <sys/wait.h>             /* for wait() */
552
553 #include <security/pam_appl.h>    /* for PAM functions */
554 #include <security/pam_misc.h>    /* for misc_conv PAM utility function */
555
556 #define SERVICE_NAME "pam_selinux_check"   /* the name of this program for PAM */
557                                   /* The file containing the context to run
558                                    * the scripts under.                     */
559 int authenticate_via_pam( const char *user ,   pam_handle_t **pamh);
560
561 /* authenticate_via_pam()
562  *
563  * in:     user
564  * out:    nothing
565  * return: value   condition
566  *         -----   ---------
567  *           1     pam thinks that the user authenticated themselves properly
568  *           0     otherwise
569  *
570  * this function uses pam to authenticate the user running this
571  * program.  this is the only function in this program that makes pam
572  * calls.
573  *
574  */
575
576 int authenticate_via_pam( const char *user ,   pam_handle_t **pamh) {
577
578   struct pam_conv *conv;
579   int result = 0;    /* our result, set to 0 (not authenticated) by default */
580
581   /* this is a jump table of functions for pam to use when it wants to *
582    * communicate with the user.  we'll be using misc_conv(), which is  *
583    * provided for us via pam_misc.h.                                   */
584   struct pam_conv pam_conversation = {
585     misc_conv,
586     NULL
587   };
588   conv = &pam_conversation;
589
590
591   /* make `p_pam_handle' a valid pam handle so we can use it when *
592    * calling pam functions.                                       */
593   if( PAM_SUCCESS != pam_start( SERVICE_NAME,
594                                 user,
595                                 conv,
596                                 pamh ) ) {
597     fprintf( stderr, _("failed to initialize PAM\n") );
598     exit( -1 );
599   }
600
601   if( PAM_SUCCESS != pam_set_item(*pamh, PAM_RUSER, user))
602   {
603       fprintf( stderr, _("failed to pam_set_item()\n") );
604       exit( -1 );
605   }
606
607   /* Ask PAM to authenticate the user running this program */
608   if( PAM_SUCCESS == pam_authenticate(*pamh,0) ) {
609     if ( PAM_SUCCESS == pam_open_session(*pamh, 0) )
610       result = 1;  /* user authenticated OK! */
611   }
612   return( result );
613
614 } /* authenticate_via_pam() */
615
616 int main(int argc, char **argv) {
617   pam_handle_t *pamh;
618   int childPid;
619
620   if (!authenticate_via_pam(argv[1],&pamh))
621     exit(-1);
622
623   childPid = fork();
624   if (childPid < 0) {
625     int errsv = errno;
626
627     /* error in fork() */
628     fprintf(stderr, _("login: failure forking: %s"), strerror(errsv));
629     pam_close_session(pamh, 0);
630     /* We're done with PAM.  Free `pam_handle'. */
631     pam_end( pamh, PAM_SUCCESS );
632     exit(0);
633   }
634   if (childPid) {
635     close(0); close(1); close(2);
636     struct sigaction sa;
637     memset(&sa,0,sizeof(sa));
638     sa.sa_handler = SIG_IGN;
639     sigaction(SIGQUIT, &sa, NULL);
640     sigaction(SIGINT, &sa, NULL);
641     while(wait(NULL) == -1 && errno == EINTR) /**/ ;
642     openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
643     pam_close_session(pamh, 0);
644     /* We're done with PAM.  Free `pam_handle'. */
645     pam_end( pamh, PAM_SUCCESS );
646     exit(0);
647   }
648   argv[0]="/bin/sh";
649   argv[1]=NULL;
650
651   /* NOTE: The environment has not been sanitized. LD_PRELOAD and other fun
652    * things could be set. */
653   execv("/bin/sh",argv);
654   fprintf(stderr,"Failure\n");
655   return 0;
656 }
657 #endif