add MailService and MailmanService

This commit is contained in:
Max Erenberg 2021-07-24 00:08:22 +00:00
parent de0f473881
commit 3b78b7ffb4
96 changed files with 487 additions and 6013 deletions

View File

@ -1,4 +0,0 @@
[DEFAULT]
sign-tags = True
posttag = git push /users/git/public/pyceo.git --tags
debian-tag=v%(version)s

8
.gitignore vendored
View File

@ -1,5 +1,3 @@
/build-stamp
/build
*.pyc
/build-ceo
/build-ceod
__pycache__/
/venv/
.vscode/

40
bin/ceo
View File

@ -1,40 +0,0 @@
#!/usr/bin/python
import sys, ldap
from getpass import getpass
import ceo.urwid.main
import ceo.console.main
from ceo import ldapi, members
def start():
try:
if len(sys.argv) == 1:
print "Reading config file...",
members.configure()
print "Connecting to LDAP..."
members.connect(AuthCallback())
ceo.urwid.main.start()
else:
members.configure()
members.connect(AuthCallback())
ceo.console.main.start()
except ldap.LOCAL_ERROR, e:
print ldapi.format_ldaperror(e)
except ldap.INSUFFICIENT_ACCESS, e:
print ldapi.format_ldaperror(e)
print "You probably aren't permitted to do whatever you just tried."
print "Admittedly, ceo probably shouldn't have crashed either."
class AuthCallback:
def callback(self, error):
try:
print "Password: ",
return getpass("")
except KeyboardInterrupt:
print ""
sys.exit(1)
if __name__ == '__main__':
start()

View File

@ -1,5 +0,0 @@
if test -e .git; then
git-buildpackage --git-ignore-new -us -uc
else
debuild -us -uc
fi

View File

@ -0,0 +1,24 @@
from typing import List
from zope.interface import Interface
class IFileService(Interface):
"""
A service which can access, create and modify files on the
NFS users' directory.
"""
def create_home_dir(username: str, is_club: bool = False):
"""
Create a new home dir for the given user or club.
"""
def get_forwarding_addresses(username: str) -> List[str]:
"""
Get the contents of the user's ~/.forward file,
one line at a time.
"""
def set_forwarding_addresses(username: str, addresses: List[str]):
"""Set the contents of the user's ~/.forward file."""

View File

@ -25,14 +25,14 @@ class IGroup(Interface):
def get_members() -> List[IUser]:
"""Get a list of the members in this group."""
def serialize_for_modlist() -> Dict:
def serialize_for_modlist() -> Dict[str, List[bytes]]:
"""
Serialize this group into a dict to be passed to
ldap.modlist.addModlist().
"""
# static method
def deserialize_from_dict(data: Dict):
def deserialize_from_dict(data: Dict[str, List[bytes]]):
"""Deserialize this group from a dict returned by ldap.search_s().
:returns: IGroup

View File

@ -10,18 +10,18 @@ class ILDAPService(Interface):
def get_user(username: str) -> IUser:
"""Retrieve the user with the given username."""
def save_user(user: IUser) -> IUser:
def add_user(user: IUser) -> IUser:
"""
Save the user in the database.
Add the user to the database.
A new UID and GID will be generated and returned in the new user.
"""
def get_group(cn: str, is_club: bool = False) -> IGroup:
"""Retrieve the group with the given cn (Unix group name)."""
def save_group(group: IGroup) -> IGroup:
def add_group(group: IGroup) -> IGroup:
"""
Save the group in the database.
Add the group to the database.
The GID will not be changed and must be valid.
"""
@ -30,3 +30,6 @@ class ILDAPService(Interface):
def modify_group(old_group: IGroup, new_group: IGroup):
"""Replace old_group with new_group."""
def add_sudo_role(uid: str):
"""Create a sudo role for the club with this UID."""

View File

@ -0,0 +1,15 @@
from typing import Dict
from zope.interface import Interface
from .IUser import IUser
class IMailService(Interface):
"""An interface to send email messages."""
def send(_from: str, to: str, headers: Dict[str, str], content: str):
"""Send a message with the given headers and content."""
def send_welcome_message_to(user: IUser):
"""Send a welcome message to the new member."""

View File

@ -0,0 +1,11 @@
from zope.interface import Interface
class IMailmanService(Interface):
"""A service to susbcribe and unsubscribe people from mailing lists."""
def subscribe(address: str, mailing_list: str):
"""Subscribe the email address to the mailing list."""
def unsubscribe(address: str, mailing_list: str):
"""Unsubscribe the email address from the mailing list."""

View File

@ -0,0 +1,15 @@
from typing import Union
from zope.interface import Interface
class IUWLDAPService(Interface):
"""Represents the UW LDAP database."""
def get(username: str):
"""
Return the LDAP record for the given user, or
None if no such record exists.
:rtype: Union[UWLDAPRecord, None]
"""

View File

@ -24,6 +24,12 @@ class IUser(Interface):
# Non-LDAP attributes
forwarding_addresses = Attribute('list of email forwarding addresses')
def get_forwarding_addresses(self) -> List[str]:
"""Get the forwarding addresses for this user."""
def set_forwarding_addresses(self, addresses: List[str]):
"""Set the forwarding addresses for this user."""
def is_club() -> bool:
"""
Returns True if this is the Unix user for a club.
@ -31,7 +37,10 @@ class IUser(Interface):
"""
def add_to_ldap():
"""Add a new record to LDAP for this user."""
"""
Add a new record to LDAP for this user.
A new UID number and GID number will be created.
"""
def add_to_kerberos(password: str):
"""Add a new Kerberos principal for this user."""
@ -51,14 +60,17 @@ class IUser(Interface):
def change_password(password: str):
"""Replace the user's password."""
def serialize_for_modlist() -> Dict:
def create_home_dir():
"""Create a new home directory for this user."""
def serialize_for_modlist() -> Dict[str, List[bytes]]:
"""
Serialize this user into a dict to be passed to
ldap.modlist.addModlist().
"""
# static method
def deserialize_from_dict(data: Dict):
def deserialize_from_dict(data: Dict[str, List[bytes]]):
"""Deserialize this user from a dict returned by ldap.search_s().
:returns: IUser

View File

@ -3,3 +3,7 @@ from .IConfig import IConfig
from .IUser import IUser
from .ILDAPService import ILDAPService
from .IGroup import IGroup
from .IFileService import IFileService
from .IUWLDAPService import IUWLDAPService
from .IMailService import IMailService
from .IMailmanService import IMailmanService

View File

@ -9,15 +9,28 @@ class Config:
_domain = 'csclub.internal'
_ldap_base = ','.join(['dc=' + dc for dc in _domain.split('.')])
_config = {
'base_domain': _domain,
'ldap_admin_principal': 'ceod/admin',
'ldap_server_url': 'ldap://ldap-master.' + _domain,
'ldap_users_base': 'ou=People,' + _ldap_base,
'ldap_groups_base': 'ou=Group,' + _ldap_base,
'ldap_sudo_base': 'ou=SUDOers,' + _ldap_base,
'ldap_sasl_realm': _domain.upper(),
'uwldap_server_url': 'ldap://uwldap.uwaterloo.ca',
'uwldap_base': 'dc=uwaterloo,dc=ca',
'member_min_id': 20001,
'member_max_id': 29999,
'club_min_id': 30001,
'club_max_id': 39999,
'member_home': '/users',
'club_home': '/users',
'member_home_skel': '/users/skel',
'club_home_skel': '/users/skel',
'smtp_url': 'smtp://mail.' + _domain,
'smtp_starttls': False,
'mailman3_api_base_url': 'http://localhost:8001/3.1',
'mailman3_api_username': 'restadmin',
'mailman3_api_password': 'mailman3',
}
def get(self, key: str) -> str:

79
ceod/model/FileService.py Normal file
View File

@ -0,0 +1,79 @@
import os
import pwd
import shutil
from typing import List
from zope import component
from zope.interface import implementer
from .validators import is_valid_forwarding_address, InvalidForwardingAddressException
from ceo_common.interfaces import IFileService, IConfig
@implementer(IFileService)
class FileService:
def __init__(self):
cfg = component.getUtility(IConfig)
self.member_home_skel = cfg.get('member_home_skel')
self.club_home_skel = cfg.get('club_home_skel')
def create_home_dir(self, username: str, is_club: bool = False):
if is_club:
skel_dir = self.club_home_skel
else:
skel_dir = self.member_home_skel
pwnam = pwd.getpwnam(username)
home = pwnam.pw_dir
uid = pwnam.pw_uid
gid = pwnam.pw_gid
# recursively copy skel dir to user's home
shutil.copytree(skel_dir, home)
# Set ownership and permissions on user's home.
# The setgid bit ensures that all files created under that
# directory belong to the owner (useful for clubs).
os.chmod(home, mode=0o2751) # rwxr-s--x
os.chown(home, uid=uid, gid=gid)
# recursively set file ownership
for root, dirs, files in os.walk(home):
for dir in dirs:
os.chown(os.path.join(root, dir), uid=uid, gid=gid)
for file in files:
os.chown(os.path.join(root, file), uid=uid, gid=gid)
def get_forwarding_addresses(self, username: str) -> List[str]:
pwnam = pwd.getpwnam(username)
home = pwnam.pw_dir
forward_file = os.path.join(home, '.forward')
if not os.path.isfile(forward_file):
return []
lines = [
line.strip() for line in open(forward_file).readlines()
]
return [
line for line in lines
if line != '' and line[0] != '#'
]
def set_forwarding_addresses(self, username: str, addresses: List[str]):
for line in addresses:
if not is_valid_forwarding_address(line):
raise InvalidForwardingAddressException(line)
pwnam = pwd.getpwnam(username)
home = pwnam.pw_dir
uid = pwnam.pw_uid
gid = pwnam.pw_gid
forward_file = os.path.join(home, '.forward')
if os.path.exists(forward_file):
# create a backup
backup_forward_file = forward_file + '.bak'
shutil.copyfile(forward_file, backup_forward_file)
os.chown(backup_forward_file, uid=uid, gid=gid)
else:
# create a new ~/.forward file
open(forward_file, 'w')
os.chown(forward_file, uid=uid, gid=gid)
with open(forward_file, 'w') as f:
for line in addresses:
f.write(line + '\n')

View File

@ -40,7 +40,7 @@ class Group:
def add_to_ldap(self):
self.ldap_srv.add_group(self)
def serialize_for_modlist(self) -> Dict:
def serialize_for_modlist(self) -> Dict[str, List[bytes]]:
data = {
'cn': [self.cn],
'gidNumber': [str(self.gid_number)],
@ -55,7 +55,7 @@ class Group:
return strings_to_bytes(data)
@staticmethod
def deserialize_from_dict(data: Dict) -> IGroup:
def deserialize_from_dict(data: Dict[str, List[bytes]]) -> IGroup:
data = bytes_to_strings(data)
return Group(
cn=data['cn'][0],

View File

@ -10,6 +10,7 @@ from zope.interface import implementer
from ceo_common.interfaces import ILDAPService, IKerberosService, IConfig, IUser, IGroup
from .User import User
from .Group import Group
from .SudoRole import SudoRole
class UserNotFoundError:
@ -34,6 +35,7 @@ class LDAPService:
self.club_max_id = cfg.get('club_max_id')
def _get_ldap_conn(self, gssapi_bind: bool = True) -> ldap.ldapobject.LDAPObject:
# TODO: cache the connection
conn = ldap.initialize(self.ldap_server_url)
if gssapi_bind:
self._gssapi_bind(conn)
@ -99,7 +101,13 @@ class LDAPService:
return uid
raise Exception('no UIDs remaining')
def save_user(self, user: IUser) -> IUser:
def add_sudo_role(self, uid: str):
conn = self._get_ldap_conn()
sudo_role = SudoRole(uid)
modlist = ldap.modlist.addModlist(sudo_role.serialize_for_modlist())
conn.add_s(sudo_role.dn, modlist)
def add_user(self, user: IUser) -> IUser:
if user.is_club():
min_id, max_id = self.club_min_id, self.club_max_id
else:
@ -112,9 +120,10 @@ class LDAPService:
modlist = ldap.modlist.addModlist(new_user.serialize_for_modlist())
conn.add_s(new_user.dn, modlist)
return new_user
def save_group(self, group: IGroup) -> IGroup:
def add_group(self, group: IGroup) -> IGroup:
conn = self._get_ldap_conn()
# make sure that the caller initialized the GID number
assert group.gid_number

63
ceod/model/MailService.py Normal file
View File

@ -0,0 +1,63 @@
import datetime
from email.message import EmailMessage
import re
import smtplib
from typing import Dict
import jinja2
from zope import component
from zope.interface import implementer
from ceo_common.interfaces import IMailService, IConfig, IUser
smtp_url_re = re.compile(r'^(?P<scheme>smtps?)://(?P<host>[\w.-]+)(:(?P<port>\d+))?$')
@implementer(IMailService)
class MailService:
def __init__(self):
cfg = component.getUtility(IConfig)
smtp_url = cfg.get('smtp_url')
match = smtp_url_re.match(smtp_url)
if match is None:
raise Exception('Invalid SMTP URL: %s' % smtp_url)
self.smtps = match.group('scheme') == 'smtps'
self.host = match.group('host')
self.port = int(match.group('port') or 25)
self.starttls = cfg.get('smtp_starttls')
assert not (self.smtps and self.starttls)
self.base_domain = cfg.get('base_domain')
self.jinja_env = jinja2.Environment(
loader=jinja2.PackageLoader('ceod.model'),
)
def send(self, _from: str, to: str, headers: Dict[str, str], content: str):
msg = EmailMessage()
msg.set_content(content)
msg['From'] = _from
msg['To'] = to
msg['Date'] = datetime.datetime.now().astimezone().strftime('%a, %d %b %Y %H:%M:%S %z')
for key, val in headers.items():
msg[key] = val
if self.smtps:
client = smtplib.SMTP_SSL(self.host, self.port)
else:
client = smtplib.SMTP(self.host, self.port)
client.ehlo()
if self.starttls:
client.starttls()
client.send_message(msg)
client.quit()
def send_welcome_message_to(self, user: IUser):
template = self.jinja_env.get_template('welcome_message.j2')
# TODO: store surname and givenName in LDAP
first_name = user.cn.split(' ', 1)[0]
body = template.render(name=first_name, user=user.uid)
self.send(
f'Computer Science Club <exec@{self.base_domain}>',
f'{user.cn} <{user.uid}@{self.base_domain}>',
{'Subject': 'Welcome to the Computer Science Club'},
body,
)

View File

@ -0,0 +1,51 @@
import requests
from requests.auth import HTTPBasicAuth
from zope import component
from zope.interface import implementer
from ceo_common.interfaces import IMailmanService, IConfig
@implementer(IMailmanService)
class MailmanService:
def __init__(self):
cfg = component.getUtility(IConfig)
self.base_domain = cfg.get('base_domain')
self.api_base_url = cfg.get('mailman3_api_base_url')
self.api_username = cfg.get('mailman3_api_username')
self.api_password = cfg.get('mailman3_api_password')
def subscribe(self, address: str, mailing_list: str):
if '@' in mailing_list:
mailing_list = mailing_list[:mailing_list.index('@')]
if '@' not in address:
address = f'{address}@{self.base_domain}'
url = f'{self.api_base_url}/members'
resp = requests.post(
url,
data={
'list_id': f'{mailing_list}.{self.base_domain}',
'subscriber': address,
'pre_verified': 'True',
'pre_confirmed': 'True',
'pre_approved': 'True',
},
auth=HTTPBasicAuth(self.api_username, self.api_password),
)
resp.raise_for_status()
def unsubscribe(self, address: str, mailing_list: str):
if '@' not in mailing_list:
mailing_list = f'{mailing_list}@{self.base_domain}'
if '@' not in address:
address = f'{address}@{self.base_domain}'
url = f'{self.api_base_url}/lists/{mailing_list}/member/{address}'
resp = requests.delete(
url,
data={
'pre_approved': 'True',
'pre_confirmed': 'True',
},
auth=HTTPBasicAuth(self.api_username, self.api_password),
)
resp.raise_for_status()

31
ceod/model/SudoRole.py Normal file
View File

@ -0,0 +1,31 @@
from zope import component
from .utils import strings_to_bytes
from ceo_common.interfaces import IConfig
class SudoRole:
"""Represents a sudoRole record in LDAP."""
def __init__(self, uid: str):
cfg = component.getUtility(IConfig)
ldap_sudo_base = cfg.get('ldap_sudo_base')
self.uid = uid
self.dn = f'cn=%{uid},{ldap_sudo_base}'
def serialize_for_modlist(self):
# TODO: use sudoOrder
data = {
'objectClass': [
'top',
'sudoRole',
],
'cn': '%' + self.uid,
'sudoUser': '%' + self.uid,
'sudoHost': 'ALL',
'sudoCommand': 'ALL',
'sudoOption': '!authenticate',
'sudoRunAsUser': self.uid,
}
return strings_to_bytes(data)

View File

@ -0,0 +1,30 @@
from typing import List, Dict, Union
from .utils import bytes_to_strings
class UWLDAPRecord:
"""Represents a record from the UW LDAP."""
def __init__(
self,
uid: str,
program: Union[str, None],
mail_local_addresses: List[str],
):
self.uid = uid
self.program = program
self.mail_local_addresses = mail_local_addresses
@staticmethod
def deserialize_from_dict(self, data: Dict[str, List[bytes]]):
"""
Deserializes a dict returned from ldap.search_s() into a
UWLDAPRecord.
"""
data = bytes_to_strings(data)
return UWLDAPRecord(
uid=data['uid'][0],
program=data.get('ou', [None])[0],
mail_local_addresses=data['mailLocalAddress'],
)

View File

@ -0,0 +1,23 @@
from typing import Union
import ldap
from zope import component
from zope.interface import implementer
from .UWLDAPRecord import UWLDAPRecord
from ceo_common.interfaces import IUWLDAPService, IConfig
@implementer(IUWLDAPService)
class UWLDAPService:
def __init__(self):
cfg = component.getUtility(IConfig)
self.uwldap_server_url = cfg.get('uwldap_server_url')
self.uwldap_base = cfg.get('uwldap_base')
def get(self, username: str) -> Union[UWLDAPRecord, None]:
conn = ldap.initialize(self.uwldap_server_url)
results = conn.search_s(self.uwldap_base, ldap.SCOPE_SUBTREE, f'uid={username}')
if not results:
return None
return UWLDAPRecord.deserialize_from_dict(results[0])

View File

@ -6,7 +6,8 @@ from zope import component
from zope.interface import implementer
from .utils import strings_to_bytes, bytes_to_strings
from ceo_common.interfaces import ILDAPService, IKerberosService, IUser, IConfig
from ceo_common.interfaces import ILDAPService, IKerberosService, IFileService, \
IUser, IConfig
@implementer(IUser)
@ -26,6 +27,8 @@ class User:
):
if not is_club and not terms and not non_member_terms:
raise Exception('terms and non_member_terms cannot both be empty')
cfg = component.getUtility(IConfig)
self.uid = uid
self.cn = cn
self.program = program
@ -34,20 +37,23 @@ class User:
self.login_shell = login_shell
self.uid_number = uid_number
self.gid_number = gid_number
self.home_directory = home_directory or os.path.join('/users', uid)
if home_directory is None:
if is_club:
home_parent = cfg.get('member_home')
else:
home_parent = cfg.get('club_home')
self.home_directory = os.path.join(home_parent, uid)
else:
self.home_directory = home_directory
self.positions = positions or []
self.mail_local_addresses = mail_local_addresses or []
self._is_club = is_club
cfg = component.getUtility(IConfig)
self.ldap_sasl_realm = cfg.get('ldap_sasl_realm')
self.dn = f'uid={uid},{cfg.get("ldap_users_base")}'
self.ldap_srv = component.getUtility(ILDAPService)
self.krb_srv = component.getUtility(IKerberosService)
@property
def forwarding_addresses(self):
raise NotImplementedError()
self.file_srv = component.getUtility(IFileService)
def __repr__(self) -> str:
lines = [
@ -80,7 +86,7 @@ class User:
return self._is_club
def add_to_ldap(self):
new_member = self.ldap_srv.save_user(self)
new_member = self.ldap_srv.add_user(self)
self.uid_number = new_member.uid_number
self.gid_number = new_member.gid_number
@ -90,6 +96,9 @@ class User:
def change_password(self, password: str):
self.krb_srv.change_password(self.uid, password)
def create_home_dir(self):
self.file_srv.create_home_dir(self.uid, self._is_club)
def serialize_for_modlist(self) -> Dict:
data = {
'cn': [self.cn],
@ -124,7 +133,7 @@ class User:
return strings_to_bytes(data)
@staticmethod
def deserialize_from_dict(data: Dict) -> IUser:
def deserialize_from_dict(data: Dict[str, List[bytes]]) -> IUser:
data = bytes_to_strings(data)
return User(
uid=data['uid'][0],
@ -167,3 +176,11 @@ class User:
new_user.positions.remove(position)
self.ldap_srv.modify_user(self, new_user)
self.positions = new_user.positions
def get_forwarding_addresses(self) -> List[str]:
return self.file_srv.get_forwarding_addresses(self.uid)
def set_forwarding_addresses(self, addresses: List[str]):
self.file_srv.set_forwarding_addresses(self.uid, addresses)
forwarding_addresses = property(get_forwarding_addresses, set_forwarding_addresses)

View File

@ -2,3 +2,9 @@ from .KerberosService import KerberosService
from .LDAPService import LDAPService, UserNotFoundError, GroupNotFoundError
from .User import User
from .Group import Group
from .UWLDAPService import UWLDAPService
from .UWLDAPRecord import UWLDAPRecord
from .FileService import FileService
from .SudoRole import SudoRole
from .MailService import MailService
from .MailmanService import MailmanService

View File

@ -1,25 +1,4 @@
#!/bin/bash -p
# This is a privileged script.
IFS=$' \t\n'
PATH=/usr/bin:/bin
unset ENV BASH_ENV CDPATH
umask 077
prog=$CEO_PROG
auth=$CEO_AUTH
tmp="$(tempfile)"
trap "rm $tmp" 0
exec >"$tmp"
h_from="Computer Science Club <exec@csclub.uwaterloo.ca>"
h_to="$CEO_NAME <$CEO_USER@csclub.uwaterloo.ca>"
subj="Welcome to the Computer Science Club"
if test "$prog" = addmember; then
user="$CEO_USER" name="$CEO_NAME"
body="Hello $name:
Hello {{ name }}:
Welcome to the Computer Science Club! We are pleased that you have chosen to join us. We welcome you to come out to our events, or just hang out in our office (MC 3036/3037). You have been automatically subscribed to our mailing list, csc-general, which we use to keep you informed of upcoming events.
@ -40,17 +19,17 @@ You can hear about upcoming events in a number of ways:
Even when events aren't being held, you are welcome to hang out in the club office (MC 3036/3037, across the hall from MathSoc). It's often open late into the evening, and sells pop and snacks at reasonable prices. If you're so inclined, you are also welcome in our IRC channel, #csc on FreeNode.
You now have a CSC user account with username \"$user\" and the password you supplied when you joined. You can use this account to log into almost any CSC system, including our office terminals and servers. A complete list is available at:
You now have a CSC user account with username "{{ user }}" and the password you supplied when you joined. You can use this account to log into almost any CSC system, including our office terminals and servers. A complete list is available at:
http://wiki.csclub.uwaterloo.ca/Machine_List
You can connect remotely using SSH. On Windows, PuTTY is a popular SSH client; on Unix-like operating systems, you can connect with the 'ssh' command, like this:
ssh $user@corn-syrup.csclub.uwaterloo.ca
ssh {{ user }}@corn-syrup.csclub.uwaterloo.ca
To use CSC web hosting, simply place files in the 'www' directory in your home directory. Files placed there will be available at:
http://csclub.uwaterloo.ca/~$user/
http://csclub.uwaterloo.ca/~{{ user }}/
We support many server-side technologies, including PHP, Perl and Python. If you need a MySQL database, you can create one for yourself using the 'ceo' command-line tool.
@ -70,18 +49,3 @@ To contact the executive, email:
Regards,
Computer Science Club Executive
"
elif [[ "$prog" = addclubrep || "$prog" = addclub ]]; then
exit 0
else
exit 1
fi
echo "From: $h_from"
echo "To: $h_to"
echo "Subject: $subj"
echo
echo "$body" | fmt -s
exec >&2
env - /usr/sbin/sendmail -t -f "exec@csclub.uwaterloo.ca" < "$tmp"

52
ceod/model/validators.py Normal file
View File

@ -0,0 +1,52 @@
import inspect
import re
from typing import Callable
class InvalidUsernameException(Exception):
pass
class InvalidForwardingAddressException(Exception):
pass
valid_username_re = re.compile(r'^[a-z][\w-]+$')
# Only allow usernames and email addresses to be set in ~/.forward
valid_forwarding_address_re = re.compile(r'^[\w-]+|[\w.+-]+@[\w-]+(\.[\w-]+)*$')
def is_valid_username(username: str) -> bool:
"""Returns True if the username has a valid format."""
return valid_username_re.match(username) is not None
def check_valid_username(f: Callable) -> Callable:
"""
A decorator which raises an Exception if the username passed
to f is invalid.
f must accept `username` as a regular argument.
"""
argspec = inspect.getfullargspec(f)
try:
arg_idx = argspec.args.index('username')
except ValueError:
return f
def wrapper(*args, **kwargs):
username = None
if arg_idx < len(args):
username = args[arg_idx]
elif 'username' in kwargs:
username = kwargs['username']
if username is not None and not is_valid_username(username):
raise InvalidUsernameException(username)
return f(*args, **kwargs)
return wrapper
def is_valid_forwarding_address(address: str) -> bool:
"""Returns True if the address is a valid forwarding address."""
return valid_forwarding_address_re.match(address) is not None

9
debian/.gitignore vendored
View File

@ -1,9 +0,0 @@
/ceo.substvars
/ceo-common
/ceo-clients
/ceo-daemon
/ceo-python
/files
/*.debhelper
/*.debhelper.log
/*.substvars

View File

@ -1,2 +0,0 @@
docs/addclub.1
docs/addmember.1

View File

@ -1 +0,0 @@
etc/csc

View File

@ -1 +0,0 @@
etc/*.cf etc/ops etc/spam etc/csc

View File

@ -1,55 +0,0 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: ceod
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: CEO Daemon
### END INIT INFO
set -e
test -x /usr/sbin/ceod || exit 0
. /lib/lsb/init-functions
case "$1" in
start)
log_daemon_msg "Starting CEO Daemon" "ceod"
if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/ceod.pid --exec /usr/sbin/ceod -- -dq; then
log_end_msg 0
else
log_end_msg 1
fi
;;
stop)
log_daemon_msg "Stopping CEO Daemon" "ceod"
if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/ceod.pid; then
log_end_msg 0
else
log_end_msg 1
fi
;;
restart|force-reload)
log_daemon_msg "Restarting CEO Daemon" "ceod"
start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/ceod.pid
if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/ceod.pid --exec /usr/sbin/ceod -- -dq; then
log_end_msg 0
else
log_end_msg 1
fi
;;
status)
status_of_proc -p /var/run/ceod.pid /usr/sbin/ceod ceod && exit 0 || exit $?
;;
*)
log_action_msg "Usage: /etc/init.d/ceod {start|stop|force-reload|restart|status}"
exit 1
esac
exit 0

View File

@ -1 +0,0 @@
etc/ldap/schema

View File

@ -1 +0,0 @@
etc/csc.schema etc/ldap/schema

View File

@ -1 +0,0 @@
docs/ceod.8

View File

@ -1 +0,0 @@
docs/ceo.1

739
debian/changelog vendored
View File

@ -1,739 +0,0 @@
ceo (0.7.1-buster1) buster; urgency=medium
* Update mailman path to use virtualenv
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Tue, 18 May 2021 01:45:49 -0400
ceo (0.7.0-buster1) buster; urgency=medium
* Set userPassword field in LDAP for SASL authentication
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 07 May 2021 21:44:02 -0400
ceo (0.6.0-buster1.2) buster; urgency=medium
* Decrease minimum username length from 3 to 2
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Sun, 02 May 2021 18:02:31 -0400
ceo (0.6.0-buster1.1) buster; urgency=medium
* Use Mailman 3 instead of Mailman 2
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Sun, 11 Apr 2021 21:54:06 -0400
ceo (0.6.0-stretch1) stretch; urgency=high
* Move adduser and mail operations to phosphoric-acid due to decommissioning
of aspartame
* Packaging for stretch
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 21 Mar 2021 23:04:05 -0400
ceo (0.6.0-buster1) buster; urgency=high
* Move adduser and mail operations to phosphoric-acid due to decommissioning
of aspartame
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 21 Mar 2021 22:39:05 -0400
ceo (0.5.28-bionic1.1) bionic; urgency=medium
* Packaging for bionic
-- Jennifer Zhou <c7zou@csclub.uwaterloo.ca> Sun, 21 Oct 2018 21:38:57 -0400
ceo (0.5.28-buster1) buster; urgency=medium
* Package for buster
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 15 Apr 2018 14:31:08 -0400
ceo (0.5.28-xenial1) xenial; urgency=medium
* Build for xenial
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Tue, 02 May 2017 00:24:45 -0400
ceo (0.5.28-jessie1) jessie; urgency=medium
* Build for jessie
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Tue, 02 May 2017 00:16:31 -0400
ceo (0.5.28-stretch1) stretch; urgency=medium
* Check for host (IPv4 or IPV6) or MX record when verying valid email
addresses
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 01 May 2017 13:07:21 -0500
ceo (0.5.27-stretch1) stretch; urgency=medium
* Build for stretch
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 11 Jan 2017 16:07:21 -0500
ceo (0.5.27jessie2) jessie; urgency=low
* Include library as a dependency
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sat, 20 Feb 2016 15:54:29 -0500
ceo (0.5.27trusty2) trusty; urgency=medium
* Include library as a dependency
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sat, 20 Feb 2016 15:57:18 -0500
ceo (0.5.27trusty1) trusty; urgency=high
* Resolved issue from previous release which resulted in CEO not launching
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 19 Feb 2016 23:38:41 -0500
ceo (0.5.27jessie1) jessie; urgency=high
* Resolved issue from previous release which resulted in CEO not launching
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 19 Feb 2016 23:38:41 -0500
ceo (0.5.27jessie) jessie; urgency=medium
* "Library" now launches "librarian"
-- Felix Bauckholt <fbauckho@csclub.uwaterloo.ca> Fri, 19 Feb 2016 22:12:25 -0500
ceo (0.5.26trusty) trusty; urgency=medium
* "Library" now launches "librarian"
-- Felix Bauckholt <fbauckho@csclub.uwaterloo.ca> Fri, 19 Feb 2016 22:07:37 -0500
ceo (0.5.26) jessie; urgency=medium
* Repackage for jessie
* Fix build for latest package versions
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 11 Nov 2015 22:39:49 -0500
ceo (0.5.25jessie0) jessie; urgency=low
* Replace mention of the safe with the cup.
* Remind users that club accounts are free.
-- Sean Hunt <scshunt@csclub.uwaterloo.ca> Tue, 22 Jul 2014 14:20:16 -0400
ceo (0.5.24ubuntu5) saucy; urgency=low
* Packaging for saucy.
-- Sean Hunt <scshunt@csclub.uwaterloo.ca> Thu, 05 Dec 2013 15:59:17 -0500
ceo (0.5.24jessie0) jessie; urgency=low
* Packaging for jessie.
-- Luqman Aden <laden@csclub.uwaterloo.ca> Thu, 10 Oct 2013 21:51:26 -0400
ceo (0.5.24squeeze0) oldstable; urgency=low
* Rebuild for squeeze, since a wheezy package was accepted there by accident.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 16 Sep 2013 08:33:58 -0400
ceo (0.5.24) stable; urgency=low
* Fix bug introduced in Kerberos change.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 16 Sep 2013 08:28:51 -0400
ceo (0.5.23) stable; urgency=low
* Stable is now wheezy; rebuild.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 07 Sep 2013 11:59:24 -0400
ceo (0.5.22) stable; urgency=low
* Drop support for Kerberos LDAP backend; this is not the current CSC setup.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 07 Sep 2013 11:45:33 -0400
ceo (0.5.21) testing; urgency=low
* Build with older protoc-c for compatibility with squeeze.
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Tue, 28 May 2013 11:14:36 -0400
ceo (0.5.20) testing; urgency=low
* Work around bug in libgssapi 2.0.25 present in wheezy.
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Tue, 28 May 2013 10:45:09 -0400
ceo (0.5.19ubuntu2) quantal; urgency=low
* Packaging for quantal.
-- Owen Michael Smith <omsmith@gwem.csclub.uwaterloo.ca> Sat, 25 May 2013 19:46:52 -0400
ceo (0.5.19ubuntu1) precise; urgency=low
* Added precise package with changes
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 06 Feb 2013 23:44:18 -0500
ceo (0.5.19) stable; urgency=low
* Updated mail, adduser host to be aspartame, not ginseng (following filesystem migration)
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 06 Feb 2013 23:36:46 -0500
ceo (0.5.18ubuntu1) precise; urgency=low
* Added precise package with changes.
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 12 Sep 2012 08:42:02 -0400
ceo (0.5.18) stable; urgency=low
* Updated mailman host to be mail, not caffeine (following mail container migration)
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Mon, 10 Sep 2012 19:06:16 -0400
ceo (0.5.17ubuntu2) precise; urgency=low
* Accidentally merged in broken changes. Fixing.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Thu, 26 Apr 2012 15:19:03 -0400
ceo (0.5.17) stable; urgency=low
* Change behavior of ceod to add Kerberos principal,
* as opposed to changing principal password.
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Fri, 16 Mar 2012 15:27:35 -0400
ceo (0.5.16) stable; urgency=low
* Fix CEO for CMC by allow mailman to be disabled.
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sat, 17 Sep 2011 16:36:01 -0400
ceo (0.5.14) stable; urgency=low
* Add support for sending a welcome message.
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Fri, 26 Aug 2011 00:59:08 -0400
ceo (0.5.13) stable; urgency=low
* Fix Mailman path
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 09 May 2011 19:12:09 -0400
ceo (0.5.12) stable; urgency=low
* Change sudoRunAs to sudoRunAsUser.
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 13 Mar 2011 03:24:30 -0400
ceo (0.5.11) stable; urgency=low
* Fix library check in and search bug introduced in 0.5.9+nmu1.
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Fri, 04 Mar 2011 16:52:32 -0500
ceo (0.5.10) stable; urgency=low
* Fix squeeze build warnings
* Add m4burns to debian/control
-- Michael Spang <mspang@csclub.uwaterloo.ca> Fri, 04 Mar 2011 00:47:09 -0500
ceo (0.5.9+nmu1) stable; urgency=low
* Non-maintainer upload.
* Fix library book search page to display message when no books are found.
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Mon, 28 Feb 2011 13:00:24 -0500
ceo (0.5.9) stable; urgency=low
* Fix build for squeeze.
-- Michael Spang <mspang@csclub.uwaterloo.ca> Thu, 14 Oct 2010 14:22:04 -0400
ceo (0.5.8+nmu1) stable; urgency=low
* fixed bug reported by jdonland
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sun, 26 Sep 2010 22:32:50 -0400
ceo (0.5.8) stable; urgency=low
* tab support in most forms (note that the tab key is already bound for the LDAP lookup fields)
* new members can be added for multiple terms without going through renewal
* fix for the squeeze version of urwid
* new members are automatically added to csc-general
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 25 Sep 2010 01:04:02 -0400
ceo (0.5.7+nmu4) stable; urgency=low
* Non-maintainer upload.
* add Office Manager position to positions list
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Tue, 14 Sep 2010 18:19:50 -0400
ceo (0.5.7+nmu3) stable; urgency=low
* Added phpmyadmin to mysql info file generated by CEO
-- Michael Ellis <me@michaelellis.ca> Thu, 19 Aug 2010 14:06:16 -0400
ceo (0.5.7+nmu2) stable; urgency=low
* Removed the need for separate entries to manage office and syscom
* Added check to ensure group is valid
-- Michael Ellis <me@michaelellis.ca> Fri, 18 Jun 2010 21:29:48 -0400
ceo (0.5.7+nmu1) stable; urgency=low
* Non-maintainer upload.
* Removed uwdir lookup for expired accounts emailing
-- Michael Ellis <m2ellis@caffeine.csclub.uwaterloo.ca> Tue, 18 May 2010 18:18:02 -0400
ceo (0.5.7) stable; urgency=low
[ Michael Spang ]
* Fix expiredaccounts
[ Michael Ellis ]
* Reworded expired account email. Club rep accounts can be renewed for
free (as usual).
[ Michael Spang ]
* Readd quota support
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 09 May 2010 02:10:48 -0400
ceo (0.5.6) stable; urgency=low
[ Michael Spang ]
* Fix use of freopen
* Fix auth for mysql database creation
[ Jeremy Brandon Roman ]
* added ability to use first letter of menu items
[ Michael Spang ]
* Remove ternary operators
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 20 Dec 2009 13:45:48 -0500
ceo (0.5.5) stable; urgency=low
* Add missing dependency on python-mysql
* Add CLI version of mysql thing
-- Michael Spang <mspang@csclub.uwaterloo.ca> Mon, 02 Nov 2009 20:34:52 +0000
ceo (0.5.4) stable; urgency=low
* Switch from SCTP to TCP
-- Michael Spang <mspang@csclub.uwaterloo.ca> Mon, 02 Nov 2009 03:04:52 +0000
ceo (0.5.3) stable; urgency=low
* Fix gss error reporting bug
* Clarify email forwarding upon renewal
* Fail fast if not authenticated
* Encrypt all post-auth ceoc<->ceod communication
* Improve error handling when writing
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sat, 24 Oct 2009 14:49:51 -0400
ceo (0.5.2) stable; urgency=low
* Clarify search operation in menu
* Move some code
* Fix segfault
* Write mysql file to ~club
* Kill mathsoclist
* Blacklist orphaned/expired from updateprograms
* Add status thing
* Force redraw after status thing
-- Michael Spang <mspang@csclub.uwaterloo.ca> Wed, 16 Sep 2009 18:32:56 -0400
ceo (0.5.1) stable; urgency=low
* Add mysql magic.
* Add email forwarding magic.
* Labels on the menu.
-- Michael Spang <mspang@csclub.uwaterloo.ca> Wed, 09 Sep 2009 17:54:49 -0400
ceo (0.5.0) stable; urgency=low
* Add ceo daemon.
-- Michael Spang <mspang@uwaterloo.ca> Thu, 30 Jul 2009 00:19:42 -0400
ceo (0.4.24) stable; urgency=low
* Bump standards version.
-- Michael Spang <mspang@uwaterloo.ca> Wed, 29 Jul 2009 07:31:24 -0400
ceo (0.4.23) stable; urgency=low
* CEO library now only finds books that are signed out as being overdue.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 03:30:01 -0500
ceo (0.4.22) stable; urgency=low
* CEO now closes window when it should. (Sorry)
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 02:25:01 -0500
ceo (0.4.21) stable; urgency=low
* CEO Library can now add boox.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 02:09:01 -0500
ceo (0.4.20) stable; urgency=low
* Update kadmin headers
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Tue, 24 Feb 2009 16:08:12 -0500
ceo (0.4.19) stable; urgency=low
* Rebuild for lenny.
-- Michael Spang <mspang@uwaterloo.ca> Tue, 17 Feb 2009 22:23:30 -0500
ceo (0.4.18) stable; urgency=low
[ Michael Gregson ]
* Added new search function, and books now display due dates.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 29 Jan 2009 01:04:00 -0500
ceo (0.4.17) stable; urgency=low
[ Michael Gregson ]
* Books can now be returned!!! Yay!
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 23:42:00 -0500
ceo (0.4.16) stable; urgency=low
[ Michael Gregson ]
* Fixed error in calling of members.current
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 22:40:00 -0500
ceo (0.4.15) stable; urgency=low
[ Michael Gregson ]
* Fixed incorrect usage of members.registered in library
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 19:10:00 -0500
ceo (0.4.14) stable; urgency=low
[ Michael Gregson ]
* Corrected members.registered() to account for
non-existent members.
* Corrected overdue search.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 18:40:00 -0500
ceo (0.4.13) stable; urgency=low
[ Michael Gregson ]
* Add user validation to library system
* Add search function to library
* Can search for overdue books.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 17:00:00 -0500
ceo (0.4.12) stable; urgency=low
[ Michael Gregson ]
* Rewrite library system.
* Support for book checkout and return on sqlobject backends
* We dont die when not having LDAP to connect to.
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 14 Jan 2009 19:38:00 -0400
ceo (0.4.11) stable; urgency=low
[ David Bartley ]
* Add library path to config
[ Nick Guenther ]
* library backend, initial version
* Library GUI is coming, but awkwardsadface
* CEO notifies of it's connect attempt (since if LDAP is being sad
then CEO hangs without any indication of why)
* Search works whoooo
* We've gone from not having a library, to having a basic library that
almost works! There's kinks and the code could be cleaner in places,
but it's a really decent start for only a day's work. yayyyy python
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Mon, 02 Jun 2008 23:49:09 -0400
ceo (0.4.10) stable; urgency=low
[ David Bartley ]
* Always call deauth
* Add configurable refquota support
[ Michael Spang ]
* Auth as ceo/admin for zfsaddhomedir
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Wed, 28 May 2008 02:01:53 -0400
ceo (0.4.9) stable; urgency=low
* Move mathsoc regex and exception userid's into config
* Import sys
* Fix help text
* Use refquota instead of quota
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Thu, 15 May 2008 22:14:50 -0400
ceo (0.4.8) stable; urgency=low
* No point in recommending quota anymore
* Add help for command-line ceo
* Drop memberUid support; all groups use uniqueMember now
* Simplify help
* Improve help message
* Add mathsoclist command
* Add term argument to mathsoclist
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Thu, 24 Apr 2008 19:57:12 -0400
ceo (0.4.7) stable; urgency=low
[ David Bartley ]
* Add zfsaddhomedir
[ Michael Spang ]
* Initialize program name in openlog
* Whitespace fix
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Tue, 25 Mar 2008 14:13:36 -0400
ceo (0.4.6) stable; urgency=low
* Fix off-by-one error
* Search menu bug fix
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Sat, 15 Mar 2008 02:13:25 -0400
ceo (0.4.5) stable; urgency=low
* Don't offer to update to an empty program
* It's doubtful that a user would need to mount a floppy disk
* Add library stubs and refactor menu creation
* Add inactive command
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Mon, 10 Mar 2008 00:35:09 -0400
ceo (0.4.4) stable; urgency=low
[ David Bartley ]
* Added console app
* Install ceo.console
* Set params=[] by default in ldapi.search
* Add list_all and uid2dn; make list_* return {dn:...} instead of
{uid:...}
* Implement updateprogram (interactively updates program from uwldap)
* Sort memberlist
* Add office staff to floppy group
* Refactor uwldap constants
* Implement expired account emails
* Add expired-account and notify-hook to git
* Send to both uwdir email and csclub email
* Fix bug in group management
* Refactor console code
[ Michael Spang ]
* Fix magic
* Fix magic, really
* Actually do magic, tested this time
* Fix use of club settings in addmember
* Fix use of member UID range in addclub
-- Michael Spang <mspang@uwaterloo.ca> Fri, 25 Jan 2008 20:36:42 -0500
ceo (0.4.3) stable; urgency=low
* Add cro to positions
* Fix typo
* Fix group modification code
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Tue, 08 Jan 2008 19:58:19 -0500
ceo (0.4.2) stable; urgency=low
[ David Bartley ]
* Add password prompt
* Only allow 3 password attempts
* Remove extraneous whitespace
* Add tab completion for userid fields
* Clarify group failure
* Improve exception handling
* Improved tab-completion
* Add sudo entry to ldap when creating clubs
[ Michael Spang ]
* Reorganize build process
* Reorganize namespace
* Use python-ldap directly in members
* Cleanup warnings: unused imports, etc
* Better error handling in the gui
* Fix list by term and list by name
* Display "Connecting..." during gui startup
* Remove chfn and chsh and allow shell changes in the gui
* Enlarge the shells list
* Don't try to install chsh and chfn
* Remove python-pam dependency
* Remove ceoquery
* Add manpages and remove TODO
* Allow init of MemberException with no arguments
* Remove obsolete function ceo_add_club()
* POSIX ACL support in addhomedir and addclub
* Add club representative support
* Show "Rep Terms" when displaying member
* Conditionally shows terms
* Add git-buildpackage configuration
-- Michael Spang <mspang@uwaterloo.ca> Mon, 24 Dec 2007 13:41:27 -0500
ceo (0.4.1) stable; urgency=low
* Minor fixes
-- Michael Spang <mspang@uwaterloo.ca> Wed, 12 Dec 2007 03:40:17 -0500
ceo (0.4.0) stable; urgency=low
* New release
-- Michael Spang <mspang@uwaterloo.ca> Wed, 12 Dec 2007 03:07:05 -0500
ceo (0.3.9) stable; urgency=low
* New release
-- Michael Spang <mspang@uwaterloo.ca> Mon, 10 Dec 2007 03:56:06 -0500
ceo (0.3.3) stable; urgency=low
* Add club and group modify page
* Add sasl support
* Complete group and position management
* Remove ceo-old
* Fix bugs
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Wed, 21 Nov 2007 20:56:14 -0500
ceo (0.3.2) unstable; urgency=low
[ Michael Spang ]
* Fix CEO group add for rfc2307bis
[ David Bartley ]
* Add 'search by group'
* Lookup name and program based on uwdir id
* Add group and position management
-- Michael Spang <mspang@uwaterloo.ca> Wed, 21 Nov 2007 17:21:40 -0500
ceo (0.3.1) unstable; urgency=low
* addhomedir: invalidate nscd tables
* ceo-urwid: add create club account menuitem
* Add urwid to dependencies
-- Michael Spang <mspang@uwaterloo.ca> Fri, 5 Oct 2007 10:16:41 -0400
ceo (0.3.0) unstable; urgency=low
* Add experimental urwid-based GUI
* Rip out studentid support
* Unbreak termusers in ceoquery
* Increase widths of UI windows
* PgSQL to LDAP transition
-- Michael Spang <mspang@uwaterloo.ca> Tue, 25 Sep 2007 04:00:10 -0400
ceo (0.2.4) unstable; urgency=low
* Added csc.schema.
* Vim-style keybindings for CEO menus.
* Bug fix: call setreuid(euid, euid) in csc-chfn and csc-chsh.
* Bug fix: run less in "secure" mode.
* Renamed package to ceo.
-- Michael Spang <mspang@uwaterloo.ca> Mon, 28 May 2007 02:05:28 -0400
csc (0.2.3) unstable; urgency=low
* Added "ceoquery", a utility to retrieve lists of members and users.
* Added "csc-chsh" and "csc-chfn" utilities.
* Bug fix: build_gecos() did not include enough commas between fields.
* Member attributes are now added to LDAP as well as the PgSQL database.
-- Michael Spang <mspang@uwaterloo.ca> Sun, 18 Feb 2007 21:35:28 -0500
csc (0.2.2) unstable; urgency=low
* Added "addhomedir", a utility to create home directories for new users.
* Bug fix: CEO still referenced an exception that changed name in 0.2.
* Documentation updates.
-- Michael Spang <mspang@uwaterloo.ca> Mon, 29 Jan 2007 01:47:31 -0500
csc (0.2.1) unstable; urgency=low
* Documentation updates only
* Added docs/GIT-HOWTO and docs/INSTALLING
-- Michael Spang <mspang@uwaterloo.ca> Sun, 28 Jan 2007 01:24:37 -0500
csc (0.2) unstable; urgency=low
* Tests added to most Python modules.
* Split configuration files.
* Added maintainer scripts to manage permissions during install and purge.
* Added functions for use by tools planned for next release (chfn, etc).
* Added support for account "repair", which will recreate LDAP entries
and principals if necessary.
* The recreate account menu option in ceo is now active.
* Replaced instances of "== None" and "!= None" with "is None" and
"is not None", respectively (thanks to: Nick Guenther).
* Renamed terms.valid() to terms.validate() (thanks to: Nick Guenther).
-- Michael Spang <mspang@uwaterloo.ca> Fri, 26 Jan 2007 20:10:14 -0500
csc (0.1) unstable; urgency=low
* Initial Release.
-- Michael Spang <mspang@uwaterloo.ca> Thu, 28 Dec 2006 04:07:03 -0500

1
debian/compat vendored
View File

@ -1 +0,0 @@
5

42
debian/control vendored
View File

@ -1,42 +0,0 @@
Source: ceo
Section: admin
Priority: optional
Maintainer: Systems Committee <syscom@csclub.uwaterloo.ca>
Uploaders: Michael Spang <mspang@csclub.uwaterloo.ca>,
David Bartley <dtbartle@csclub.uwaterloo.ca>,
Michael Gregson <mgregson@csclub.uwaterloo.ca>,
Jeremy Roman <jbroman@csclub.uwaterloo.ca>,
Marc Burns <m4burns@csclub.uwaterloo.ca>,
Zachary Seguin <ztseguin@csclub.uwaterloo.ca>
Build-Depends: debhelper (>= 5.0.0), python-dev (>= 2.4), dh-python, python, libkrb5-dev (>= 1.7), libldap2-dev, libsasl2-dev, libsctp-dev, libprotobuf-c-dev, libacl1-dev, protobuf-compiler, protobuf-c-compiler
Standards-Version: 3.9.1
Package: ceo-common
Architecture: all
Depends: ${misc:Depends}
Description: Computer Science Club Common Files
This package contains the CSC Electronic Office
common files.
Package: ceo-python
Architecture: all
Replaces: ceo-gui
Conflicts: ceo-gui
Depends: ceo-clients, library, python-ldap, python-urwid, python-sqlobject, python-protobuf, python-psycopg | python-psycopg2, python-mysqldb, python-dnspython, ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}
Description: Computer Science Club Administrative GUI
This package contains the CSC Electronic Office
graphical user interface.
Package: ceo-clients
Architecture: any
Depends: ceo-common, ${shlibs:Depends}, ${misc:Depends}
Description: Computer Science Club Administrative Clients
This package contains the CSC Electronic Office
client programs.
Package: ceo-daemon
Architecture: any
Depends: ceo-python, ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}
Description: Computer Science Club Administrative Daemon
This package contains the CSC Electronic Office
daemon.

31
debian/copyright vendored
View File

@ -1,31 +0,0 @@
This package was debianized by Michael Spang <mspang@uwaterloo.ca> on
Thu, 28 Dec 2006 04:07:03 -0500.
Copyright (c) 2006-2007, Michael Spang
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the University of Waterloo Computer Science Club
nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

52
debian/rules vendored
View File

@ -1,52 +0,0 @@
#!/usr/bin/make -f
CFLAGS := -g -O2 -fstack-protector-all -fPIE
LDFLAGS := -pie -Wl,--as-needed
build:
cd src && make CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
clean:
dh_testdir
dh_testroot
dh_clean
$(MAKE) -C src clean
python setup.py -q clean -a --build-base=build-ceo
python setupd.py -q clean -a --build-base=build-ceod
rm -rf build-ceo build-ceod
install: build
dh_testdir
dh_testroot
dh_installdirs
python setup.py -q build --build-base=build-ceo install --no-compile -O0 --prefix=/usr --root=debian/ceo-python
python setupd.py -q build --build-base=build-ceod install --no-compile -O0 --prefix=/usr --root=debian/ceo-daemon \
--install-scripts=/usr/lib/ceod
$(MAKE) -C src DESTDIR=$(CURDIR)/debian/ceo-clients PREFIX=/usr install_clients
$(MAKE) -C src DESTDIR=$(CURDIR)/debian/ceo-daemon PREFIX=/usr install_daemon
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installexamples
dh_installinit --name ceod -- start 95 2 3 4 5 . stop 05 0 1 6 .
dh_install
dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
dh_python2
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary-indep:
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

View File

@ -1,55 +0,0 @@
Getting the Source
------------------
The sources for this project are in a git repository. Git is a distributed
revision control tool originally created by Linus Torvalds to track the Linux
kernel tree. With git, there is generally no central repository that everyone
commits their changes to. Instead, collaboration is done by "pulling" changes
from the repositories of other contributors.
When you check out the sources, you will get the entire history along with
the latest version. You do not need any special permissions to clone a
repository and start making changes.
To retrieve the ceo sources, clone the public repository:
git clone /users/git/public/pyceo.git
Making Changes
--------------
Now that you have your own repository, you can start making changes. You
may can add, update, or delete files as necessary and then commit these
changes to your local repository. Then you can make these changes available
to others. Read the documentation to learn more about basic git usage.
Git Resources
-------------
For a tutorial, see [1] generally and [2] if you are familiar with CVS.
The manpages for git are also invaluable, use `man git-foo` to view them,
or look online at [3].
Finally, if you're interested in how git works internally, see [4] for
documentation of the "core" commands, and [5] for documentation of the
repository format.
[1] http://www.kernel.org/pub/software/scm/git/docs/tutorial.html
[2] http://www.kernel.org/pub/software/scm/git/docs/cvs-migration.html
[3] http://www.kernel.org/pub/software/scm/git/docs/
[4] http://www.kernel.org/pub/software/scm/git/docs/core-tutorial.html
[5] http://www.kernel.org/pub/software/scm/git/docs/repository-layout.html
Setting up a Public Repository
------------------------------
If you make changes, you will probably want to share them with the other
contributors. The only thing other people need to fetch your changes into
their own repository is the location of your repository and read access to
it. With that they can use `git pull` to fetch and merge your changes.
If you want to make changes but not publish them immediately after each
commit, create a second "public" repository and use "git push" when you
are ready to make your changes public. Refer to the Internet for more
details.

View File

@ -1,78 +0,0 @@
BUILDING AND INSTALLING
-----------------------
This document describes the steps needed to get the package built and
installed on CSC systems. If you don't have authority to do this, you
can safely skip it.
Building the Package
--------------------
To build a Debian package out of the sources, run `debuild` at the top
of the source tree. If all goes well, a Debian package and source tarball
will appear in the parent directory.
Do NOT build the package as root (rather, don't build anything as root in
general). Use 'fakeroot' so that the permissions in the .deb can be set
correctly. It is only necessary to build as root if you are using pbuilder,
which builds in a chroot.
You can examine the package with tools like dpkg-deb(1) and debdiff(1).
One useful command is `dpkg-deb -c <deb-file>`. This will give you a list
of files that will be installed.
If your build is a development build, you can safely delete it (it will
be overwritten anyway if a subsequent build has the same version number).
Otherwise copy it to a safe place.
Installing the Package
----------------------
So you have made your changes and have committed them to your repository, your
last test build was successful, and you're ready to install the package.
To install the package:
1. Compare your debian/changelog with the changelog from the currently
installed package. If your changelog has entries missing, find and
merge them with git. This will ensure you do not overwrite others'
changes.
The changelog on caffeine is in "/usr/share/doc/csc/changelog.gz".
2. Describe your changes in debian/changelog
Run "dch -v new_version" and add bullets to describe all changes
in the new version. Note that this format must be readable by
dpkg-parsechangelog.
4. Commit the changelog update to your repository
You might want to mention that you are installing the package
(i.e. it's a "release") in the commit message.
5. Build the package
Use 'debuild' to build the package.
5. Install the package
Run `dpkg -i csc_<version>_<arch>.deb`.
6. Archive the package file and source
You will be left with four files: a .deb, a .tar.gz, a .changes,
and a .dsc. Save these to a safe place (preferably in /users/git
so other can find them easily).
7. Push to /users/git/public/pyceo.git
This is a convenient hub for pushing/pulling between contributors.
You must be in the 'git' group to do this - if you're able to install
the package you will certainly be able to add yourself to this group.
If everyone follows these steps, every installed version will be a
descendant of the previous. Further, since old versions are archived it
will be easy to quickly get ceo working again after a bad update.

View File

@ -1,13 +0,0 @@
.TH ADDCLUB 1 "December 16, 2007"
.SH NAME
addclub \- add club accounts to the directory
.SH SYNOPSIS
.B addclub
userid clubname
.SH DESCRIPTION
.B Addclub
creates an LDAP entry and home directory for new club accounts.
.SH SEE ALSO
.BR ceo (1).
.SH AUTHOR
Michael Spang <mspang@csclub.uwaterloo.ca>

View File

@ -1,16 +0,0 @@
.TH ADDMEMBER 1 "December 16, 2007"
.SH NAME
addmember \- add club members to the directory
.SH SYNOPSIS
.B addmember
userid name [ program ]
.SH DESCRIPTION
.B Addmember
performs all tasks necessary for creation of a new CSC member. It creates
an LDAP entry, Kerberos principal, and home directory for the new member.
It does NOT register the new member for any terms. This must be done after
the member is created.
.SH SEE ALSO
.BR ceo (1).
.SH AUTHOR
Michael Spang <mspang@csclub.uwaterloo.ca>

View File

@ -1,17 +0,0 @@
.TH CEO 1 "December 16, 2007"
.SH NAME
ceo \- CSC Electronic Office
.SH DESCRIPTION
CSC Electronic Office is used to manage membership registration and
user accounts for the Computer Science Club. It has a graphical
user interface, started by typing
.B ceo
with no arguments.
.PP
.SH SEE ALSO
.BR addmember (1),
.BR addclub (1).
.SH AUTHORS
Michael Spang <mspang@csclub.uwaterloo.ca>
.br
David Bartley <dtbartle@csclub.uwaterloo.ca>

View File

@ -1,13 +0,0 @@
.TH CEOD 1 "September 9, 2009"
.SH NAME
ceo \- CSC Electronic Office Daemon
.SH DESCRIPTION
CSC Electronic Office Daemon is used by CEO for operations
that require superuser privileges.
.B ceo
with no arguments.
.PP
.SH SEE ALSO
.BR ceo (1),
.SH AUTHORS
Michael Spang <mspang@csclub.uwaterloo.ca>

View File

@ -1,48 +0,0 @@
# /etc/csc/accounts.cf: CSC Accounts Configuration
### Member Account Options ###
member_min_id = 20001
member_max_id = 29999
member_shell = "/bin/bash"
member_home = "/users"
member_home_skel = "/users/skel"
### Club Account Options ###
club_min_id = 30001
club_max_id = 39999
club_shell = "/bin/bash"
club_home = "/users"
club_home_skel = "/users/skel"
### Administrative Account Options ###
admin_min_id = 10001
admin_max_id = 19999
### LDAP Options ###
ldap_server_url = "ldaps://ldap-master.csclub.uwaterloo.ca"
ldap_users_base = "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
ldap_groups_base = "ou=Group,dc=csclub,dc=uwaterloo,dc=ca"
ldap_sudo_base = "ou=SUDOers,dc=csclub,dc=uwaterloo,dc=ca"
ldap_sasl_mech = "GSSAPI"
ldap_sasl_realm = "CSCLUB.UWATERLOO.CA"
ldap_admin_principal = "ceod/admin@CSCLUB.UWATERLOO.CA"
### Kerberos Options ###
krb5_realm = "CSCLUB.UWATERLOO.CA"
krb5_admin_principal = "ceod/admin@CSCLUB.UWATERLOO.CA"
### Spam ###
notify_hook = "/etc/csc/spam/new-member"
expire_hook = "/etc/csc/spam/expired-account"
### Miscellaneous ###
username_regex = "^[a-z][-a-z0-9]*$"
min_password_length = 4
shells_file = "/etc/shells"

View File

@ -1,35 +0,0 @@
# CSC Member Information Schema
attributetype ( 1.3.6.1.4.1.27934.1.1.1 NAME 'term'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{5} )
attributetype ( 1.3.6.1.4.1.27934.1.1.2 NAME 'program'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.27934.1.1.3 NAME 'studentid'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{8} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.27934.1.1.4 NAME 'position'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
attributetype ( 1.3.6.1.4.1.27934.1.1.5 NAME 'nonMemberTerm'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{5} )
objectclass ( 1.3.6.1.4.1.27934.1.2.1 NAME 'member'
SUP top AUXILIARY
MUST ( cn $ uid )
MAY ( studentid $ program $ term $ nonMemberTerm $ description $ position ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.2 NAME 'club'
SUP top AUXILIARY
MUST ( cn $ uid ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.3 NAME 'group'
SUP top STRUCTURAL
MUST ( cn )
MAY ( uniqueMember ) )

View File

@ -1,2 +0,0 @@
members_list = csc-general
list_domain = csclub.uwaterloo.ca

View File

@ -1 +0,0 @@
phosphoric-acid adduser root 0x01

View File

@ -1 +0,0 @@
phosphoric-acid mail root 0x02

View File

@ -1 +0,0 @@
mail mailman list 0x04

View File

@ -1 +0,0 @@
caffeine mysql mysql 0x03

View File

@ -1,50 +0,0 @@
#!/bin/sh
name=$1
email=$2
shift 2
tmp="$(tempfile)"
trap "rm $tmp" 0
exec >"$tmp"
echo "From: Computer Science Club <ceo+expired@csclub.uwaterloo.ca>"
echo "Reply-to: CSClub Exec <exec@csclub.uwaterloo.ca>"
echo "To: $name <$email>"
echo "Subject: [CSClub] Account Expiration"
echo ""
echo "Hello,
We noticed that your Computer Science Club membership has expired. We would
like to remind you of the many benefits of being a member of the club:
* 4 GiB of disk quota
* Web space
* Email address
* Shell account
* Access to our library
If you would like to renew your membership the fee is \$2 per term; club rep
accounts may be renewed for free. You may use one of the following methods to
pay the renewal fee:
* Come by our office (MC 3036)
* Send us a PayPal donation and send us the transaction id; see
http://csclub.uwaterloo.ca/about/donations for details
* Mail us a cheque; here's our address:
Computer Science Club
Math & Computer 3036/3037
University of Waterloo
200 University Avenue West
Waterloo, ON N3L 3G1
Canada
If you have any questions, feel free to contact us by phone at
(519) 888-4567 x33870, or by email at exec@csclub.uwaterloo.ca.
Regards,
The Computer Science Club"
exec >&- 2>&-
/usr/sbin/sendmail -t -f "ceo@csclub.uwaterloo.ca" < "$tmp"

View File

@ -1,13 +0,0 @@
#!/bin/bash -p
# This is a privileged script.
IFS=$' \t\n'
PATH=/usr/bin:/bin
unset ENV BASH_ENV CDPATH
umask 077
cd `dirname $0`
export CEO_PROG="$1" CEO_AUTH="$2" CEO_USER="$3" CEO_NAME="$4" CEO_DEPT="$5" CEO_STATUS="$6"
export CEO_OUTPUT="$(cat)"
run-parts --umask=077 new-member.d

View File

@ -1,70 +0,0 @@
#!/bin/bash -p
# This is a privileged script.
IFS=$' \t\n'
PATH=/usr/bin:/bin
unset ENV BASH_ENV CDPATH
umask 077
prog=$CEO_PROG
auth=$CEO_AUTH
tmp="$(tempfile)"
trap "rm $tmp" 0
exec >"$tmp"
authrn="$(getent passwd "$auth" | awk -F: '{ print $5 }' | sed -e 's/,.*//')"
h_from="$prog <ceo+$prog@csclub.uwaterloo.ca>"
h_to="Membership and Accounts <ceo@csclub.uwaterloo.ca>"
h_cc="$authrn <$auth@csclub.uwaterloo.ca>"
if [[ "$prog" = addmember || "$prog" == addclubrep ]]; then
user="$CEO_USER" name="$CEO_NAME" dept="$CEO_DEPT" status="$CEO_STATUS"
subj="New Member: $user"
test -z "$dept" && dept="things unknown"
body="Name: $name
Account: $user
Program: $dept
Added by: $auth"
elif [[ "$prog" = addclub ]]; then
user="$CEO_USER" name="$CEO_NAME" status="$CEO_STATUS"
subj="New Club Account: $user"
body="Club: $name
Account: $user
Added by: $auth"
else
exit 1
fi
output="$CEO_OUTPUT"
if test "$status" = "failure"; then
subj="$subj (FAILURES)"
fi
echo "From: $h_from"
echo "To: $h_to"
echo "Cc: $h_cc"
echo "X-Auth-User: $auth"
echo "X-New-User: $user"
echo "X-New-Name: $name"
echo "Subject: $subj"
echo
echo "$body" | fmt -s
echo
if test "$status" = "success"; then
echo all failures went undetected
elif test -n "$output"; then
echo "$output"
fi
echo
echo Your Friend,
echo "$prog"
exec >&2
env - /usr/sbin/sendmail -t -f "ceo@csclub.uwaterloo.ca" < "$tmp"

View File

@ -1,49 +0,0 @@
#!/bin/sh
name=$1
email=$2
shift 2
tmp="$(tempfile)"
trap "rm $tmp" 0
exec >"$tmp"
echo "From: Computer Science Club <ceo+expired@csclub.uwaterloo.ca>"
echo "Reply-to: CSClub Exec <exec@csclub.uwaterloo.ca>"
echo "To: $name <$email>"
echo "Subject: [CSClub] Account Expiration"
echo ""
echo "Hello,
We noticed that your Computer Science Club membership has expired. We would
like to remind you of the many benifits of being a member of the club:
* 2 GiB of disk quota
* Web space
* Email address
* Shell account
* Access to our library
If you would like to renew your membership (the fee is \$2 per term), we have
various methods of doing so:
* Come by our office (MC 3036)
* Send us a PayPal donation and send us the transaction id; see
http://csclub.uwaterloo.ca/about/donations for details
* Mail us a cheque; here's our address:
Computer Science Club
Math & Computer 3036/3037
University of Waterloo
200 University Avenue West
Waterloo, ON N3L 3G1
Canada
If you have any questions, feel free to contact us by phone at
(519) 888-4567 x33870, or by email at exec@csclub.uwaterloo.ca.
Regards,
The Computer Science Club"
exec >&- 2>&-
/usr/sbin/sendmail -t -f "ceo@csclub.uwaterloo.ca" < "$tmp"

View File

@ -1,71 +0,0 @@
#!/bin/bash -p
# This is a privileged script.
IFS=$' \t\n'
PATH=/usr/bin:/bin
unset ENV BASH_ENV CDPATH
umask 077
prog=$1
auth=$2
shift 2
tmp="$(tempfile)"
trap "rm $tmp" 0
exec >"$tmp"
authrn="$(getent passwd "$auth" | awk -F: '{ print $5 }' | sed -e 's/,.*//')"
h_from="$prog <ceo+$prog@csclub.uwaterloo.ca>"
h_to="Membership and Accounts <ceo@csclub.uwaterloo.ca>"
h_cc="$authrn <$auth@csclub.uwaterloo.ca>"
if test "$prog" = addmember; then
user=$1 name=$2 dept=$3 status=$4; shift 4
subj="New Member: $user"
test -z "$dept" && dept="things unknown"
body="Name: $name
Account: $user
Program: $dept
Added by: $auth"
elif test "$prog" = addclub; then
user=$1 name=$2 status=$4; shift 4
subj="New Club Account: $user"
body="Club: $name
Account: $user
Added by: $auth"
else
exit 1
fi
output=$(cat)
if test "$status" = "failure"; then
subj="$subj (FAILURES)"
fi
echo "From: $h_from"
echo "To: $h_to"
echo "Cc: $h_cc"
echo "X-Auth-User: $auth"
echo "X-New-User: $user"
echo "X-New-Name: $name"
echo "Subject: $subj"
echo
echo "$body" | fmt -s
echo
if test "$status" = "success"; then
echo all failures went undetected
elif test -n "$output"; then
echo "$output"
fi
echo
echo Your Friend,
echo "$prog"
exec >&2
env - /usr/sbin/sendmail -t -f "ceo@csclub.uwaterloo.ca" < "$tmp"

View File

@ -1,11 +0,0 @@
#!/usr/bin/env python
from distutils.core import setup
setup(
name='ceo',
description='CSC Electronic Office',
packages=[ 'ceo', 'ceo.urwid', 'ceo.console' ],
scripts=['bin/ceo'],
)

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
from distutils.core import setup
setup(
name='ceod',
description='CSC Electronic Office Daemon',
scripts=['src/op-mysql','src/op-mailman'],
)

14
src/.gitignore vendored
View File

@ -1,14 +0,0 @@
*.o
.*.swp
.nfs*
/addmember
/addclub
/adduser
/op-adduser
/op-mail
/zfsaddhomedir
/config-test
/ceod
/ceoc
/ceo.pb-c.c
/ceo.pb-c.h

View File

@ -1,86 +0,0 @@
CFLAGS := -g3 -O2 -Wall -Werror -DDEBUG
LDFLAGS := -Wl,--as-needed
INCLUDES := $(shell krb5-config --cflags)
override CFLAGS += -std=gnu99 $(INCLUDES)
DESTDIR :=
PREFIX := /usr/local
BIN_PROGS := addmember addclub ceod
LIB_PROGS := ceoc op-adduser op-mail
EXT_PROGS := config-test
LDAP_OBJECTS := ldap.o
LDAP_LIBS := -lldap
LDAP_PROGS := op-adduser
KRB5_OBJECTS := krb5.o kadm.o
KRB5_LIBS := $(shell krb5-config --libs krb5 kadm-client)
KRB5_PROGS := addmember addclub op-adduser
HOME_OBJECTS := homedir.o
HOME_LIBS := -lacl
HOME_PROGS := op-adduser
NET_OBJECTS := net.o gss.o ops.o
NET_LIBS := $(shell krb5-config --libs gssapi)
NET_PROGS := ceod ceoc
PROTO_OBJECTS := ceo.pb-c.o
PROTO_LIBS := -lprotobuf-c
PROTO_PROGS := op-adduser op-mail addmember addclub
CONFIG_OBJECTS := config.o parser.o
CONFIG_LIBS :=
CONFIG_PROGS := $(LDAP_PROGS) $(KRB5_PROGS) $(NET_PROGS) $(PROTO_PROGS)
UTIL_OBJECTS := util.o strbuf.o
UTIL_PROGS := config-test $(CONFIG_PROGS)
all: $(BIN_PROGS) $(LIB_PROGS) $(EXT_PROGS) ../ceo/ceo_pb2.py
clean:
rm -f $(BIN_PROGS) $(LIB_PROGS) $(EXT_PROGS) *.o ceo.pb-c.c ceo.pb-c.h
rm -f ceo_pb2.py ../ceo/ceo_pb2.py
op-adduser.o addmember.o addclub.o: ceo.pb-c.h
ceo.pb-c.c ceo.pb-c.h: ceo.proto
protoc-c --c_out=. ceo.proto
%: %.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
../ceo/ceo_pb2.py: ceo.proto
protoc --python_out=../ceo ceo.proto
ceod: dmaster.o dslave.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
config-test: config-test.o parser.o
config.o: config.h config-vars.h
install_clients:
install -d $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/lib/ceod
install addmember addclub $(DESTDIR)$(PREFIX)/bin
install ceoc $(DESTDIR)$(PREFIX)/lib/ceod
install_daemon:
install -d $(DESTDIR)$(PREFIX)/sbin $(DESTDIR)$(PREFIX)/lib/ceod
install ceod $(DESTDIR)$(PREFIX)/sbin
install op-adduser $(DESTDIR)$(PREFIX)/lib/ceod
install op-mail $(DESTDIR)$(PREFIX)/lib/ceod
install: install_clients install_daemon
$(NET_PROGS): LDLIBS += $(NET_LIBS)
$(NET_PROGS): $(NET_OBJECTS)
$(LDAP_PROGS): LDLIBS += $(LDAP_LIBS)
$(LDAP_PROGS): $(LDAP_OBJECTS)
$(KRB5_PROGS): LDLIBS += $(KRB5_LIBS)
$(KRB5_PROGS): $(KRB5_OBJECTS)
$(HOME_PROGS): LDLIBS += $(HOME_LIBS)
$(HOME_PROGS): $(HOME_OBJECTS)
$(PROTO_PROGS): LDLIBS += $(PROTO_LIBS)
$(PROTO_PROGS): $(PROTO_OBJECTS)
$(CONFIG_PROGS): LDLIBS += $(CONFIG_LIBS)
$(CONFIG_PROGS): $(CONFIG_OBJECTS)
$(UTIL_PROGS): LDLIBS += $(UTIL_LIBS)
$(UTIL_PROGS): $(UTIL_OBJECTS)
.PHONY: clean all install install_clients install_daemon

View File

@ -1,115 +0,0 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <libgen.h>
#include <syslog.h>
#include "util.h"
#include "config.h"
#include "ldap.h"
#include "krb5.h"
#include "kadm.h"
#include "ceo.pb-c.h"
char *prog = NULL;
static char *name = NULL;
static char *userid = NULL;
static struct option opts[] = {
{ NULL, 0, NULL, '\0' },
};
const char *default_lib_dir = "/usr/lib/ceod";
const char *lib_dir;
static void usage() {
fprintf(stderr, "Usage: %s userid clubname\n", prog);
exit(2);
}
int addclub(void) {
struct strbuf preq = STRBUF_INIT;
struct strbuf pret = STRBUF_INIT;
char cpath[1024];
char *cargv[] = { "ceoc", "adduser", NULL };
int ret = 0;
if (snprintf(cpath, sizeof(cpath), "%s/ceoc", lib_dir) >= sizeof(cpath))
fatal("path too long");
Ceo__AddUser req;
ceo__add_user__init(&req);
req.username = userid;
req.realname = name;
req.type = CEO__ADD_USER__TYPE__CLUB;
strbuf_grow(&preq, ceo__add_user__get_packed_size(&req));
strbuf_setlen(&preq, ceo__add_user__pack(&req, (uint8_t *)preq.buf));
if (spawnvem(cpath, cargv, environ, &preq, &pret, 0))
return 1;
Ceo__AddUserResponse *resp = ceo__add_user_response__unpack(NULL,
pret.len, (uint8_t *)pret.buf);
if (!resp)
fatal("failed to unpack response");
for (int i = 0; i < resp->n_messages; i++) {
if (resp->messages[i]->status) {
ret = -1;
error("%s", resp->messages[i]->message);
} else {
notice("%s", resp->messages[i]->message);
}
}
ceo__add_user_response__free_unpacked(resp, NULL);
strbuf_release(&preq);
strbuf_release(&pret);
return ret;
}
int main(int argc, char *argv[]) {
int opt;
int ret;
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_AUTHPRIV, 1);
configure();
while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) {
switch (opt) {
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
if (argc - optind != 2 && argc - optind != 3)
usage();
userid = argv[optind++];
name = argv[optind++];
lib_dir = getenv("CEO_LIB_DIR") ?: default_lib_dir;
ret = addclub();
free_config();
free(prog);
return ret;
}

View File

@ -1,131 +0,0 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <libgen.h>
#include <syslog.h>
#include "util.h"
#include "config.h"
#include "ldap.h"
#include "krb5.h"
#include "kadm.h"
#include "ceo.pb-c.h"
char *prog = NULL;
static int use_stdin = 0;
static char *name = NULL;
static char *userid = NULL;
static char *program = NULL;
static char password[1024];
static struct option opts[] = {
{ "stdin", 0, NULL, 's' },
{ NULL, 0, NULL, '\0' },
};
const char *default_lib_dir = "/usr/lib/ceod";
const char *lib_dir;
static void usage() {
fprintf(stderr, "Usage: %s userid realname [program]\n", prog);
exit(2);
}
int addmember(void) {
struct strbuf preq = STRBUF_INIT;
struct strbuf pret = STRBUF_INIT;
char cpath[1024];
char *cargv[] = { "ceoc", "adduser", NULL };
int ret = 0;
if (snprintf(cpath, sizeof(cpath), "%s/ceoc", lib_dir) >= sizeof(cpath))
fatal("path too long");
if (ceo_read_password(password, sizeof(password), use_stdin))
return 1;
Ceo__AddUser req;
ceo__add_user__init(&req);
req.username = userid;
req.password = password;
req.program = program;
req.realname = name;
req.type = CEO__ADD_USER__TYPE__MEMBER;
strbuf_grow(&preq, ceo__add_user__get_packed_size(&req));
strbuf_setlen(&preq, ceo__add_user__pack(&req, (uint8_t *)preq.buf));
if (spawnvem(cpath, cargv, environ, &preq, &pret, 0))
return 1;
Ceo__AddUserResponse *resp = ceo__add_user_response__unpack(NULL,
pret.len, (uint8_t *)pret.buf);
if (!resp)
fatal("failed to unpack response");
for (int i = 0; i < resp->n_messages; i++) {
if (resp->messages[i]->status) {
ret = -1;
error("%s", resp->messages[i]->message);
} else {
notice("%s", resp->messages[i]->message);
}
}
ceo__add_user_response__free_unpacked(resp, NULL);
strbuf_release(&preq);
strbuf_release(&pret);
return ret;
}
int main(int argc, char *argv[]) {
int opt;
int ret;
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_AUTHPRIV, 1);
configure();
while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) {
switch (opt) {
case 's':
use_stdin = 1;
break;
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
if (argc - optind != 2 && argc - optind != 3)
usage();
userid = argv[optind++];
name = argv[optind++];
if (argc - optind)
program = argv[optind++];
lib_dir = getenv("CEO_LIB_DIR") ?: default_lib_dir;
ret = addmember();
free_config();
free(prog);
return ret;
}

View File

@ -1,43 +0,0 @@
package ceo;
message StatusMessage {
required int32 status = 1;
required string message = 2;
}
message AddUser {
enum Type {
MEMBER = 1;
CLUB = 2;
CLUB_REP = 3;
}
required Type type = 1;
required string username = 2;
optional string password = 3;
optional string realname = 4;
optional string program = 5;
optional string email = 6;
}
message AddUserResponse {
repeated StatusMessage messages = 1;
}
message UpdateMail {
required string username = 1;
optional string forward = 2;
}
message UpdateMailResponse {
repeated StatusMessage messages = 1;
}
message AddMySQLUser {
required string username = 1;
}
message AddMySQLUserResponse {
repeated StatusMessage messages = 1;
optional string password = 2;
}

View File

@ -1,167 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include "util.h"
#include "net.h"
#include "gss.h"
#include "ops.h"
#include "config.h"
char *prog = NULL;
static struct option opts[] = {
{ NULL, 0, NULL, '\0' },
};
static void usage() {
fprintf(stderr, "Usage: %s op\n", prog);
exit(2);
}
static void send_gss_token(int sock, struct sockaddr *addr, socklen_t addrlen, gss_buffer_t token) {
OM_uint32 maj_stat, min_stat;
if (ceo_send_message(sock, token->value, token->length, MSG_AUTH))
fatalpe("write");
maj_stat = gss_release_buffer(&min_stat, token);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_buffer", maj_stat, min_stat);
}
static void client_gss_auth(int sock, struct sockaddr *addr, socklen_t addrlen) {
gss_buffer_desc incoming_tok, outgoing_tok;
struct strbuf msg = STRBUF_INIT;
uint32_t msgtype;
int complete;
complete = initial_client_token(&outgoing_tok);
for (;;) {
if (outgoing_tok.length)
send_gss_token(sock, addr, addrlen, &outgoing_tok);
else if (!complete)
fatal("no token to send during auth");
if (complete)
break;
if (ceo_receive_message(sock, &msg, &msgtype))
fatal("connection closed during auth");
if (msgtype != MSG_AUTH)
fatal("unexpected message type 0x%x", msgtype);
incoming_tok.value = msg.buf;
incoming_tok.length = msg.len;
complete = process_client_token(&incoming_tok, &outgoing_tok);
}
strbuf_release(&msg);
}
void run_remote(struct op *op, struct strbuf *in, struct strbuf *out) {
const char *hostname = op->hostname;
int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in addr;
uint32_t msgtype;
struct strbuf in_cipher = STRBUF_INIT, out_cipher = STRBUF_INIT;
if (!in->len)
fatal("no data to send");
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9987);
addr.sin_addr = op->addr;
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)))
fatalpe("connect");
client_acquire_creds("ceod", hostname);
client_gss_auth(sock, (sa *)&addr, sizeof(addr));
gss_encipher(in, &in_cipher);
if (ceo_send_message(sock, in_cipher.buf, in_cipher.len, op->id))
fatalpe("write");
if (ceo_receive_message(sock, &out_cipher, &msgtype))
fatal("no response received for op %s", op->name);
gss_decipher(&out_cipher, out);
if (msgtype != op->id)
fatal("wrong message type from server: expected %d got %d", op->id, msgtype);
if (close(sock))
fatalpe("close");
strbuf_release(&in_cipher);
strbuf_release(&out_cipher);
}
int client_main(char *op_name) {
struct op *op = find_op(op_name);
if (!op)
fatal("no such op: %s", op_name);
struct strbuf in = STRBUF_INIT;
struct strbuf out = STRBUF_INIT;
if (strbuf_read(&in, STDIN_FILENO, 0) < 0)
fatalpe("read");
run_remote(op, &in, &out);
if (strbuf_write(&out, STDOUT_FILENO) < 0)
fatalpe("write");
strbuf_release(&in);
strbuf_release(&out);
return 0;
}
int main(int argc, char *argv[]) {
int opt;
int ret;
char *op;
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_USER, 1);
configure();
setup_ops();
setup_fqdn();
while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) {
switch (opt) {
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
if (argc - optind != 1)
usage();
op = argv[optind++];
ret = client_main(op);
free_gss();
free_fqdn();
free_config();
free_ops();
free(prog);
return ret;
}

View File

@ -1,21 +0,0 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "parser.h"
#include "util.h"
void config_var(const char *name, const char *value) {
printf("%s = \"%s\"\n", name, value);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s filename\n\n", argv[0]);
exit(1);
}
config_parse(argv[1]);
return 0;
}

View File

@ -1,24 +0,0 @@
CONFIG_STR(member_shell)
CONFIG_INT(member_min_id)
CONFIG_INT(member_max_id)
CONFIG_STR(member_home)
CONFIG_STR(member_home_skel)
CONFIG_STR(club_shell)
CONFIG_INT(club_min_id)
CONFIG_INT(club_max_id)
CONFIG_STR(club_home)
CONFIG_STR(club_home_skel)
CONFIG_STR(notify_hook)
CONFIG_STR(krb5_realm)
CONFIG_STR(krb5_admin_principal)
CONFIG_STR(ldap_server_url)
CONFIG_STR(ldap_users_base)
CONFIG_STR(ldap_groups_base)
CONFIG_STR(ldap_sudo_base)
CONFIG_STR(ldap_sasl_mech)
CONFIG_STR(ldap_sasl_realm)
CONFIG_STR(ldap_admin_principal)

View File

@ -1,91 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include "config.h"
#include "parser.h"
#include "util.h"
#define DEF_STR NULL
#define DEF_INT LONG_MIN
#define CONFIG_STR(x) char *x = DEF_STR;
#define CONFIG_INT(x) long x = DEF_INT;
#include "config-vars.h"
#undef CONFIG_STR
#undef CONFIG_INT
struct config_var {
const char *name;
void *p;
enum { CONFIG_TYPE_STR, CONFIG_TYPE_INT } type;
};
#define CONFIG_STR(x) {#x, &x, CONFIG_TYPE_STR },
#define CONFIG_INT(x) {#x, &x, CONFIG_TYPE_INT },
static struct config_var config_vars[] = {
#include "config-vars.h"
};
#undef CONFIG_STR
#undef CONFIG_INT
const char *default_config_dir = "/etc/csc";
const char *config_filename = "accounts.cf";
const char *config_dir;
void config_var(char *var, char *val) {
int i;
for (i = 0; i < sizeof(config_vars)/sizeof(*config_vars); i++) {
if (!strcmp(var, config_vars[i].name)) {
switch (config_vars[i].type) {
case CONFIG_TYPE_STR:
if (*(char **)config_vars[i].p)
free(*(char **)config_vars[i].p);
*(char **)config_vars[i].p = xstrdup(val);
break;
case CONFIG_TYPE_INT:
*(long *)config_vars[i].p = config_long(var, val);
break;
default:
fatal("unknown config var type %d", config_vars[i].type);
}
}
}
}
void configure(void) {
int i;
char conffile[1024];
config_dir = getenv("CEO_CONFIG_DIR") ?: default_config_dir;
if (snprintf(conffile, sizeof(conffile), "%s/%s", config_dir, config_filename) >= sizeof(conffile))
fatal("huge config path");
config_parse(conffile);
for (i = 0; i < sizeof(config_vars)/sizeof(*config_vars); i++) {
switch (config_vars[i].type) {
case CONFIG_TYPE_STR:
if (*(char **)config_vars[i].p == DEF_STR)
badconf("undefined string variable: %s", config_vars[i].name);
break;
case CONFIG_TYPE_INT:
if (*(long *)config_vars[i].p == DEF_INT)
badconf("undefined integer variable: %s", config_vars[i].name);
break;
default:
fatal("unknown config var type %d", config_vars[i].type);
}
}
}
void free_config(void) {
for (int i = 0; i < sizeof(config_vars)/sizeof(*config_vars); i++) {
if (config_vars[i].type == CONFIG_TYPE_STR) {
free(*(char **)config_vars[i].p);
*(char **)config_vars[i].p = NULL;
}
}
}

View File

@ -1,10 +0,0 @@
#define CONFIG_STR(x) extern char *x;
#define CONFIG_INT(x) extern long x;
#include "config-vars.h"
#undef CONFIG_STR
#undef CONFIG_INT
void configure(void);
void free_config(void);
extern const char *config_dir;

View File

@ -1,7 +0,0 @@
/* dmain.c */
extern int terminate;
extern int fatal_signal;
/* dslave.c */
void slave_main(int sock, struct sockaddr *addr);
void setup_slave(void);

View File

@ -1,218 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <alloca.h>
#include <fcntl.h>
#include "util.h"
#include "net.h"
#include "config.h"
#include "gss.h"
#include "daemon.h"
#include "ldap.h"
#include "kadm.h"
#include "krb5.h"
#include "ops.h"
static struct option opts[] = {
{ "detach", 0, NULL, 'd' },
{ "quiet", 0, NULL, 'q' },
{ NULL, 0, NULL, '\0' },
};
char *prog = NULL;
int terminate = 0;
int fatal_signal;
static int detach = 0;
static void usage() {
fprintf(stderr, "Usage: %s [--detach]\n", prog);
exit(2);
}
static void signal_handler(int sig) {
if (sig == SIGTERM || sig == SIGINT) {
const char *s = (sig == SIGTERM) ? "terminated" : "interrupt";
notice("shutting down (%s)", s);
terminate = 1;
fatal_signal = sig;
signal(sig, SIG_DFL);
} else if (sig == SIGSEGV) {
error("segmentation fault");
signal(sig, SIG_DFL);
raise(sig);
} else if (sig != SIGCHLD) {
fatal("unhandled signal %d", sig);
}
}
static void setup_signals(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = signal_handler;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
}
static void setup_pidfile(void) {
int fd;
size_t pidlen;
char pidbuf[1024];
const char *pidfile = "/var/run/ceod.pid";
fd = open(pidfile, O_CREAT|O_RDWR, 0644);
if (fd < 0)
fatalpe("open: %s", pidfile);
if (lockf(fd, F_TLOCK, 0))
fatalpe("lockf: %s", pidfile);
if (ftruncate(fd, 0))
fatalpe("ftruncate: %s", pidfile);
pidlen = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid());
if (pidlen >= sizeof(pidbuf))
fatal("pid too long");
if (full_write(fd, pidbuf, pidlen))
fatalpe("write: %s", pidfile);
}
static void setup_daemon(void) {
if (detach) {
if (chdir("/"))
fatalpe("chdir('/')");
pid_t pid = fork();
if (pid < 0)
fatalpe("fork");
if (pid)
exit(0);
if (setsid() < 0)
fatalpe("setsid");
setup_pidfile();
if (!freopen("/dev/null", "r", stdin))
fatalpe("freopen");
if (!freopen("/dev/null", "w", stdout))
fatalpe("freopen");
if (!freopen("/dev/null", "w", stderr))
fatalpe("freopen");
}
}
static void setup_auth(void) {
if (setenv("KRB5CCNAME", "MEMORY:ceod", 1))
fatalpe("setenv");
server_acquire_creds("ceod");
}
static void accept_one_client(int server) {
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, addrlen);
int client = accept(server, (sa *)&addr, &addrlen);
if (client < 0) {
if (errno == EINTR)
return;
fatalpe("accept");
}
pid_t pid = fork();
if (!pid) {
close(server);
slave_main(client, (sa *)&addr);
exit(0);
}
close(client);
}
static int master_main(void) {
int sock, opt;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9987);
addr.sin_addr.s_addr = INADDR_ANY;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0)
fatalpe("socket");
opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
fatalpe("setsockopt");
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
fatalpe("bind");
if (listen(sock, 128))
fatalpe("listen");
setup_fqdn();
setup_signals();
setup_auth();
setup_ops();
setup_daemon();
notice("now accepting connections");
while (!terminate)
accept_one_client(sock);
free_gss();
free_fqdn();
free_ops();
return 0;
}
int main(int argc, char *argv[]) {
int opt;
int ret;
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_DAEMON, 0);
while ((opt = getopt_long(argc, argv, "dq", opts, NULL)) != -1) {
switch (opt) {
case 'd':
detach = 1;
break;
case 'q':
log_set_maxprio(LOG_WARNING);
break;
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
configure();
if (argc != optind)
usage();
ret = master_main();
free_config();
free(prog);
return ret;
}

View File

@ -1,149 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <alloca.h>
#include "util.h"
#include "strbuf.h"
#include "net.h"
#include "config.h"
#include "gss.h"
#include "daemon.h"
#include "ldap.h"
#include "kadm.h"
#include "krb5.h"
#include "ops.h"
static void signal_handler(int sig) {
if (sig == SIGSEGV) {
error("segmentation fault");
signal(sig, SIG_DFL);
raise(sig);
} else if (sig != SIGCHLD) {
fatal("unhandled signal %d", sig);
}
}
static void setup_slave_sigs(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = signal_handler;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_IGN);
if (terminate)
raise(fatal_signal);
}
static void handle_auth_message(struct strbuf *in, struct strbuf *out) {
gss_buffer_desc incoming_tok, outgoing_tok;
OM_uint32 maj_stat, min_stat;
incoming_tok.value = in->buf;
incoming_tok.length = in->len;
process_server_token(&incoming_tok, &outgoing_tok);
strbuf_add(out, outgoing_tok.value, outgoing_tok.length);
if (outgoing_tok.length) {
maj_stat = gss_release_buffer(&min_stat, &outgoing_tok);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_buffer", maj_stat, min_stat);
}
}
static void handle_op_message(uint32_t in_type, struct strbuf *in, struct strbuf *out) {
struct op *op = get_local_op(in_type);
struct strbuf in_plain = STRBUF_INIT, out_plain = STRBUF_INIT;
char *envp[16];
if (!op)
fatal("operation %x does not exist", in_type);
debug("running op: %s", op->name);
/* TEMPORARY */
if (!client_username())
fatal("unathenticated");
gss_decipher(in, &in_plain);
make_env(envp, "LANG", "C", "CEO_USER", client_username(),
"CEO_CONFIG_DIR", config_dir, NULL);
char *argv[] = { op->path, NULL, };
if (spawnvemu(op->path, argv, envp, &in_plain, &out_plain, 0, op->user))
fatal("child %s failed", op->path);
gss_encipher(&out_plain, out);
if (!out->len)
fatal("no response from op");
free_env(envp);
strbuf_release(&in_plain);
strbuf_release(&out_plain);
}
static void handle_one_message(int sock, struct strbuf *in, uint32_t msgtype) {
struct strbuf out = STRBUF_INIT;
if (msgtype == MSG_AUTH)
handle_auth_message(in, &out);
else
handle_op_message(msgtype, in, &out);
if (out.len && ceo_send_message(sock, out.buf, out.len, msgtype))
fatalpe("write");
strbuf_release(&out);
}
void slave_main(int sock, struct sockaddr *addr) {
char addrstr[INET_ADDRSTRLEN];
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
uint32_t msgtype;
struct strbuf msg = STRBUF_INIT;
if (addr->sa_family != AF_INET)
fatal("unsupported address family %d", addr->sa_family);
if (!inet_ntop(AF_INET, &addr_in->sin_addr, addrstr, sizeof(addrstr)))
fatalpe("inet_ntop");
notice("accepted connection from %s", addrstr);
setup_slave_sigs();
while (!terminate) {
if (ceo_receive_message(sock, &msg, &msgtype))
break;
handle_one_message(sock, &msg, msgtype);
}
notice("connection closed by peer %s", addrstr);
strbuf_release(&msg);
/* stuff allocated by dmaster */
free_gss();
free_config();
free_fqdn();
free_ops();
free(prog);
}

307
src/gss.c
View File

@ -1,307 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <grp.h>
#include "util.h"
#include "gss.h"
#include "net.h"
#include "strbuf.h"
static gss_cred_id_t my_creds = GSS_C_NO_CREDENTIAL;
static gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
static gss_name_t peer_name = GSS_C_NO_NAME;
static gss_name_t imported_service = GSS_C_NO_NAME;
static char *peer_principal;
static char *peer_username;
static OM_uint32 ret_flags;
static int complete;
char service_name[128];
void free_gss(void) {
OM_uint32 maj_stat, min_stat;
if (peer_name) {
maj_stat = gss_release_name(&min_stat, &peer_name);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_name", maj_stat, min_stat);
}
if (imported_service) {
maj_stat = gss_release_name(&min_stat, &imported_service);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_name", maj_stat, min_stat);
}
if (context_handle) {
maj_stat = gss_delete_sec_context(&min_stat, &context_handle, GSS_C_NO_BUFFER);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_delete_sec_context", maj_stat, min_stat);
}
if (my_creds) {
maj_stat = gss_release_cred(&min_stat, &my_creds);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_creds", maj_stat, min_stat);
}
free(peer_principal);
free(peer_username);
}
static char *gssbuf2str(gss_buffer_t buf) {
char *msgstr = xmalloc(buf->length + 1);
memcpy(msgstr, buf->value, buf->length);
msgstr[buf->length] = '\0';
return msgstr;
}
static void display_status(char *prefix, OM_uint32 code, int type) {
OM_uint32 maj_stat, min_stat;
gss_buffer_desc msg;
OM_uint32 msg_ctx = 0;
char *msgstr;
maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID,
&msg_ctx, &msg);
(void)maj_stat;
msgstr = gssbuf2str(&msg);
logmsg(LOG_ERR, "%s: %s", prefix, msgstr);
gss_release_buffer(&min_stat, &msg);
free(msgstr);
while (msg_ctx) {
maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID,
&msg_ctx, &msg);
msgstr = gssbuf2str(&msg);
logmsg(LOG_ERR, "additional: %s", msgstr);
gss_release_buffer(&min_stat, &msg);
free(msgstr);
}
}
void gss_fatal(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat) {
logmsg(LOG_ERR, "fatal: %s", msg);
display_status("major", maj_stat, GSS_C_GSS_CODE);
display_status("minor", min_stat, GSS_C_MECH_CODE);
exit(1);
}
static void import_service(const char *service, const char *hostname) {
OM_uint32 maj_stat, min_stat;
gss_buffer_desc buf_desc;
if (snprintf(service_name, sizeof(service_name),
"%s@%s", service, hostname) >= sizeof(service_name))
fatal("service name too long");
buf_desc.value = service_name;
buf_desc.length = strlen(service_name);
maj_stat = gss_import_name(&min_stat, &buf_desc,
GSS_C_NT_HOSTBASED_SERVICE, &imported_service);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_import_name", maj_stat, min_stat);
}
static void check_services(OM_uint32 flags) {
debug("gss services: %sconf %sinteg %smutual %sreplay %ssequence",
flags & GSS_C_CONF_FLAG ? "+" : "-",
flags & GSS_C_INTEG_FLAG ? "+" : "-",
flags & GSS_C_MUTUAL_FLAG ? "+" : "-",
flags & GSS_C_REPLAY_FLAG ? "+" : "-",
flags & GSS_C_SEQUENCE_FLAG ? "+" : "-");
if (~flags & GSS_C_CONF_FLAG)
fatal("confidentiality service required");
if (~flags & GSS_C_INTEG_FLAG)
fatal("integrity service required");
if (~flags & GSS_C_MUTUAL_FLAG)
fatal("mutual authentication required");
}
void server_acquire_creds(const char *service) {
OM_uint32 maj_stat, min_stat;
OM_uint32 time_rec;
if (!strlen(fqdn.buf))
fatal("empty fqdn");
import_service(service, fqdn.buf);
notice("acquiring credentials for %s", service_name);
maj_stat = gss_acquire_cred(&min_stat, imported_service, GSS_C_INDEFINITE,
GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &my_creds,
NULL, &time_rec);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_acquire_cred", maj_stat, min_stat);
/* Work around bug in libgssapi 2.0.25 / gssapi_krb5 2.2:
* The expiry time returned by gss_acquire_cred is always zero. */
{
int names_match = 0;
gss_name_t cred_service;
gss_cred_usage_t cred_usage;
maj_stat = gss_inquire_cred(&min_stat, my_creds, &cred_service, &time_rec, &cred_usage, NULL);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_inquire_cred", maj_stat, min_stat);
if (time_rec != GSS_C_INDEFINITE)
fatal("credentials valid for %d seconds (oops)", time_rec);
maj_stat = gss_compare_name(&min_stat, imported_service, cred_service, &names_match);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_compare_name", maj_stat, min_stat);
if (!names_match)
fatal("credentials granted for wrong service (oops)");
if (!(cred_usage & GSS_C_ACCEPT))
fatal("credentials lack usage GSS_C_ACCEPT (oops)");
}
}
void client_acquire_creds(const char *service, const char *hostname) {
import_service(service, hostname);
}
static char *princ_to_username(char *princ) {
char *ret = xstrdup(princ);
char *c = strchr(ret, '@');
if (c)
*c = '\0';
return ret;
}
int process_server_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) {
OM_uint32 maj_stat, min_stat;
OM_uint32 time_rec;
gss_OID name_type;
gss_buffer_desc peer_princ;
if (complete)
fatal("unexpected %zd-byte token from peer", incoming_tok->length);
maj_stat = gss_accept_sec_context(&min_stat, &context_handle, my_creds,
incoming_tok, GSS_C_NO_CHANNEL_BINDINGS, &peer_name, NULL,
outgoing_tok, &ret_flags, &time_rec, NULL);
if (maj_stat == GSS_S_COMPLETE) {
check_services(ret_flags);
complete = 1;
maj_stat = gss_display_name(&min_stat, peer_name, &peer_princ, &name_type);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_display_name", maj_stat, min_stat);
peer_principal = xstrdup((char *)peer_princ.value);
peer_username = princ_to_username((char *)peer_princ.value);
notice("client authenticated as %s", peer_principal);
debug("context expires in %d seconds", time_rec);
maj_stat = gss_release_buffer(&min_stat, &peer_princ);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_buffer", maj_stat, min_stat);
} else if (maj_stat != GSS_S_CONTINUE_NEEDED) {
gss_fatal("gss_accept_sec_context", maj_stat, min_stat);
}
return complete;
}
int process_client_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) {
OM_uint32 maj_stat, min_stat;
OM_uint32 time_rec;
gss_OID_desc krb5 = *gss_mech_krb5;
if (complete)
fatal("unexpected token from peer");
maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context_handle,
imported_service, &krb5, GSS_C_MUTUAL_FLAG |
GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
incoming_tok, NULL, outgoing_tok, &ret_flags,
&time_rec);
if (maj_stat == GSS_S_COMPLETE) {
notice("server authenticated as %s", service_name);
notice("context expires in %d seconds", time_rec);
check_services(ret_flags);
complete = 1;
} else if (maj_stat != GSS_S_CONTINUE_NEEDED) {
gss_fatal("gss_init_sec_context", maj_stat, min_stat);
}
return complete;
}
int initial_client_token(gss_buffer_t outgoing_tok) {
return process_client_token(GSS_C_NO_BUFFER, outgoing_tok);
}
char *client_principal(void) {
if (!complete)
fatal("authentication checked before finishing");
return peer_principal;
}
char *client_username(void) {
if (!complete)
fatal("authentication checked before finishing");
return peer_username;
}
void gss_encipher(struct strbuf *plain, struct strbuf *cipher) {
OM_uint32 maj_stat, min_stat;
gss_buffer_desc plain_tok, cipher_tok;
int conf_state;
plain_tok.value = plain->buf;
plain_tok.length = plain->len;
maj_stat = gss_wrap(&min_stat, context_handle, 1, GSS_C_QOP_DEFAULT,
&plain_tok, &conf_state, &cipher_tok);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_wrap", maj_stat, min_stat);
if (!conf_state)
fatal("gss_encipher: confidentiality service required");
strbuf_add(cipher, cipher_tok.value, cipher_tok.length);
maj_stat = gss_release_buffer(&min_stat, &cipher_tok);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_buffer", maj_stat, min_stat);
}
void gss_decipher(struct strbuf *cipher, struct strbuf *plain) {
OM_uint32 maj_stat, min_stat;
gss_buffer_desc plain_tok, cipher_tok;
int conf_state;
gss_qop_t qop_state;
cipher_tok.value = cipher->buf;
cipher_tok.length = cipher->len;
maj_stat = gss_unwrap(&min_stat, context_handle, &cipher_tok,
&plain_tok, &conf_state, &qop_state);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_unwrap", maj_stat, min_stat);
if (!conf_state)
fatal("gss_encipher: confidentiality service required");
strbuf_add(plain, plain_tok.value, plain_tok.length);
maj_stat = gss_release_buffer(&min_stat, &plain_tok);
if (maj_stat != GSS_S_COMPLETE)
gss_fatal("gss_release_buffer", maj_stat, min_stat);
}

View File

@ -1,15 +0,0 @@
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_krb5.h>
void server_acquire_creds(const char *service);
void client_acquire_creds(const char *service, const char *hostname);
void gss_fatal(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat);
int process_server_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok);
int process_client_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok);
int initial_client_token(gss_buffer_t outgoing_tok);
char *client_principal(void);
char *client_username(void);
void free_gss(void);
void gss_encipher(struct strbuf *plain, struct strbuf *cipher);
void gss_decipher(struct strbuf *cipher, struct strbuf *plain);

View File

@ -1,171 +0,0 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/acl.h>
#include <dirent.h>
#include <pwd.h>
#include <fcntl.h>
#include "homedir.h"
#include "util.h"
#include "config.h"
static int set_acl(char *dir, char *acl_text, acl_type_t type) {
acl_t acl = acl_from_text(acl_text);
if (acl == (acl_t)NULL) {
errorpe("acl_from_text: %s", acl_text);
return -1;
}
if (acl_set_file(dir, type, acl) != 0) {
errorpe("acl_set_file: %s %s 0x%X %p", acl_text, dir, (int)type, (void*)acl);
acl_free(acl);
return -1;
}
acl_free(acl);
return 0;
}
int ceo_create_home(char *homedir, char *skel, uid_t uid, gid_t gid, char *access_acl, char *default_acl, char *email) {
int mask;
DIR *skeldir;
struct dirent *skelent;
mask = umask(0);
if (mkdir(homedir, 0755)) {
errorpe("failed to create %s", homedir);
return -1;
}
if (access_acl && set_acl(homedir, access_acl, ACL_TYPE_ACCESS) != 0)
return -1;
if (default_acl && set_acl(homedir, default_acl, ACL_TYPE_DEFAULT) != 0)
return -1;
skeldir = opendir(skel);
if (!skeldir) {
errorpe("failed to open %s", skel);
return -1;
}
while ((skelent = readdir(skeldir))) {
struct stat sb;
char src[PATH_MAX], dest[PATH_MAX];
if (!strcmp(skelent->d_name, ".") || !strcmp(skelent->d_name, ".."))
continue;
snprintf(src, sizeof(src), "%s/%s", skel, skelent->d_name);
snprintf(dest, sizeof(dest), "%s/%s", homedir, skelent->d_name);
lstat(src, &sb);
if (sb.st_uid || sb.st_gid) {
warn("not creating %s due to ownership", dest);
continue;
}
if (S_ISREG(sb.st_mode)) {
int bytes;
char buf[4096];
int srcfd = open(src, O_RDONLY);
if (srcfd == -1) {
warnpe("open: %s", src);
continue;
}
int destfd = open(dest, O_WRONLY|O_CREAT|O_EXCL, sb.st_mode & 0777);
if (destfd == -1) {
warnpe("open: %s", dest);
close(srcfd);
continue;
}
for (;;) {
bytes = read(srcfd, buf, sizeof(buf));
if (!bytes)
break;
if (bytes < 0) {
warnpe("read");
break;
}
if (full_write(destfd, buf, bytes)) {
warnpe("write: %s", src);
break;
}
}
if (fchown(destfd, uid, gid))
errorpe("chown: %s", dest);
close(srcfd);
close(destfd);
} else if (S_ISDIR(sb.st_mode)) {
if (mkdir(dest, sb.st_mode & 0777)) {
warnpe("mkdir: %s", dest);
continue;
}
if (chown(dest, uid, gid))
errorpe("chown: %s", dest);
} else if (S_ISLNK(sb.st_mode)) {
char lnkdest[PATH_MAX];
int bytes;
bytes = readlink(src, lnkdest, sizeof(lnkdest));
lnkdest[bytes] = '\0';
if (bytes == -1) {
warnpe("readlink: %s", src);
continue;
}
if (symlink(lnkdest, dest)) {
warnpe("symlink: %s", dest);
continue;
}
if (lchown(dest, uid, gid))
errorpe("lchown: %s", dest);
} else {
warn("not creating %s", dest);
}
}
closedir(skeldir);
if (email && *email) {
char dest[PATH_MAX];
snprintf(dest, sizeof(dest), "%s/%s", homedir, ".forward");
int destfd = open(dest, O_WRONLY|O_CREAT|O_EXCL, 0644);
if (full_write(destfd, email, strlen(email)))
warnpe("write: %s", dest);
if (fchown(destfd, uid, gid))
errorpe("chown: %s", dest);
close(destfd);
}
if (chown(homedir, uid, gid)) {
errorpe("failed to chown %s", homedir);
return -1;
}
umask(mask);
return 0;
}
int ceo_set_quota(char *proto, int id) {
char user[128];
char *sqargs[] = { "setquota", "-a", "-p", proto, NULL, NULL };
snprintf(user, sizeof(user), "%d", id);
sqargs[4] = user;
if (spawnv("/usr/sbin/setquota", sqargs)) {
error("failed to set quota for %s", user);
return -1;
}
return 0;
}

View File

@ -1,6 +0,0 @@
#include <sys/acl.h>
#define CLUB_ACL "u::rwx,g::r-x,o::r-x,g:%d:rwx,m::rwx"
int ceo_create_home(char *homedir, char *skel, uid_t uid, gid_t gid, char *access_acl, char *default_acl, char *email);
int ceo_set_quota(char *proto, int id);

View File

@ -1,97 +0,0 @@
#include <kadm5/admin.h>
#include "kadm.h"
#include "krb5.h"
#include "util.h"
#include "config.h"
extern char *prog;
static void *handle;
void ceo_kadm_init() {
krb5_error_code retval;
kadm5_config_params params;
memset((void *) &params, 0, sizeof(params));
debug("kadmin: initializing using keytab for %s", krb5_admin_principal);
retval = kadm5_init_with_skey(
#ifdef KADM5_API_VERSION_3
context,
#endif
krb5_admin_principal, NULL,
KADM5_ADMIN_SERVICE, &params, KADM5_STRUCT_VERSION,
KADM5_API_VERSION_2, NULL, &handle);
if (retval || !handle) {
com_err(prog, retval, "while initializing kadm5");
exit(1);
}
}
void ceo_kadm_cleanup() {
debug("kadmin: cleaning up");
kadm5_destroy(handle);
}
int ceo_add_princ(char *user, char *password) {
krb5_error_code retval;
debug("kadmin: adding principal %s", user);
// Added March 2012: Change behavior of ceod to add the kerberos principal.
kadm5_policy_ent_rec defpol;
kadm5_principal_ent_rec princ;
memset((void*) &princ, 0, sizeof(princ));
if ((retval = kadm5_get_policy(handle, "default", &defpol))) {
com_err(prog, retval, "while retrieving default policy");
return retval;
}
kadm5_free_policy_ent(handle, &defpol);
princ.policy = "default";
if ((retval = krb5_parse_name(context, user, &princ.principal))) {
com_err(prog, retval, "while parsing user name");
return retval;
}
long flags = KADM5_POLICY | KADM5_PRINCIPAL;
if ((retval = kadm5_create_principal(handle, &princ, flags, password))) {
if(retval == KADM5_DUP) {
if ((retval = kadm5_chpass_principal(handle, princ.principal, password))) {
com_err(prog, retval, "while setting principal password");
return retval;
}
} else {
com_err(prog, retval, "while creating principal");
return retval;
}
}
krb5_free_principal(context, princ.principal);
return 0;
}
int ceo_del_princ(char *user) {
krb5_error_code retval;
krb5_principal princ;
debug("kadmin: deleting principal %s", user);
if ((retval = krb5_parse_name(context, user, &princ))) {
com_err(prog, retval, "while parsing principal name");
return retval;
}
retval = kadm5_delete_principal(handle, princ);
if (retval && retval != KADM5_UNK_PRINC) {
com_err(prog, retval, "while deleting principal");
return retval;
}
krb5_free_principal(context, princ);
return 0;
}

View File

@ -1,5 +0,0 @@
void ceo_kadm_init();
void ceo_kadm_cleanup();
int ceo_add_princ(char *, char *);
int ceo_del_princ(char *);

View File

@ -1,132 +0,0 @@
#include <stdio.h>
#include <krb5.h>
#include <syslog.h>
#include "krb5.h"
#include "util.h"
#include "config.h"
extern char *prog;
krb5_context context;
static void com_err_hk(const char *whoami, long code, const char *fmt, va_list args) {
char message[4096];
char *msgp = message;
msgp += snprintf(msgp, sizeof(message) - 2 - (msgp - message), "%s ", error_message(code));
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
msgp += vsnprintf(msgp, sizeof(message) - 2 - (msgp - message), fmt, args);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
*msgp++ = '\n';
*msgp++ = '\0';
logmsg(LOG_ERR, "fatal: %s", message);
exit(1);
}
void ceo_krb5_init() {
krb5_error_code retval;
set_com_err_hook(com_err_hk);
debug("krb5: initializing context");
retval = krb5_init_context(&context);
if (retval)
com_err(prog, retval, "while initializing krb5");
retval = krb5_set_default_realm(context, krb5_realm);
if (retval)
com_err(prog, retval, "while setting default realm");
}
void ceo_krb5_auth(char *principal) {
krb5_error_code retval;
krb5_creds creds;
krb5_principal princ;
krb5_ccache cache;
krb5_get_init_creds_opt options;
krb5_get_init_creds_opt_init(&options);
memset(&creds, 0, sizeof(creds));
debug("krb5: getting TGT using keytab for %s", principal);
if ((retval = krb5_parse_name(context, principal, &princ)))
com_err(prog, retval, "while resolving user %s", principal);
if ((retval = krb5_cc_default(context, &cache)))
com_err(prog, retval, "while resolving credentials cache");
if ((retval = krb5_get_init_creds_keytab(context, &creds, princ, NULL, 0, NULL, &options)))
com_err(prog, retval, "while getting initial credentials");
if ((retval = krb5_cc_initialize(context, cache, princ)))
com_err(prog, retval, "while initializing credentials cache");
if ((retval = krb5_cc_store_cred(context, cache, &creds)))
com_err(prog, retval, "while storing credentials");
krb5_free_cred_contents(context, &creds);
krb5_free_principal(context, princ);
krb5_cc_close(context, cache);
}
void ceo_krb5_deauth() {
krb5_error_code retval;
krb5_ccache cache;
debug("krb5: destroying credentials");
if ((retval = krb5_cc_default(context, &cache)))
com_err(prog, retval, "while resolving credentials cache");
if ((retval = krb5_cc_destroy(context, cache)))
com_err(prog, retval, "while destroying credentials cache");
}
void ceo_krb5_cleanup() {
debug("krb5: cleaning up");
krb5_free_context(context);
}
int ceo_read_password(char *password, unsigned int size, int use_stdin) {
int tries = 0;
unsigned int len;
do {
if (use_stdin) {
if (fgets(password, size, stdin) == NULL)
fatal("eof while reading password");
size = strlen(password);
if (password[size - 1] == '\n')
password[size - 1] = '\0';
} else {
len = size;
int retval = krb5_read_password(context, "New password", "Confirm password", password, &len);
if (retval == KRB5_LIBOS_PWDINTR) {
error("interrupted");
return -1;
} else if (retval == KRB5_LIBOS_BADPWDMATCH) {
fputs("Passwords do not match.\n", stderr);
} else if (!password || !*password) {
fputs("Please enter a password.\n", stderr);
}
}
} while (++tries < 3 && !*password);
if (!*password) {
error("maximum tries exceeded reading password");
return -1;
}
return 0;
}

View File

@ -1,14 +0,0 @@
#include <krb5.h>
#include <com_err.h>
extern char *prog;
extern krb5_context context;
void ceo_krb5_init();
void ceo_krb5_cleanup();
void ceo_krb5_auth(char *);
void ceo_krb5_deauth();
int ceo_read_password(char *, unsigned int, int);

View File

@ -1,383 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <sasl/sasl.h>
#include <krb5.h>
#define LDAP_DEPRECATED 1
#include <ldap.h>
#include "ldap.h"
#include "krb5.h"
#include "config.h"
#include "util.h"
extern char *prog;
LDAP *ld;
static void ldap_fatal(char *msg) {
int errnum = 0;
char *errstr = NULL;
char *detail = NULL;
if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &errnum) != LDAP_SUCCESS)
warn("ldap_get_option(LDAP_OPT_ERROR_NUMBER) failed");
if (ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &detail) != LDAP_SUCCESS)
warn("ldap_get_option(LDAP_OPT_ERROR_STRING) failed");
errstr = ldap_err2string(errnum);
if (detail)
fatal("%s: %s (%d): %s", msg, errstr, errnum, detail);
else if (errnum)
fatal("%s: %s (%d)", msg, errstr, errnum);
else
fatal("%s", msg);
}
static void ldap_err(char *msg) {
int errnum = 0;
char *errstr = NULL;
char *detail = NULL;
if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &errnum) != LDAP_SUCCESS)
warn("ldap_get_option(LDAP_OPT_ERROR_NUMBER) failed");
if (ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &detail) != LDAP_SUCCESS)
warn("ldap_get_option(LDAP_OPT_ERROR_STRING) failed");
errstr = ldap_err2string(errnum);
if (detail)
error("%s: %s (%d): %s", msg, errstr, errnum, detail);
else if (errnum)
error("%s: %s (%d)", msg, errstr, errnum);
else
error("%s", msg);
}
int ceo_add_group(char *cn, char *basedn, int no) {
if (!cn || !basedn)
fatal("addgroup: Invalid argument");
LDAPMod *mods[8];
int i = -1;
int ret = 0;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "objectClass";
char *objectClasses[] = { "top", "group", "posixGroup", NULL };
mods[i]->mod_values = objectClasses;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "cn";
char *uids[] = { cn, NULL };
mods[i]->mod_values = uids;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "gidNumber";
char idno[16];
snprintf(idno, sizeof(idno), "%d", no);
char *gidNumbers[] = { idno, NULL };
mods[i]->mod_values = gidNumbers;
mods[++i] = NULL;
char dn[1024];
snprintf(dn, sizeof(dn), "cn=%s,%s", cn, basedn);
if (ldap_add_s(ld, dn, mods) != LDAP_SUCCESS) {
ldap_err("addgroup");
ret = -1;
}
for (i = 0; mods[i]; i++)
free(mods[i]);
return ret;
}
int ceo_add_group_sudo(char *group, char *basedn) {
if (!group || !basedn)
fatal("addgroup: Invalid argument");
LDAPMod *mods[8];
int i = -1;
int ret = 0;
char cn[17];
snprintf(cn, sizeof(cn), "%%%s", group);
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "objectClass";
char *objectClasses[] = { "top", "sudoRole", NULL };
mods[i]->mod_values = objectClasses;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "cn";
char *uids[] = { cn, NULL };
mods[i]->mod_values = uids;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "sudoUser";
char *sudouser[] = { cn, NULL };
mods[i]->mod_values = sudouser;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "sudoHost";
char *sudohost[] = { "ALL", NULL };
mods[i]->mod_values = sudohost;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "sudoCommand";
char *sudocommand[] = { "ALL", NULL };
mods[i]->mod_values = sudocommand;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "sudoOption";
char *sudooption[] = { "!authenticate", NULL };
mods[i]->mod_values = sudooption;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "sudoRunAsUser";
char *sudorunas[] = { group, NULL };
mods[i]->mod_values = sudorunas;
char dn[1024];
snprintf(dn, sizeof(dn), "cn=%%%s,%s", group, basedn);
mods[++i] = NULL;
if (ldap_add_s(ld, dn, mods) != LDAP_SUCCESS) {
ldap_err("addgroup");
ret = -1;
}
for (i = 0; mods[i]; i++)
free(mods[i]);
return ret;
}
int ceo_add_user(char *uid, char *basedn, char *objclass, char *cn, char *home, char *shell, int no, ...) {
va_list args;
if (!uid || !basedn || !cn || !home || !shell)
fatal("adduser: Invalid argument");
LDAPMod *mods[16];
char *vals[16][2];
int i = -1;
int ret = 0;
int classes = 4;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "objectClass";
char *objectClasses[] = { "top", "account", "posixAccount", "shadowAccount", NULL, NULL, NULL, NULL };
if (objclass != NULL)
objectClasses[classes++] = objclass;
mods[i]->mod_values = objectClasses;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "uid";
char *uids[] = { uid, NULL };
mods[i]->mod_values = uids;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "cn";
char *cns[] = { cn, NULL };
mods[i]->mod_values = cns;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "loginShell";
char *shells[] = { shell, NULL };
mods[i]->mod_values = shells;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "uidNumber";
char idno[16];
snprintf(idno, sizeof(idno), "%d", no);
char *uidNumbers[] = { idno, NULL };
mods[i]->mod_values = uidNumbers;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "gidNumber";
mods[i]->mod_values = uidNumbers;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "homeDirectory";
char *homeDirectory[] = { home, NULL };
mods[i]->mod_values = homeDirectory;
va_start(args, no);
char *attr;
while ((attr = va_arg(args, char *))) {
char *val = va_arg(args, char *);
if (!val || !*val)
continue;
if (i == sizeof(mods) / sizeof(*mods) - 2) {
error("too many attributes");
return -1;
}
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = attr;
vals[i][0] = val;
vals[i][1] = NULL;
mods[i]->mod_values = vals[i];
}
mods[++i] = NULL;
char dn[1024];
snprintf(dn, sizeof(dn), "uid=%s,%s", uid, basedn);
if (ldap_add_s(ld, dn, mods) != LDAP_SUCCESS) {
ldap_err("adduser");
ret = -1;
}
for (i = 0; mods[i]; i++)
free(mods[i]);
return ret;
}
int ceo_new_uid(int min, int max) {
char filter[64];
char *attrs[] = { LDAP_NO_ATTRS, NULL };
LDAPMessage *res;
int i;
for (i = min; i <= max; i++) {
// id taken due to passwd
if (getpwuid(i) != NULL)
continue;
// id taken due to group
if (getgrgid(i) != NULL)
continue;
snprintf(filter, sizeof(filter), "(|(uidNumber=%d)(gidNumber=%d))", i, i);
if (ldap_search_s(ld, ldap_users_base, LDAP_SCOPE_SUBTREE, filter, attrs, 1, &res) != LDAP_SUCCESS) {
ldap_err("firstuid");
return -1;
}
int count = ldap_count_entries(ld, res);
ldap_msgfree(res);
// id taken due to LDAP
if (count)
continue;
return i;
}
return -1;
}
int ceo_user_exists(char *uid) {
char *attrs[] = { LDAP_NO_ATTRS, NULL };
LDAPMessage *msg = NULL;
char filter[128];
int count;
if (!uid)
fatal("null uid");
snprintf(filter, sizeof(filter), "uid=%s", uid);
if (ldap_search_s(ld, ldap_users_base, LDAP_SCOPE_SUBTREE, filter, attrs, 0, &msg) != LDAP_SUCCESS) {
ldap_err("user_exists");
return -1;
}
count = ldap_count_entries(ld, msg);
ldap_msgfree(msg);
return count > 0;
}
int ceo_group_exists(char *cn) {
char *attrs[] = { LDAP_NO_ATTRS, NULL };
LDAPMessage *msg = NULL;
char filter[128];
int count;
if (!cn)
fatal("null cd");
snprintf(filter, sizeof(filter), "cn=%s", cn);
if (ldap_search_s(ld, ldap_groups_base, LDAP_SCOPE_SUBTREE, filter, attrs, 0, &msg) != LDAP_SUCCESS) {
ldap_err("group_exists");
return -1;
}
count = ldap_count_entries(ld, msg);
ldap_msgfree(msg);
return count > 0;
}
static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *in) {
sasl_interact_t *interact = in;
while (interact->id != SASL_CB_LIST_END) {
switch (interact->id) {
// GSSAPI doesn't require any callbacks
default:
interact->result = "";
interact->len = 0;
}
interact++;
}
return LDAP_SUCCESS;
}
void ceo_ldap_init() {
int proto = LDAP_DEFAULT_PROTOCOL;
if (!ldap_admin_principal)
fatal("not configured");
if (ldap_initialize(&ld, ldap_server_url) != LDAP_SUCCESS)
ldap_fatal("ldap_initialize");
if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) != LDAP_OPT_SUCCESS)
ldap_fatal("ldap_set_option");
if (ldap_sasl_interactive_bind_s(ld, NULL, ldap_sasl_mech, NULL, NULL,
LDAP_SASL_QUIET, &ldap_sasl_interact, NULL) != LDAP_SUCCESS)
ldap_fatal("Bind failed");
}
void ceo_ldap_cleanup() {
ldap_unbind(ld);
}

View File

@ -1,12 +0,0 @@
#define LDAP_DEFAULT_PROTOCOL LDAP_VERSION3
int ceo_add_user(char *, char *, char *, char *, char *, char *, int, ...);
int ceo_add_group(char *, char *, int);
int ceo_add_group_sudo(char *, char *);
int ceo_new_uid(int, int);
void ceo_ldap_init();
void ceo_ldap_cleanup();
int ceo_user_exists(char *);
int ceo_group_exists(char *);

View File

@ -1,95 +0,0 @@
#include <stdio.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include "util.h"
#include "net.h"
#include "gss.h"
#include "strbuf.h"
struct strbuf fqdn = STRBUF_INIT;
const size_t MAX_MSGLEN = 65536;
const size_t MSG_BUFINC = 4096;
void setup_fqdn(void) {
struct utsname uts;
struct hostent *lo;
if (uname(&uts))
fatalpe("uname");
lo = gethostbyname(uts.nodename);
if (!lo)
fatalpe("gethostbyname");
strbuf_addstr(&fqdn, lo->h_name);
}
void free_fqdn(void) {
strbuf_release(&fqdn);
}
int ceo_send_message(int sock, void *buf, size_t len, uint32_t msgtype) {
uint32_t msgheader[2];
msgheader[0] = htonl(len);
msgheader[1] = htonl(msgtype);
if (full_write(sock, msgheader, sizeof(msgheader)) < 0)
fatalpe("write");
if (full_write(sock, buf, len) < 0)
fatalpe("write");
return 0;
}
int ceo_receive_message(int sock, struct strbuf *msg, uint32_t *msgtype) {
uint32_t msglen, received = 0;
uint32_t msgheader[2];
ssize_t bytes;
strbuf_reset(msg);
while (received < sizeof(msgheader)) {
bytes = read(sock, msgheader, sizeof(msgheader) - received);
if (bytes < 0) {
if (errno == EAGAIN)
continue;
fatalpe("read");
}
if (!bytes && !received)
return -1;
if (!bytes)
fatalpe("short header received");
received += bytes;
}
msglen = ntohl(msgheader[0]);
*msgtype = ntohl(msgheader[1]);
received = 0;
if (!msglen)
fatal("length is zero in message header");
if (msglen > MAX_MSGLEN)
fatal("length is huge in message header");
strbuf_grow(msg, msglen);
strbuf_setlen(msg, msglen);
while (received < msglen) {
bytes = read(sock, msg->buf + received, msglen - received);
if (bytes < 0) {
if (errno == EAGAIN)
continue;
fatalpe("read");
}
if (!bytes)
fatal("short message received");
received += bytes;
}
return 0;
}

View File

@ -1,24 +0,0 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <gssapi/gssapi.h>
typedef struct sockaddr sa;
extern struct strbuf fqdn;
extern void setup_fqdn(void);
extern void free_fqdn(void);
enum {
MSG_AUTH = 0x8000000,
MSG_EXPLODE = 0x8000001,
};
#define EKERB -2
#define ELDAP -3
#define EHOME -4
#define EQUOTA -5
int ceo_receive_message(int sock, struct strbuf *msg, uint32_t *msgtype);
int ceo_send_message(int sock, void *msg, size_t len, uint32_t msgtype);

View File

@ -1,335 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <alloca.h>
#include <pwd.h>
#include <grp.h>
#include <sys/wait.h>
#include "util.h"
#include "net.h"
#include "ceo.pb-c.h"
#include "config.h"
#include "gss.h"
#include "krb5.h"
#include "ldap.h"
#include "homedir.h"
#include "kadm.h"
#include "daemon.h"
#include "strbuf.h"
char *prog;
static const int MAX_MESSAGES = 32;
static const int MAX_MESGSIZE = 512;
char *user_types[] = {
[CEO__ADD_USER__TYPE__MEMBER] = "member",
[CEO__ADD_USER__TYPE__CLUB] = "club",
[CEO__ADD_USER__TYPE__CLUB_REP] = "clubrep",
};
Ceo__AddUserResponse *response_create(void) {
Ceo__AddUserResponse *r = xmalloc(sizeof(Ceo__AddUserResponse));
ceo__add_user_response__init(r);
r->n_messages = 0;
r->messages = xmalloc(MAX_MESSAGES * sizeof(Ceo__StatusMessage *));
return r;
}
PRINTF_LIKE(2)
int32_t response_message(Ceo__AddUserResponse *r, int32_t status, char *fmt, ...) {
va_list args;
Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage));
char *message = xmalloc(MAX_MESGSIZE);
va_start(args, fmt);
vsnprintf(message, MAX_MESGSIZE, fmt, args);
va_end(args);
ceo__status_message__init(statusmsg);
statusmsg->status = status;
statusmsg->message = message;
if (r->n_messages >= MAX_MESSAGES)
fatal("too many messages");
r->messages[r->n_messages++] = statusmsg;
if (status)
error("%s", message);
else
notice("%s", message);
return status;
}
void response_delete(Ceo__AddUserResponse *r) {
int i;
for (i = 0; i < r->n_messages; i++) {
free(r->messages[i]->message);
free(r->messages[i]);
}
free(r->messages);
free(r);
}
static int check_adduser(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client) {
int office = check_group(client, "office");
int syscom = check_group(client, "syscom");
notice("adding uid=%s cn=%s by %s", in->username, in->realname, client);
if (!office && !syscom)
return response_message(out, EPERM, "%s not authorized to create users", client);
if (!in->username)
return response_message(out, EINVAL, "missing required argument: username");
if (!in->realname)
return response_message(out, EINVAL, "missing required argument: realname");
switch (in->type) {
case CEO__ADD_USER__TYPE__MEMBER:
case CEO__ADD_USER__TYPE__CLUB_REP:
if (!in->password)
return response_message(out, EINVAL, "missing required argument: password");
break;
case CEO__ADD_USER__TYPE__CLUB:
if (in->password)
return response_message(out, EINVAL, "club accounts cannot have passwords");
if (in->program)
return response_message(out, EINVAL, "club accounts cannot have programs");
break;
default:
return response_message(out, EINVAL, "invalid user type: %d", in->type);
}
if (getpwnam(in->username) != NULL)
return response_message(out, EEXIST, "user %s already exists", in->username);
if (getgrnam(in->username) != NULL)
return response_message(out, EEXIST, "group %s already exists", in->username);
if (ceo_user_exists(in->username))
return response_message(out, EEXIST, "user %s already exists in LDAP", in->username);
if (ceo_group_exists(in->username))
return response_message(out, EEXIST, "group %s already exists in LDAP", in->username);
return 0;
}
static void adduser_spam(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client, char *prog, int status) {
char *argv[] = {
notify_hook, prog, client,
in->username, in->realname, in->program ?: "",
status ? "failure" : "success", NULL
};
struct strbuf message = STRBUF_INIT;
for (int i = 0; i < out->n_messages; i++)
strbuf_addf(&message, "%s\n", out->messages[i]->message);
spawnv_msg(notify_hook, argv, &message);
strbuf_release(&message);
}
static int32_t addmember(Ceo__AddUser *in, Ceo__AddUserResponse *out) {
char homedir[1024];
char principal[1024];
char sasl[1024];
int user_stat, group_stat, krb_stat, home_stat, quota_stat;
int id;
if (snprintf(principal, sizeof(principal), "%s@%s",
in->username, krb5_realm) >= sizeof(principal))
fatal("principal overflow");
if (snprintf(homedir, sizeof(homedir), "%s/%s",
member_home, in->username) >= sizeof(homedir))
fatal("homedir overflow");
if ((id = ceo_new_uid(member_min_id, member_max_id)) <= 0)
fatal("no available uids in range [%ld, %ld]", member_min_id, member_max_id);
if ((krb_stat = ceo_del_princ(in->username)))
return response_message(out, EEXIST, "unable to overwrite orphaned kerberos principal %s", in->username);
if (snprintf(sasl, sizeof(sasl), "{SASL}%s",
principal) >= sizeof(sasl))
fatal("sasl overflow");
if ((user_stat = ceo_add_user(in->username, ldap_users_base, "member", in->realname, homedir,
member_shell, id, "program", in->program, "userPassword", sasl, NULL)))
return response_message(out, ELDAP, "unable to create ldap account %s", in->username);
response_message(out, 0, "successfully created ldap account");
/* errors that occur after this point are not fatal */
if ((krb_stat = ceo_add_princ(in->username, in->password)))
return response_message(out, EKERB, "unable to create kerberos principal %s", in->username);
response_message(out, 0, "successfully created principal");
if ((group_stat = ceo_add_group(in->username, ldap_groups_base, id)))
response_message(out, ELDAP, "unable to create ldap group %s", in->username);
else
response_message(out, 0, "successfully created ldap group");
if ((home_stat = ceo_create_home(homedir, member_home_skel, id, id, NULL, NULL, in->email)))
response_message(out, EHOME, "unable to create home directory for %s", in->username);
else
response_message(out, 0, "successfully created home directory");
if ((quota_stat = ceo_set_quota("ctdalek", id)))
response_message(out, EQUOTA, "unable to set quota for %s", in->username);
else
response_message(out, 0, "successfully set quota");
return krb_stat || user_stat || group_stat || home_stat || quota_stat;
}
static int32_t addclub(Ceo__AddUser *in, Ceo__AddUserResponse *out) {
char homedir[1024];
char acl[64];
int krb_stat, user_stat, group_stat, sudo_stat, home_stat, quota_stat;
int id;
if (snprintf(homedir, sizeof(homedir), "%s/%s", club_home, in->username) >= sizeof(homedir))
fatal("homedir overflow");
if ((id = ceo_new_uid(club_min_id, club_max_id)) <= 0)
fatal("no available uids in range [%ld, %ld]", club_min_id, club_max_id);
if (snprintf(acl, sizeof(acl), CLUB_ACL, id) >= sizeof(acl))
fatal("acl overflow");
if ((krb_stat = ceo_del_princ(in->username)))
return response_message(out, EKERB, "unable to clear principal %s", in->username);
if ((user_stat = ceo_add_user(in->username, ldap_users_base, "club", in->realname, homedir,
club_shell, id, NULL)))
return response_message(out, ELDAP, "unable to create ldap account %s", in->username);
response_message(out, 0, "successfully created ldap account");
/* errors that occur after this point are not fatal */
if ((group_stat = ceo_add_group(in->username, ldap_groups_base, id)))
response_message(out, ELDAP, "unable to create ldap group %s", in->username);
else
response_message(out, 0, "successfully created ldap group");
if ((sudo_stat = ceo_add_group_sudo(in->username, ldap_sudo_base)))
response_message(out, ELDAP, "unable to create ldap sudoers %s", in->username);
else
response_message(out, 0, "successfully created ldap sudoers");
if ((home_stat = ceo_create_home(homedir, club_home_skel, id, id, acl, acl, NULL)))
response_message(out, EHOME, "unable to create home directory for %s", in->username);
else
response_message(out, 0, "successfully created home directory");
if ((quota_stat = ceo_set_quota("csc", id)))
response_message(out, EQUOTA, "unable to set quota for %s", in->username);
else
response_message(out, 0, "successfully set quota");
return user_stat || group_stat || sudo_stat || home_stat || quota_stat;
}
static int32_t adduser(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client) {
int32_t chk_stat, status;
char *prog;
chk_stat = check_adduser(in, out, client);
if (chk_stat)
return chk_stat;
if (in->type == CEO__ADD_USER__TYPE__MEMBER) {
status = addmember(in, out);
prog = "addmember";
} else if (in->type == CEO__ADD_USER__TYPE__CLUB_REP) {
status = addmember(in, out);
prog = "addclubrep";
} else if (in->type == CEO__ADD_USER__TYPE__CLUB) {
status = addclub(in, out);
prog = "addclub";
} else {
fatal("unknown user type %d", in->type);
}
if (status)
response_message(out, 0, "there were failures, please contact systems committee");
adduser_spam(in, out, client, prog, status);
return status;
}
void cmd_adduser(void) {
Ceo__AddUser *in_proto;
Ceo__AddUserResponse *out_proto = response_create();
struct strbuf in = STRBUF_INIT;
struct strbuf out = STRBUF_INIT;
if (strbuf_read(&in, STDIN_FILENO, 0) < 0)
fatalpe("read");
in_proto = ceo__add_user__unpack(NULL,
in.len, (uint8_t *)in.buf);
if (!in_proto)
fatal("malformed add user message");
char *client = getenv("CEO_USER");
if (!client)
fatal("environment variable CEO_USER is not set");
adduser(in_proto, out_proto, client);
strbuf_grow(&out, ceo__add_user_response__get_packed_size(out_proto));
strbuf_setlen(&out, ceo__add_user_response__pack(out_proto, (uint8_t *)out.buf));
if (full_write(STDOUT_FILENO, out.buf, out.len))
fatalpe("write: stdout");
ceo__add_user__free_unpacked(in_proto, NULL);
response_delete(out_proto);
strbuf_release(&in);
strbuf_release(&out);
}
int main(int argc, char *argv[]) {
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_AUTHPRIV, 0);
configure();
if (setenv("KRB5CCNAME", "MEMORY:adduser", 1))
fatalpe("setenv");
ceo_krb5_init();
ceo_krb5_auth(ldap_admin_principal);
ceo_ldap_init();
ceo_kadm_init();
cmd_adduser();
ceo_kadm_cleanup();
ceo_ldap_cleanup();
ceo_krb5_deauth();
ceo_krb5_cleanup();
free_config();
free(prog);
return 0;
}

View File

@ -1,217 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <alloca.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <ctype.h>
#include "util.h"
#include "net.h"
#include "ceo.pb-c.h"
#include "config.h"
#include "strbuf.h"
char *prog;
static const int MAX_MESSAGES = 32;
static const int MAX_MESGSIZE = 512;
Ceo__UpdateMailResponse *response_create(void) {
Ceo__UpdateMailResponse *r = xmalloc(sizeof(Ceo__UpdateMailResponse));
ceo__update_mail_response__init(r);
r->n_messages = 0;
r->messages = xmalloc(MAX_MESSAGES * sizeof(Ceo__StatusMessage *));
return r;
}
PRINTF_LIKE(2)
int32_t response_message(Ceo__UpdateMailResponse *r, int32_t status, char *fmt, ...) {
va_list args;
Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage));
char *message = xmalloc(MAX_MESGSIZE);
va_start(args, fmt);
vsnprintf(message, MAX_MESGSIZE, fmt, args);
va_end(args);
ceo__status_message__init(statusmsg);
statusmsg->status = status;
statusmsg->message = message;
if (r->n_messages >= MAX_MESSAGES)
fatal("too many messages");
r->messages[r->n_messages++] = statusmsg;
if (status)
error("%s", message);
else
notice("%s", message);
return status;
}
void response_delete(Ceo__UpdateMailResponse *r) {
int i;
for (i = 0; i < r->n_messages; i++) {
free(r->messages[i]->message);
free(r->messages[i]);
}
free(r->messages);
free(r);
}
static int check_update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) {
int client_office = check_group(client, "office");
int client_syscom = check_group(client, "syscom");
notice("update mail uid=%s mail=%s by %s", in->username, in->forward, client);
if (!in->username)
return response_message(out, EINVAL, "missing required argument: username");
int recipient_syscom = check_group(in->username, "syscom");
if (!client_syscom && !client_office && strcmp(in->username, client))
return response_message(out, EPERM, "%s not authorized to update mail", client);
if (recipient_syscom && !client_syscom)
return response_message(out, EPERM, "denied, recipient is on systems committee");
/* don't allow office staff to set complicated forwards; in particular | is a security hole */
if (in->forward) {
for (char *p = in->forward; *p; p++) {
switch (*p) {
case '"':
case '\'':
case ',':
case '|':
case '$':
case '/':
case '#':
case ':':
return response_message(out, EINVAL, "invalid character in forward: %c", *p);
default:
break;
}
if (isspace(*p))
return response_message(out, EINVAL, "invalid character in forward: %c", *p);
}
}
return 0;
}
static int32_t update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) {
int32_t chk_stat;
mode_t mask;
chk_stat = check_update_mail(in, out, client);
if (chk_stat)
return chk_stat;
mask = umask(0);
if (in->forward) {
struct passwd *user = getpwnam(in->username);
if (!user)
return response_message(out, errno, "getpwnam: %s: %s", in->username, strerror(errno));
if (setregid(user->pw_gid, user->pw_gid))
return response_message(out, errno, "setregid: %s: %s", in->username, strerror(errno));
if (setreuid(user->pw_uid, user->pw_uid))
return response_message(out, errno, "setreuid: %s: %s", in->username, strerror(errno));
char path[1024];
if (snprintf(path, sizeof(path), "%s/.forward", user->pw_dir) >= sizeof(path))
return response_message(out, ENAMETOOLONG, "homedir is too long");
if (unlink(path) && errno != ENOENT)
return response_message(out, errno, "unlink: %s: %s", path, strerror(errno));
if (*in->forward) {
int fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
if (fd < 0)
return response_message(out, errno, "open: %s: %s", path, strerror(errno));
struct strbuf file_contents = STRBUF_INIT;
strbuf_addf(&file_contents, "%s\n", in->forward);
if (full_write(fd, file_contents.buf, file_contents.len))
response_message(out, errno, "write: %s: %s", path, strerror(errno));
strbuf_release(&file_contents);
if (close(fd))
return response_message(out, errno, "close: %s: %s", path, strerror(errno));
response_message(out, 0, "successfully updated forward for %s", in->username);
} else {
response_message(out, 0, "successfully cleared forward for %s", in->username);
}
}
umask(mask);
return response_message(out, 0, "finished updating mail for %s", in->username);
}
void cmd_update_mail(void) {
Ceo__UpdateMail *in_proto;
Ceo__UpdateMailResponse *out_proto = response_create();
struct strbuf in = STRBUF_INIT;
struct strbuf out = STRBUF_INIT;
if (strbuf_read(&in, STDIN_FILENO, 0) < 0)
fatalpe("read");
in_proto = ceo__update_mail__unpack(NULL,
in.len, (uint8_t *)in.buf);
if (!in_proto)
fatal("malformed update mail message");
char *client = getenv("CEO_USER");
if (!client)
fatal("environment variable CEO_USER is not set");
update_mail(in_proto, out_proto, client);
strbuf_grow(&out, ceo__update_mail_response__get_packed_size(out_proto));
strbuf_setlen(&out, ceo__update_mail_response__pack(out_proto, (uint8_t *)out.buf));
if (full_write(STDOUT_FILENO, out.buf, out.len))
fatalpe("write: stdout");
ceo__update_mail__free_unpacked(in_proto, NULL);
response_delete(out_proto);
strbuf_release(&in);
strbuf_release(&out);
}
int main(int argc, char *argv[]) {
prog = xstrdup(basename(argv[0]));
init_log(prog, LOG_PID, LOG_AUTHPRIV, 0);
configure();
cmd_update_mail();
free_config();
free(prog);
return 0;
}

View File

@ -1,47 +0,0 @@
#!/usr/bin/python
import os, syslog
from subprocess import Popen, PIPE, STDOUT
from ceo import conf
from ceo.ops import get_ceo_user, check_group
CONFIG_FILE = '/etc/csc/mailman.cf'
cfg = {}
def configure():
string_fields = ['members_list', 'list_domain']
# read configuration file
cfg_tmp = conf.read(CONFIG_FILE)
# verify configuration
conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
# update the current configuration with the loaded values
cfg.update(cfg_tmp)
def main():
configure()
remote_user = get_ceo_user()
user_to_add = raw_input()
if cfg['members_list'] == 'none':
print 'Disabled: %s' % user_to_add
return
if remote_user == user_to_add or check_group(remote_user, 'office') or check_group(remote_user, 'syscom'):
mailman = Popen(["/opt/mailman3/bin/mailman", "addmembers", "-", "%s@%s" % (cfg['members_list'], cfg['list_domain'])],
stdin=PIPE, stdout=PIPE, stderr=STDOUT)
out, err = mailman.communicate("%s@%s\n" % (user_to_add, cfg['list_domain']))
syslog.syslog(syslog.LOG_INFO, out)
print out
else:
message = "Access denied: user '%s' cannot subscribe users to %s" % (remote_user, cfg['members_list'])
syslog.syslog(syslog.LOG_NOTICE, message)
print message
if __name__ == '__main__':
syslog.openlog('op-mailman', syslog.LOG_PID, syslog.LOG_DAEMON)
main()

View File

@ -1,99 +0,0 @@
#!/usr/bin/python
import os, sys, string, random, syslog, grp, errno, re
from ceo import ceo_pb2, members, conf, ops
from ceo.ops import response_message, get_ceo_user, check_group
import MySQLdb
CONFIG_FILE = '/etc/csc/mysql.cf'
cfg = {}
def configure():
string_fields = ['mysql_admin_username', 'mysql_admin_password']
# read configuration file
cfg_tmp = conf.read(CONFIG_FILE)
# verify configuration
conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
# update the current configuration with the loaded values
cfg.update(cfg_tmp)
def random_password():
chars = string.letters + string.digits
return ''.join(random.choice(chars) for i in xrange(20))
def check_auth(remote_user, mysql_user, response):
if remote_user == mysql_user:
return response_message(response, 0, 'user %s creating database for self' % remote_user)
club = members.get(mysql_user)
if not club:
return response_message(response, errno.EPERM, 'user %s does not exist' % mysql_user)
if 'club' in club.get('objectClass', []):
if check_group(remote_user, mysql_user):
return response_message(response, 0, 'user %s is in club group %s' % (remote_user, mysql_user))
elif check_group(remote_user, 'syscom'):
return response_message(response, 0, 'user %s is on systems committee' % remote_user)
else:
return response_message(response, errno.EPERM, 'denied, user %s is not in club group %s' % (remote_user, mysql_user))
else:
if check_group(remote_user, 'syscom'):
return response_message(response, 0, 'user %s is on systems committee' % remote_user)
else:
return response_message(response, errno.EPERM, 'denied, you may not create databases for other members')
def mysql_createdb(remote_user, mysql_user, response):
if check_auth(remote_user, mysql_user, response):
return
response.password = random_password()
if not re.match('^[a-zA-Z0-9-]+$', mysql_user):
response_message(response, errno.EINVAL, 'invalid characters in username %s' % mysql_user)
return
if not re.match('^[a-zA-Z0-9-]+$', response.password):
response_message(response, errno.EINVAL, 'invalid characters in password %s' % response.password)
return
try:
connection = MySQLdb.Connect(user=cfg['mysql_admin_username'], passwd=cfg['mysql_admin_password'])
cursor = connection.cursor()
cursor.execute("GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@`localhost` IDENTIFIED BY '%s'"
% (mysql_user, mysql_user, response.password))
cursor.execute("CREATE DATABASE IF NOT EXISTS `%s`" % mysql_user)
cursor.close()
connection.close()
response_message(response, 0, 'successfully created database %s' % mysql_user)
except MySQLdb.MySQLError, e:
response_message(response, 1, 'exception occured creating database: %s' % e)
def mysql_op():
input = sys.stdin.read()
request = ceo_pb2.AddMySQLUser()
request.ParseFromString(input)
remote_user = get_ceo_user()
mysql_user = request.username
response = ceo_pb2.AddMySQLUserResponse()
response_message(response, 0, 'mysql create db=%s by %s' % (mysql_user, remote_user))
mysql_createdb(remote_user, mysql_user, response)
sys.stdout.write(response.SerializeToString())
def main():
configure()
members.configure()
members.connect_anonymous()
syslog.openlog('op-mysql', syslog.LOG_PID, syslog.LOG_DAEMON)
mysql_op()
if __name__ == '__main__':
main()

128
src/ops.c
View File

@ -1,128 +0,0 @@
#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include "strbuf.h"
#include "ops.h"
#include "net.h"
#include "util.h"
#include "config.h"
static struct op *ops;
static const char *default_op_dir = "/usr/lib/ceod";
static const char *op_dir;
static void add_op(char *host, char *name, char *user, uint32_t id) {
struct op *new = xmalloc(sizeof(struct op));
errno = 0;
new->next = ops;
new->name = xstrdup(name);
new->id = id;
new->path = NULL;
new->user = xstrdup(user);
struct hostent *hostent = gethostbyname(host);
if (!hostent)
badconf("cannot add op %s: %s: %s", name, host, hstrerror(h_errno));
new->hostname = strdup(hostent->h_name);
new->local = !strcmp(fqdn.buf, hostent->h_name);
new->addr = *(struct in_addr *)hostent->h_addr_list[0];
if (new->local) {
new->path = xmalloc(strlen(op_dir) + strlen("/op-") + strlen(name) + 1);
sprintf(new->path, "%s/op-%s", op_dir, name);
if (access(new->path, X_OK))
fatalpe("cannot add op: %s: %s", name, new->path);
struct passwd *pw = getpwnam(user);
if (!pw)
fatalpe("cannot add op %s: getpwnam: %s", name, user);
}
ops = new;
debug("added op %s (%s%s) [%s]", new->name, new->local ? "" : "on ",
new->local ? "local" : host, new->user);
}
struct op *get_local_op(uint32_t id) {
for (struct op *op = ops; op; op = op->next) {
if (op->local && op->id == id)
return op;
}
return NULL;
}
struct op *find_op(const char *name) {
for (struct op *op = ops; op; op = op->next) {
if (!strcmp(name, op->name))
return op;
}
return NULL;
}
void setup_ops(void) {
char op_config_dir[1024];
DIR *dp;
struct dirent *de;
struct strbuf line = STRBUF_INIT;
unsigned lineno = 0;
unsigned op_count = 0;
op_dir = getenv("CEO_LIB_DIR") ?: default_op_dir;
if (snprintf(op_config_dir, sizeof(op_config_dir), "%s/%s", config_dir, "ops") >= sizeof(op_config_dir))
fatal("ops dir path too long");
dp = opendir(op_config_dir);
if (!dp)
fatalpe("opendir: %s", op_config_dir);
while ((de = readdir(dp))) {
FILE *fp = fopenat(dp, de->d_name, O_RDONLY);
if (!fp)
warnpe("open: %s/%s", op_config_dir, de->d_name);
while (strbuf_getline(&line, fp, '\n') != EOF) {
lineno++;
strbuf_trim(&line);
if (!line.len || line.buf[0] == '#')
continue;
struct strbuf **words = strbuf_splitws(&line);
if (strbuf_list_len(words) != 4)
badconf("%s/%s: expected four words on line %d", op_config_dir, de->d_name, lineno);
errno = 0;
char *end;
int id = strtol(words[3]->buf, &end, 0);
if (errno || *end)
badconf("%s/%s: invalid id '%s' on line %d", op_config_dir, de->d_name, words[2]->buf, lineno);
add_op(words[0]->buf, words[1]->buf, words[2]->buf, id);
op_count++;
strbuf_list_free(words);
}
fclose(fp);
}
closedir(dp);
strbuf_release(&line);
}
void free_ops(void) {
while (ops) {
struct op *next = ops->next;
free(ops->name);
free(ops->hostname);
free(ops->path);
free(ops->user);
free(ops);
ops = next;
}
}

View File

@ -1,15 +0,0 @@
struct op {
char *name;
uint32_t id;
int local;
char *hostname;
char *path;
struct in_addr addr;
struct op *next;
char *user;
};
void setup_ops(void);
void free_ops(void);
struct op *find_op(const char *name);
struct op *get_local_op(uint32_t id);

View File

@ -1,215 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include "parser.h"
#include "util.h"
#include "config.h"
#define VAR_MAX 256
void config_var(const char *, const char *);
struct config_file {
FILE *p;
char *name;
int line;
struct config_file *parent;
int comment;
};
static void parse_config_file(char *, struct config_file *);
static void parse_error(struct config_file *file, char *msg) {
fatal("parse error on line %d of %s: %s", file->line, file->name, msg);
}
static int parse_char(struct config_file *file) {
int c = getc(file->p);
if (c == '\n')
(file->line)++;
return c;
}
static void unparse_char(struct config_file *file, int c) {
if (c == EOF)
return;
ungetc(c, file->p);
if (c == '\n')
(file->line)--;
}
static void parse_name(struct config_file *file, char *name, size_t maxlen) {
int len = 0;
int c;
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n') {
unparse_char(file, c);
break;
}
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') {
unparse_char(file, c);
break;
}
if (len == maxlen - 1)
parse_error(file, "max name length exceeded");
name[len++] = c;
}
if (len == 0)
parse_error(file, "expected name");
name[len++] = '\0';
}
static void parse_value(struct config_file *file, char *value, size_t maxlen) {
int len = 0;
int quote = 0;
int comment = 0;
int space = 0;
int c;
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n')
break;
if (c == '#')
comment = 1;
if ((isspace(c) && !quote) || comment) {
space = 1;
continue;
}
if (c == '"') {
quote = ! quote;
continue;
}
if (len == maxlen - space - 1)
parse_error(file, "max value length exceeded");
if (space && len) {
value[len++] = ' ';
}
space = 0;
value[len++] = c;
}
if (quote)
parse_error(file, "unbalanced quotes");
value[len++] = '\0';
}
static void parse_include(struct config_file *file) {
char path[PATH_MAX];
struct config_file *parent = file->parent;
parse_value(file, path, sizeof(path));
while (parent != NULL) {
if (!strcmp(file->name, parent->name))
return;
parent = parent->parent;
}
parse_config_file(path, file);
}
static void parse_config(struct config_file *file) {
int c;
int comment = 0;
char var[VAR_MAX];
char value[VAR_MAX];
for (;;) {
c = parse_char(file);
if (c == '\n') {
comment = 0;
continue;
}
if (c == EOF)
return;
if (isspace(c) | comment)
continue;
if (c == '#') {
comment = 1;
continue;
}
unparse_char(file, c);
parse_name(file, var, sizeof(var));
if (!strcmp(var, "include")) {
parse_include(file);
continue;
}
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n')
parse_error(file, "expected '=' before line end");
if (c == '=')
break;
if (!isspace(c))
parse_error(file, "expected '='");
}
parse_value(file, value, sizeof(value));
config_var(var, value);
}
}
static void parse_config_file(char *name, struct config_file *parent) {
struct config_file file;
file.p = fopen(name, "r");
file.name = name;
file.line = 1;
file.parent = parent;
if (!file.p) {
if (parent)
parse_error(parent, strerror(errno));
else
fatal("failed to open configuration file '%s': %s", name, strerror(errno));
}
parse_config(&file);
fclose(file.p);
}
long config_long(char *var, char *val) {
char *endptr;
long longval;
longval = strtol(val, &endptr, 0);
if (*val == '\0' || *endptr != '\0')
fatal("expected integer value for %s", var);
return longval;
}
void config_parse(char *filename) {
debug("loading configuration from %s", filename);
parse_config_file(filename, NULL);
}

View File

@ -1,2 +0,0 @@
void config_parse(char *);
long config_long(char *var, char *val);

View File

@ -1,458 +0,0 @@
/*
Copyright (c) 2005-2008 Junio C. Hamano et al.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License (version 2)
as published by the Free Software Foundation.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "strbuf.h"
#include "util.h"
#define die fatal
#define alloc_nr(x) (((x)+16)*3/2)
/*
* * Realloc the buffer pointed at by variable 'x' so that it can hold
* * at least 'nr' entries; the number of entries currently allocated
* * is 'alloc', using the standard growing factor alloc_nr() macro.
* *
* * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
* */
#define ALLOC_GROW(x, nr, alloc) \
do { \
if ((nr) > alloc) { \
if (alloc_nr(alloc) < (nr)) \
alloc = (nr); \
else \
alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
} while(0)
static inline char *gitstrchrnul(const char *s, int c)
{
while (*s && *s != c)
s++;
return (char *)s;
}
int prefixcmp(const char *str, const char *prefix)
{
for (; ; str++, prefix++)
if (!*prefix)
return 0;
else if (*str != *prefix)
return (unsigned char)*prefix - (unsigned char)*str;
}
/*
* Used as the default ->buf value, so that people can always assume
* buf is non NULL and ->buf is NUL terminated even for a freshly
* initialized strbuf.
*/
char strbuf_slopbuf[1];
void strbuf_init(struct strbuf *sb, size_t hint)
{
sb->alloc = sb->len = 0;
sb->buf = strbuf_slopbuf;
if (hint)
strbuf_grow(sb, hint);
}
void strbuf_release(struct strbuf *sb)
{
if (sb->alloc) {
free(sb->buf);
strbuf_init(sb, 0);
}
}
char *strbuf_detach(struct strbuf *sb, size_t *sz)
{
char *res = sb->alloc ? sb->buf : NULL;
if (sz)
*sz = sb->len;
strbuf_init(sb, 0);
return res;
}
void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
{
strbuf_release(sb);
sb->buf = buf;
sb->len = len;
sb->alloc = alloc;
strbuf_grow(sb, 0);
sb->buf[sb->len] = '\0';
}
void strbuf_grow(struct strbuf *sb, size_t extra)
{
if (sb->len + extra + 1 <= sb->len)
die("you want to use way too much memory");
if (!sb->alloc)
sb->buf = NULL;
ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
}
void strbuf_trim(struct strbuf *sb)
{
char *b = sb->buf;
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
sb->len--;
while (sb->len > 0 && isspace(*b)) {
b++;
sb->len--;
}
memmove(sb->buf, b, sb->len);
sb->buf[sb->len] = '\0';
}
void strbuf_rtrim(struct strbuf *sb)
{
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
sb->len--;
sb->buf[sb->len] = '\0';
}
void strbuf_ltrim(struct strbuf *sb)
{
char *b = sb->buf;
while (sb->len > 0 && isspace(*b)) {
b++;
sb->len--;
}
memmove(sb->buf, b, sb->len);
sb->buf[sb->len] = '\0';
}
void strbuf_tolower(struct strbuf *sb)
{
int i;
for (i = 0; i < sb->len; i++)
sb->buf[i] = tolower(sb->buf[i]);
}
struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
{
int alloc = 2, pos = 0;
char *n, *p;
struct strbuf **ret;
struct strbuf *t;
ret = xcalloc(alloc, sizeof(struct strbuf *));
p = n = sb->buf;
while (n < sb->buf + sb->len) {
int len;
n = memchr(n, delim, sb->len - (n - sb->buf));
if (pos + 1 >= alloc) {
alloc = alloc * 2;
ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
}
if (!n)
n = sb->buf + sb->len - 1;
len = n - p + 1;
t = xmalloc(sizeof(struct strbuf));
strbuf_init(t, len);
strbuf_add(t, p, len);
ret[pos] = t;
ret[++pos] = NULL;
p = ++n;
}
return ret;
}
struct strbuf **strbuf_splitws(const struct strbuf *sb)
{
int alloc = 2, pos = 0;
struct strbuf **ret;
int prev_ws = 1, ws;
int i, start = 0;
ret = xcalloc(alloc, sizeof(struct strbuf *));
for (i = 0; i <= sb->len; i++) {
ws = !sb->buf[i] || isspace(sb->buf[i]);
if (prev_ws == ws)
continue;
if (!ws) {
start = i;
} else {
if (pos + 1 >= alloc) {
alloc = alloc * 2;
ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
}
ret[pos] = xmalloc(sizeof(struct strbuf));
strbuf_init(ret[pos], i - start);
strbuf_add(ret[pos], &sb->buf[start], i - start);
pos++;
}
prev_ws = ws;
}
ret[pos] = NULL;
return ret;
}
void strbuf_list_free(struct strbuf **sbs)
{
struct strbuf **s = sbs;
while (*s) {
strbuf_release(*s);
free(*s++);
}
free(sbs);
}
size_t strbuf_list_len(struct strbuf **sbs)
{
size_t n = 0;
while (*sbs++)
n++;
return n;
}
int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
{
int cmp;
if (a->len < b->len) {
cmp = memcmp(a->buf, b->buf, a->len);
return cmp ? cmp : -1;
} else {
cmp = memcmp(a->buf, b->buf, b->len);
return cmp ? cmp : a->len != b->len;
}
}
void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
const void *data, size_t dlen)
{
if (pos + len < pos)
die("you want to use way too much memory");
if (pos > sb->len)
die("`pos' is too far after the end of the buffer");
if (pos + len > sb->len)
die("`pos + len' is too far after the end of the buffer");
if (dlen >= len)
strbuf_grow(sb, dlen - len);
memmove(sb->buf + pos + dlen,
sb->buf + pos + len,
sb->len - pos - len);
memcpy(sb->buf + pos, data, dlen);
strbuf_setlen(sb, sb->len + dlen - len);
}
void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
{
strbuf_splice(sb, pos, 0, data, len);
}
void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
{
strbuf_splice(sb, pos, len, NULL, 0);
}
void strbuf_add(struct strbuf *sb, const void *data, size_t len)
{
strbuf_grow(sb, len);
memcpy(sb->buf + sb->len, data, len);
strbuf_setlen(sb, sb->len + len);
}
void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
{
strbuf_grow(sb, len);
memcpy(sb->buf + sb->len, sb->buf + pos, len);
strbuf_setlen(sb, sb->len + len);
}
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
{
int len;
va_list ap;
if (!strbuf_avail(sb))
strbuf_grow(sb, 64);
va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_end(ap);
if (len < 0)
die("your vsnprintf is broken");
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_end(ap);
if (len > strbuf_avail(sb)) {
die("this should not happen, your snprintf is broken");
}
}
strbuf_setlen(sb, sb->len + len);
}
void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list args)
{
int len;
va_list ap;
va_copy(ap, args);
if (!strbuf_avail(sb))
strbuf_grow(sb, 64);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_copy(ap, args);
if (len < 0)
die("your vsnprintf is broken");
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
if (len > strbuf_avail(sb)) {
die("this should not happen, your snprintf is broken");
}
}
strbuf_setlen(sb, sb->len + len);
}
void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
void *context)
{
for (;;) {
const char *percent;
size_t consumed;
percent = gitstrchrnul(format, '%');
strbuf_add(sb, format, percent - format);
if (!*percent)
break;
format = percent + 1;
consumed = fn(sb, format, context);
if (consumed)
format += consumed;
else
strbuf_addch(sb, '%');
}
}
size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
void *context)
{
struct strbuf_expand_dict_entry *e = context;
size_t len;
for (; e->placeholder && (len = strlen(e->placeholder)); e++) {
if (!strncmp(placeholder, e->placeholder, len)) {
if (e->value)
strbuf_addstr(sb, e->value);
return len;
}
}
return 0;
}
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
strbuf_grow(sb, size);
res = fread(sb->buf + sb->len, 1, size, f);
if (res > 0) {
strbuf_setlen(sb, sb->len + res);
}
return res;
}
ssize_t strbuf_write(struct strbuf *sb, int fd) {
ssize_t total = 0;
while (total < sb->len) {
ssize_t cnt = write(fd, sb->buf + total, sb->len - total);
if (cnt <= 0)
return -1;
total += cnt;
}
return total;
}
ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
{
size_t oldlen = sb->len;
strbuf_grow(sb, hint ? hint : 8192);
for (;;) {
ssize_t cnt;
cnt = read(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
if (cnt < 0) {
strbuf_setlen(sb, oldlen);
return -1;
}
if (!cnt)
break;
sb->len += cnt;
strbuf_grow(sb, 8192);
}
sb->buf[sb->len] = '\0';
return sb->len - oldlen;
}
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
strbuf_grow(sb, 0);
if (feof(fp))
return EOF;
strbuf_reset(sb);
while ((ch = fgetc(fp)) != EOF) {
if (ch == term)
break;
strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
}
if (ch == EOF && sb->len == 0)
return EOF;
sb->buf[sb->len] = '\0';
return 0;
}
int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
{
int fd, len;
fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
len = strbuf_read(sb, fd, hint);
close(fd);
if (len < 0)
return -1;
return len;
}

View File

@ -1,158 +0,0 @@
/*
Copyright (c) 2005-2008 Junio C. Hamano et al.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License (version 2)
as published by the Free Software Foundation.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef STRBUF_H
#define STRBUF_H
/*
* Strbuf's can be use in many ways: as a byte array, or to store arbitrary
* long, overflow safe strings.
*
* Strbufs has some invariants that are very important to keep in mind:
*
* 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
* build complex strings/buffers whose final size isn't easily known.
*
* It is NOT legal to copy the ->buf pointer away.
* `strbuf_detach' is the operation that detachs a buffer from its shell
* while keeping the shell valid wrt its invariants.
*
* 2. the ->buf member is a byte array that has at least ->len + 1 bytes
* allocated. The extra byte is used to store a '\0', allowing the ->buf
* member to be a valid C-string. Every strbuf function ensure this
* invariant is preserved.
*
* Note that it is OK to "play" with the buffer directly if you work it
* that way:
*
* strbuf_grow(sb, SOME_SIZE);
* ... Here, the memory array starting at sb->buf, and of length
* ... strbuf_avail(sb) is all yours, and you are sure that
* ... strbuf_avail(sb) is at least SOME_SIZE.
* strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
*
* Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
*
* Doing so is safe, though if it has to be done in many places, adding the
* missing API to the strbuf module is the way to go.
*
* XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
* even if it's true in the current implementation. Alloc is somehow a
* "private" member that should not be messed with.
*/
#include <assert.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
extern char strbuf_slopbuf[];
struct strbuf {
size_t alloc;
size_t len;
char *buf;
};
#define STRBUF_INIT { 0, 0, strbuf_slopbuf }
/*----- strbuf life cycle -----*/
extern void strbuf_init(struct strbuf *, size_t);
extern void strbuf_release(struct strbuf *);
extern char *strbuf_detach(struct strbuf *, size_t *);
extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);
static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {
struct strbuf tmp = *a;
*a = *b;
*b = tmp;
}
/*----- strbuf size related -----*/
static inline size_t strbuf_avail(const struct strbuf *sb) {
return sb->alloc ? sb->alloc - sb->len - 1 : 0;
}
extern void strbuf_grow(struct strbuf *, size_t);
static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
if (!sb->alloc)
strbuf_grow(sb, 0);
assert(len < sb->alloc);
sb->len = len;
sb->buf[len] = '\0';
}
#define strbuf_reset(sb) strbuf_setlen(sb, 0)
/*----- content related -----*/
extern void strbuf_trim(struct strbuf *);
extern void strbuf_rtrim(struct strbuf *);
extern void strbuf_ltrim(struct strbuf *);
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
extern void strbuf_tolower(struct strbuf *);
extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
extern struct strbuf **strbuf_splitws(const struct strbuf *);
extern void strbuf_list_free(struct strbuf **);
extern size_t strbuf_list_len(struct strbuf **);
/*----- add data in your buffer -----*/
static inline void strbuf_addch(struct strbuf *sb, int c) {
strbuf_grow(sb, 1);
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t);
extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
/* splice pos..pos+len with given data */
extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
const void *, size_t);
extern void strbuf_add(struct strbuf *, const void *, size_t);
static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
strbuf_add(sb, s, strlen(s));
}
static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
strbuf_add(sb, sb2->buf, sb2->len);
}
extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
struct strbuf_expand_dict_entry {
const char *placeholder;
const char *value;
};
extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
__attribute__((format(printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
/* XXX: if read fails, any partial read is undone */
extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
extern ssize_t strbuf_write(struct strbuf *sb, int fd);
extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
extern int strbuf_getline(struct strbuf *, FILE *, int);
extern void stripspace(struct strbuf *buf, int skip_comments);
extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
#endif /* STRBUF_H */

View File

@ -1,305 +0,0 @@
#define _ATFILE_SOURCE
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include "util.h"
#include "strbuf.h"
static int log_stderr = 1;
static int log_maxprio = LOG_DEBUG;
void init_log(const char *ident, int option, int facility, int lstderr) {
openlog(ident, option, facility);
log_stderr = lstderr || isatty(STDERR_FILENO);
}
void log_set_maxprio(int prio) {
log_maxprio = prio;
}
static void errmsg(int prio, const char *prefix, const char *fmt, va_list args) {
struct strbuf msg = STRBUF_INIT;
strbuf_addf(&msg, "%s: ", prefix);
strbuf_vaddf(&msg, fmt, args);
strbuf_addch(&msg, '\n');
syslog(prio, "%s", msg.buf);
if (log_stderr && prio <= log_maxprio)
fputs(msg.buf, stderr);
strbuf_release(&msg);
}
static void errmsgpe(int prio, const char *prefix, const char *fmt, va_list args) {
struct strbuf msg = STRBUF_INIT;
strbuf_addf(&msg, "%s: ", prefix);
strbuf_vaddf(&msg, fmt, args);
strbuf_addf(&msg, ": %s\n", strerror(errno));
syslog(prio, "%s", msg.buf);
if (log_stderr && prio <= log_maxprio)
fputs(msg.buf, stderr);
strbuf_release(&msg);
}
NORETURN static void die(int prio, const char *prefix, const char *msg, va_list args) {
errmsg(prio, prefix, msg, args);
exit(1);
}
NORETURN static void diepe(int prio, const char *prefix, const char *msg, va_list args) {
errmsgpe(prio, prefix, msg, args);
exit(1);
}
NORETURN void fatal(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_CRIT, "fatal", msg, args);
va_end(args);
}
void error(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_ERR, "error", msg, args);
va_end(args);
}
void warn(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_WARNING, "warning", msg, args);
va_end(args);
}
void notice(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_NOTICE, "notice", msg, args);
va_end(args);
}
void debug(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_DEBUG, "debug", msg, args);
va_end(args);
}
void logmsg(int priority, const char *msg, ...) {
va_list args;
va_start(args, msg);
vsyslog(priority, msg, args);
va_end(args);
va_start(args, msg);
if (log_stderr && priority <= log_maxprio) {
vfprintf(stderr, msg, args);
fputc('\n', stderr);
}
va_end(args);
}
NORETURN void deny(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_ERR, "denied", msg, args);
va_end(args);
}
NORETURN void badconf(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_CRIT, "configuration error", msg, args);
va_end(args);
}
NORETURN void fatalpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
diepe(LOG_CRIT, "fatal", msg, args);
va_end(args);
}
void errorpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsgpe(LOG_ERR, "error", msg, args);
va_end(args);
}
void warnpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsgpe(LOG_WARNING, "warning", msg, args);
va_end(args);
}
int spawnv(const char *path, char *const argv[]) {
int pid, status;
fflush(stdout);
fflush(stderr);
pid = fork();
if (pid < 0)
fatalpe("fork");
else if (pid)
waitpid(pid, &status, 0);
else
exit(execv(path, argv));
return status;
}
int full_write(int fd, const void *buf, size_t count) {
ssize_t total = 0;
while (total < count) {
ssize_t wcount = write(fd, (char *)buf + total, count - total);
if (wcount < 0)
return wcount;
total += wcount;
}
return 0;
}
int spawnvem(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr) {
return spawnvemu(path, argv, envp, output, input, cap_stderr, NULL);
}
int spawnvemu(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr, char *user) {
int pid, wpid, status;
int tochild[2];
int fmchild[2];
if (pipe(tochild))
fatalpe("pipe");
if (pipe(fmchild))
fatalpe("pipe");
fflush(stdout);
fflush(stderr);
pid = fork();
if (pid < 0)
fatalpe("fork");
if (!pid) {
dup2(tochild[0], STDIN_FILENO);
dup2(fmchild[1], STDOUT_FILENO);
if (cap_stderr)
dup2(STDOUT_FILENO, STDERR_FILENO);
close(tochild[0]);
close(tochild[1]);
close(fmchild[0]);
close(fmchild[1]);
if (user) {
struct passwd *pw = getpwnam(user);
if (!pw)
fatalpe("getpwnam: %s", user);
if (initgroups(user, pw->pw_gid))
fatalpe("initgroups: %s", user);
if (setregid(pw->pw_gid, pw->pw_gid))
fatalpe("setregid: %s", user);
if (setreuid(pw->pw_uid, pw->pw_uid))
fatalpe("setreuid");
}
execve(path, argv, envp);
fatalpe("execve");
} else {
close(tochild[0]);
close(fmchild[1]);
full_write(tochild[1], output->buf, output->len);
close(tochild[1]);
if (input)
strbuf_read(input, fmchild[0], 0);
close(fmchild[0]);
}
wpid = waitpid(pid, &status, 0);
if (wpid < 0)
fatalpe("waitpid");
else if (wpid != pid)
fatal("waitpid is broken");
if (WIFEXITED(status) && WEXITSTATUS(status))
notice("child %s exited with status %d", path, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
notice("child %s killed by signal %d", path, WTERMSIG(status));
return status;
}
int spawnv_msg(const char *path, char *const *argv, const struct strbuf *output) {
return spawnvem(path, argv, environ, output, NULL, 0);
}
int check_group(char *username, char *group) {
struct group *grp = getgrnam(group);
char **members;
if (grp)
for (members = grp->gr_mem; *members; members++)
if (!strcmp(username, *members))
return 1;
return 0;
}
FILE *fopenat(DIR *d, const char *path, int flags) {
int dfd = dirfd(d);
if (dfd < 0)
return NULL;
int fd = openat(dfd, path, flags);
if (fd < 0)
return NULL;
return fdopen(fd, flags & O_RDWR ? "r+" :
flags & O_WRONLY ? "w" :
"r");
}
void make_env(char **envp, ...) {
const size_t len = 4096;
size_t used = 0;
int args = 0;
char *buf = xmalloc(len);
va_list ap;
va_start(ap, envp);
char *name, *val;
while ((name = va_arg(ap, char *))) {
val = va_arg(ap, char *);
if (!val)
continue;
int n = snprintf(buf + used, len - used, "%s=%s", name, val);
if (n < 0)
fatalpe("snprintf");
if (n >= len - used)
fatal("environment too big");
envp[args++] = buf + used;
used += n + 1;
}
if (!args)
free(buf);
envp[args] = NULL;
}
void free_env(char **envp) {
free(*envp);
}

View File

@ -1,89 +0,0 @@
#ifndef CEO_UTIL_H
#define CEO_UTIL_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/types.h>
#include <dirent.h>
#include "strbuf.h"
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#define PRINTF_LIKE(extra) __attribute__((format(printf, extra+1, extra+2)))
#else
#define NORETURN
#define PRINTF_LIKE(extra)
#endif
#ifndef LOG_AUTHPRIV
#define LOG_AUTHPRIV LOG_AUTH
#endif
extern char **environ;
int spawnv(const char *path, char *const *argv);
int spawnv_msg(const char *path, char *const *argv, const struct strbuf *output);
int spawnvem(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr);
int spawnvemu(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr, char *user);
int full_write(int fd, const void *buf, size_t count);
ssize_t full_read(int fd, void *buf, size_t len);
FILE *fopenat(DIR *d, const char *path, int flags);
void make_env(char **envp, ...);
void free_env(char **envp);
void init_log(const char *ident, int option, int facility, int lstderr);
int check_group(char *username, char *group);
void log_set_maxprio(int prio);
PRINTF_LIKE(0) NORETURN void fatal(const char *, ...);
PRINTF_LIKE(0) NORETURN void fatalpe(const char *, ...);
PRINTF_LIKE(0) NORETURN void badconf(const char *, ...);
PRINTF_LIKE(0) NORETURN void deny(const char *, ...);
PRINTF_LIKE(0) void error(const char *, ...);
PRINTF_LIKE(0) void warn(const char *, ...);
PRINTF_LIKE(0) void notice(const char *, ...);
PRINTF_LIKE(0) void debug(const char *, ...);
PRINTF_LIKE(0) void errorpe(const char *, ...);
PRINTF_LIKE(0) void warnpe(const char *, ...);
PRINTF_LIKE(1) void logmsg(int priority, const char *, ...);
static inline void *xmalloc(size_t size) {
void *alloc = malloc(size);
if (alloc == NULL)
fatal("out of memory");
return alloc;
}
static inline void *xrealloc(void *ptr, size_t size) {
void *alloc = realloc(ptr, size);
if (alloc == NULL)
fatal("out of memory");
return alloc;
}
static inline void *xcalloc(size_t nmemb, size_t size) {
void *alloc = calloc(nmemb, size);
if (alloc == NULL)
fatal("out of memory");
return alloc;
}
static inline char *xstrdup(const char *s) {
char *dup = strdup(s);
if (dup == NULL)
fatal("out of memory");
return dup;
}
#endif