blob: 66681dabb16939e58e5839ebb4c654ccbcfe163b [file] [log] [blame]
/*
* Zebra privileges.
*
* Copyright (C) 2003 Paul Jakma.
*
* This file is part of GNU Zebra.
*
* GNU Zebra is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* GNU Zebra is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Zebra; see the file COPYING. If not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <zebra.h>
#include "log.h"
#include "privs.h"
#include "memory.h"
/* internal privileges state */
static struct _zprivs_t
{
#ifdef HAVE_LCAPS
cap_t caps; /* caps storage */
cap_value_t *syscaps_p; /* system permitted caps */
cap_value_t *syscaps_i; /* system inheritable caps */
int sys_num_p; /* number of syscaps_p */
int sys_num_i; /* number of syscaps_i */
#endif /* HAVE_LCAPS */
uid_t zuid, /* uid to run as */
zsuid; /* saved uid */
gid_t zgid; /* gid to run as */
gid_t vtygrp; /* gid for vty sockets */
} zprivs_state;
/* externally exported but not directly accessed functions */
#ifdef HAVE_LCAPS
int zprivs_change_caps (zebra_privs_ops_t);
zebra_privs_current_t zprivs_state_caps (void);
#endif /* HAVE_LCAPS */
int zprivs_change_uid (zebra_privs_ops_t);
zebra_privs_current_t zprivs_state_uid (void);
int zprivs_change_null (zebra_privs_ops_t);
zebra_privs_current_t zprivs_state_null (void);
void zprivs_terminate (void);
#ifdef HAVE_LCAPS
static int
cap_map [ZCAP_MAX] =
{
[ZCAP_SETGID] = CAP_SETGID,
[ZCAP_SETUID] = CAP_SETUID,
[ZCAP_BIND] = CAP_NET_BIND_SERVICE,
[ZCAP_BROADCAST] = CAP_NET_BROADCAST,
[ZCAP_ADMIN] = CAP_NET_ADMIN,
[ZCAP_RAW] = CAP_NET_RAW,
[ZCAP_CHROOT] = CAP_SYS_CHROOT,
[ZCAP_NICE] = CAP_SYS_NICE,
[ZCAP_PTRACE] = CAP_SYS_PTRACE,
[ZCAP_DAC_OVERRIDE] = CAP_DAC_OVERRIDE,
[ZCAP_READ_SEARCH] = CAP_DAC_READ_SEARCH,
[ZCAP_SYS_ADMIN] = CAP_SYS_ADMIN,
[ZCAP_FOWNER] = CAP_FOWNER
};
/* convert zebras privileges to system capabilities */
static cap_value_t *
zcaps2sys (zebra_capabilities_t *zcaps, int num)
{
cap_value_t *syscaps;
int i;
if (!num)
return NULL;
syscaps = (cap_value_t *) XCALLOC ( MTYPE_PRIVS,
(sizeof(cap_value_t) * num) );
if (!syscaps)
{
zlog_err ("zcap2sys: could not XCALLOC!");
return NULL;
}
for (i=0; i < num; i++)
{
syscaps[i] = cap_map[zcaps[i]];
}
return syscaps;
}
/* set or clear the effective capabilities to/from permitted */
int
zprivs_change_caps (zebra_privs_ops_t op)
{
cap_flag_value_t cflag;
if (op == ZPRIVS_RAISE)
cflag = CAP_SET;
else if (op == ZPRIVS_LOWER)
cflag = CAP_CLEAR;
else
return -1;
if ( !cap_set_flag (zprivs_state.caps, CAP_EFFECTIVE,
zprivs_state.sys_num_p, zprivs_state.syscaps_p, cflag))
return cap_set_proc (zprivs_state.caps);
return -1;
}
zebra_privs_current_t
zprivs_state_caps (void)
{
int i;
cap_flag_value_t val;
for (i=0; i < zprivs_state.sys_num_p; i++)
{
if ( cap_get_flag (zprivs_state.caps, zprivs_state.syscaps_p[i],
CAP_EFFECTIVE, &val) )
zlog_warn ("zprivs_state_caps: could not cap_get_flag, %s",
safe_strerror (errno) );
if (val == CAP_SET)
return ZPRIVS_RAISED;
}
return ZPRIVS_LOWERED;
}
#endif /* HAVE_LCAPS */
int
zprivs_change_uid (zebra_privs_ops_t op)
{
if (op == ZPRIVS_RAISE)
return seteuid (zprivs_state.zsuid);
else if (op == ZPRIVS_LOWER)
return seteuid (zprivs_state.zuid);
else
return -1;
}
zebra_privs_current_t
zprivs_state_uid (void)
{
return ( (zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED);
}
int
zprivs_change_null (zebra_privs_ops_t op)
{
return 0;
}
zebra_privs_current_t
zprivs_state_null (void)
{
return ZPRIVS_RAISED;
}
void
zprivs_init(struct zebra_privs_t *zprivs)
{
struct passwd *pwentry = NULL;
struct group *grentry = NULL;
if (!zprivs)
{
fprintf (stderr, "zprivs_init: called with NULL arg!\n");
exit (1);
}
/* NULL privs */
if (! (zprivs->user || zprivs->group
|| zprivs->cap_num_p || zprivs->cap_num_i) )
{
zprivs->change = zprivs_change_null;
zprivs->current_state = zprivs_state_null;
return;
}
if (zprivs->user)
{
if ( (pwentry = getpwnam (zprivs->user)) )
{
zprivs_state.zuid = pwentry->pw_uid;
}
else
{
/* cant use log.h here as it depends on vty */
fprintf (stderr, "privs_init: could not lookup user %s\n",
zprivs->user);
exit (1);
}
}
grentry = NULL;
if (zprivs->vty_group)
/* Add the vty_group to the supplementary groups so it can be chowned to */
{
if ( (grentry = getgrnam (zprivs->vty_group)) )
{
zprivs_state.vtygrp = grentry->gr_gid;
if ( setgroups (1, &zprivs_state.vtygrp) )
{
fprintf (stderr, "privs_init: could not setgroups, %s\n",
safe_strerror (errno) );
exit (1);
}
}
else
{
fprintf (stderr, "privs_init: could not lookup vty group %s\n",
zprivs->vty_group);
exit (1);
}
}
if (zprivs->group)
{
if ( (grentry = getgrnam (zprivs->group)) )
{
zprivs_state.zgid = grentry->gr_gid;
}
else
{
fprintf (stderr, "privs_init: could not lookup group %s\n",
zprivs->group);
exit (1);
}
/* change group now, forever. uid we do later */
if ( setregid (zprivs_state.zgid, zprivs_state.zgid) )
{
fprintf (stderr, "zprivs_init: could not setregid, %s\n",
safe_strerror (errno) );
exit (1);
}
}
#ifdef HAVE_LCAPS
zprivs_state.syscaps_p = zcaps2sys (zprivs->caps_p, zprivs->cap_num_p);
zprivs_state.sys_num_p = zprivs->cap_num_p;
zprivs_state.syscaps_i = zcaps2sys (zprivs->caps_i, zprivs->cap_num_i);
zprivs_state.sys_num_i = zprivs->cap_num_i;
/* Tell kernel we want caps maintained across uid changes */
if ( prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1 )
{
fprintf (stderr, "privs_init: could not set PR_SET_KEEPCAPS, %s\n",
safe_strerror (errno) );
exit(1);
}
if ( !zprivs_state.syscaps_p )
{
fprintf (stderr, "privs_init: capabilities enabled, but no capabilities supplied\n");
}
if ( !(zprivs_state.caps = cap_init()) )
{
fprintf (stderr, "privs_init: failed to cap_init, %s\n",
safe_strerror (errno));
exit (1);
}
/* we have caps, we have no need to ever change back the original user */
if (zprivs_state.zuid)
{
if ( setreuid (zprivs_state.zuid, zprivs_state.zuid) )
{
fprintf (stderr, "zprivs_init (cap): could not setreuid, %s\n",
safe_strerror (errno));
exit (1);
}
}
if ( cap_clear (zprivs_state.caps) )
{
fprintf (stderr, "privs_init: failed to cap_clear, %s\n",
safe_strerror (errno));
exit (1);
}
/* set permitted caps */
cap_set_flag(zprivs_state.caps, CAP_PERMITTED,
zprivs_state.sys_num_p, zprivs_state.syscaps_p, CAP_SET);
cap_set_flag(zprivs_state.caps, CAP_EFFECTIVE,
zprivs_state.sys_num_p, zprivs_state.syscaps_p, CAP_SET);
/* set inheritable caps, if any */
if (zprivs_state.sys_num_i)
{
cap_set_flag(zprivs_state.caps, CAP_INHERITABLE,
zprivs_state.sys_num_i, zprivs_state.syscaps_i, CAP_SET);
}
/* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as
* and when, and only when, they are needed.
*/
if ( cap_set_proc (zprivs_state.caps) )
{
fprintf (stderr, "privs_init: initial cap_set_proc failed\n");
exit (1);
}
/* set methods for the caller to use */
zprivs->change = zprivs_change_caps;
zprivs->current_state = zprivs_state_caps;
#elif !defined(HAVE_LCAPS)
/* we dont have caps. we'll need to maintain rid and saved uid
* and change euid back to saved uid (who we presume has all neccessary
* privileges) whenever we are asked to raise our privileges.
*/
zprivs_state.zsuid = geteuid();
if ( zprivs_state.zuid )
{
if ( setreuid (-1, zprivs_state.zuid) )
{
fprintf (stderr, "privs_init (uid): could not setreuid, %s\n",
safe_strerror (errno));
exit (1);
}
}
zprivs->change = zprivs_change_uid;
zprivs->current_state = zprivs_state_uid;
#endif /* HAVE_LCAPS */
}
void
zprivs_terminate (void)
{
#ifdef HAVE_LCAPS
if (zprivs_state.caps)
cap_clear (zprivs_state.caps);
if ( cap_set_proc (zprivs_state.caps) )
{
zlog_err ("privs_terminate: cap_set_proc failed, %s",
safe_strerror (errno) );
exit (1);
}
if (zprivs_state.sys_num_p)
XFREE (MTYPE_PRIVS, zprivs_state.syscaps_p);
if (zprivs_state.sys_num_i)
XFREE (MTYPE_PRIVS, zprivs_state.syscaps_i);
cap_free (zprivs_state.caps);
#else
if (zprivs_state.zuid)
{
if ( setreuid (zprivs_state.zuid, zprivs_state.zuid) )
{
zlog_err ("privs_terminate: could not setreuid, %s",
safe_strerror (errno) );
exit (1);
}
}
#endif /* HAVE_LCAPS */
return;
}
void
zprivs_get_ids(struct zprivs_ids_t *ids)
{
ids->uid_priv = getuid();
(zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
: (ids->uid_normal = -1);
(zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
: (ids->gid_normal = -1);
(zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp)
: (ids->gid_vty = -1);
return;
}