Add mysql database stuff

This commit is contained in:
Michael Spang 2009-09-09 17:37:35 -04:00
parent 827c17b107
commit 2552bc2243
18 changed files with 279 additions and 25 deletions

2
.gitignore vendored
View File

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

View File

@ -91,6 +91,11 @@ def connect(auth_callback):
if password == None:
raise e
def connect_anonymous():
"""Connect to LDAP."""
global ld
ld = ldap.initialize(cfg['ldap_server_url'])
def disconnect():
"""Disconnect from LDAP."""

24
ceo/mysql.py Normal file
View File

@ -0,0 +1,24 @@
import os, re, subprocess, ldap, socket
from ceo import conf, ldapi, terms, remote, ceo_pb2
from ceo.excep import InvalidArgument
class MySQLException(Exception):
pass
def create_mysql(username):
try:
request = ceo_pb2.AddMySQLUser()
request.username = username
out = remote.run_remote('mysql', request.SerializeToString())
response = ceo_pb2.AddMySQLUserResponse()
response.ParseFromString(out)
if any(message.status != 0 for message in response.messages):
raise MySQLException('\n'.join(message.message for message in response.messages))
return response.password
except remote.RemoteException, e:
raise MySQLException(e)

View File

@ -1,15 +1,78 @@
import urwid
from ceo import members
from ceo import members, mysql
from ceo.urwid import search
from ceo.urwid.widgets import *
from ceo.urwid.window import *
def databases(menu):
menu = make_menu([
("Create MySQL database", create_mysql_db, None),
("Back", raise_back, None),
])
push_window(menu, "Databases")
class IntroPage(WizardPanel):
def init_widgets(self):
self.widgets = [
urwid.Text("MySQL databases"),
urwid.Divider(),
urwid.Text("Members and hosted clubs may have one MySQL database each. You may "
"create a database for an account if: \n"
"\n"
"- It is your personal account,\n"
"- It is a club account, and you are in the club group, or\n"
"- You are on the CSC systems committee\n"
"\n"
"You may also use this to reset your database password."
)
]
def focusable(self):
return False
def create_mysql_db(data):
pass
class UserPage(WizardPanel):
def init_widgets(self):
self.userid = LdapWordEdit(csclub_uri, csclub_base, 'uid',
"Username: ")
self.widgets = [
urwid.Text("Member Information"),
urwid.Divider(),
urwid.Text("Enter the user which will own the new database."),
urwid.Divider(),
self.userid,
]
def check(self):
self.state['userid'] = self.userid.get_edit_text()
self.state['member'] = None
if self.state['userid']:
self.state['member'] = members.get(self.userid.get_edit_text())
if not self.state['member']:
set_status("Member not found")
self.focus_widget(self.userid)
return True
class EndPage(WizardPanel):
def init_widgets(self):
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def activate(self):
problem = None
try:
password = mysql.create_mysql(self.state['userid'])
self.headtext.set_text("MySQL database created")
self.midtext.set_text("Connection Information: \n"
"\n"
"Database: %s\n"
"Username: %s\n"
"Hostname: localhost\n"
"Password: %s\n"
"\n"
"Note: Databases are only accessible from caffeine\n"
% (self.state['userid'], self.state['userid'], password))
except mysql.MySQLException, e:
self.headtext.set_text("Failed to create MySQL database")
self.midtext.set_text("We failed to create the database. The error was:\n\n%s" % e)
def check(self):
pop_window()

View File

@ -143,6 +143,13 @@ def change_shell(data):
shell.EndPage
], (50, 20))
def create_mysql_db(data):
push_wizard("Create MySQL database", [
databases.IntroPage,
databases.UserPage,
databases.EndPage,
], (60, 15))
def check_group(group):
try:
me = pwd.getpwuid(os.getuid()).pw_name
@ -158,7 +165,6 @@ def top_menu():
("Renew Club Rep", renew_club_user, None),
("New Club", new_club, None),
("Library", library.library, None),
("Databases", databases.databases, None),
]
syscom_only = [
("Manage Club or Group Members", manage_group, None),
@ -168,8 +174,9 @@ def top_menu():
]
unrestricted = [
("Display Member", display_member, None),
("Change Shell", change_shell, None),
("Search", search_members, None),
("Change Shell", change_shell, None),
("Create MySQL database", create_mysql_db, None),
]
footer = [
("Exit", raise_abort, None),

2
debian/control vendored
View File

@ -29,7 +29,7 @@ Description: Computer Science Club Administrative Clients
Package: ceo-daemon
Architecture: any
Depends: ceo-python, ${shlibs:Depends}
Depends: ceo-python, ${python:Depends}, ${shlibs:Depends}
Description: Computer Science Club Administrative Daemon
This package contains the CSC Electronic Office
daemon.

9
debian/rules vendored
View File

@ -4,7 +4,6 @@ CFLAGS := -g -O2 -fstack-protector-all -fPIE
LDFLAGS := -pie -Wl,--as-needed
build:
python setup.py -q build
cd src && make CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
clean:
@ -12,13 +11,17 @@ clean:
dh_testroot
dh_clean
$(MAKE) -C src clean
python setup.py -q clean -a
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 install --no-compile -O0 --prefix=/usr --root=debian/ceo-python
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

View File

@ -1 +1 @@
ginseng adduser 0x01
ginseng adduser root 0x01

View File

@ -1 +1 @@
ginseng mail 0x02
ginseng mail root 0x02

1
etc/ops/mysql Normal file
View File

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

View File

@ -35,6 +35,7 @@ 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

View File

@ -31,3 +31,12 @@ message UpdateMail {
message UpdateMailResponse {
repeated StatusMessage messages = 1;
}
message AddMySQLUser {
required string username = 1;
}
message AddMySQLUserResponse {
repeated StatusMessage messages = 1;
optional string password = 2;
}

View File

@ -97,7 +97,7 @@ static void handle_op_message(uint32_t in_type, struct strbuf *in, struct strbuf
"CEO_CONFIG_DIR", config_dir, NULL);
char *argv[] = { op->path, NULL, };
if (spawnvem(op->path, argv, envp, in, out, 0))
if (spawnvemu(op->path, argv, envp, in, out, 0, op->user))
fatal("child %s failed", op->path);
if (!out->len)

113
src/op-mysql Executable file
View File

@ -0,0 +1,113 @@
#!/usr/bin/python
import os, sys, string, random, syslog, grp, errno, re
from ceo import ceo_pb2, members, conf
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 response_message(response, status, message):
priority = syslog.LOG_ERR if status else syslog.LOG_INFO
syslog.syslog(priority, message)
msg = response.messages.add()
msg.status = status
msg.message = message
return status
def random_password():
chars = string.letters + string.digits
return ''.join(random.choice(chars) for i in xrange(20))
def get_ceo_user():
user = os.environ.get('CEO_USER')
if not user:
raise Exception("environment variable CEO_USER not set");
return user
def check_group(user, group):
try:
return user in grp.getgrnam(group).gr_mem
except KeyError:
return False
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 '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))
else:
return response_message(response, errno.EPERM, 'denied, user %s is not in club group %s' % (remote_user, mysql_user))
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()

View File

@ -3,6 +3,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include "strbuf.h"
#include "ops.h"
@ -15,13 +16,14 @@ 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, uint32_t id) {
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)
@ -35,11 +37,15 @@ static void add_op(char *host, char *name, uint32_t id) {
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)", new->name, new->local ? "" : "on ",
new->local ? "local" : host);
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) {
@ -88,16 +94,16 @@ void setup_ops(void) {
struct strbuf **words = strbuf_splitws(&line);
if (strbuf_list_len(words) != 3)
badconf("%s/%s: expected three words on line %d", op_config_dir, de->d_name, lineno);
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[2]->buf, &end, 0);
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, id);
add_op(words[0]->buf, words[1]->buf, words[2]->buf, id);
op_count++;
strbuf_list_free(words);
@ -115,6 +121,7 @@ void free_ops(void) {
free(ops->name);
free(ops->hostname);
free(ops->path);
free(ops->user);
free(ops);
ops = next;
}

View File

@ -6,6 +6,7 @@ struct op {
char *path;
struct in_addr addr;
struct op *next;
char *user;
};
void setup_ops(void);

View File

@ -8,6 +8,7 @@
#include <syslog.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include "util.h"
#include "strbuf.h"
@ -173,6 +174,10 @@ void full_write(int fd, const void *buf, size_t count) {
}
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];
@ -197,6 +202,18 @@ int spawnvem(const char *path, char *const *argv, char *const *envp, const struc
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 {

View File

@ -28,6 +28,7 @@ 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);
void 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);