/* Authors: Jakub Hrozek Copyright (C) 2009 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 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, see . */ #include #include #include #include #include #include "util/util.h" #include "db/sysdb.h" #include "tools/tools_util.h" #include "tools/sss_sync_ops.h" #include "util/crypto/sss_crypto.h" /* * function taken from samba sources tree as of Aug 20 2009, * file source4/lib/ldb/pyldb.c */ static char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list, const char *paramname) { char **ret; int i; ret = talloc_array(NULL, char *, PyList_Size(list)+1); for (i = 0; i < PyList_Size(list); i++) { PyObject *item = PyList_GetItem(list, i); if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "%s should be strings", paramname); return NULL; } ret[i] = talloc_strndup(ret, PyString_AsString(item), PyString_Size(item)); } ret[i] = NULL; return ret; } /* ======================= sysdb python wrappers ==========================*/ /* * The sss.password object */ typedef struct { PyObject_HEAD TALLOC_CTX *mem_ctx; struct tevent_context *ev; struct sysdb_ctx *sysdb; struct confdb_ctx *confdb; struct sss_domain_info *local; int lock; int unlock; } PySssLocalObject; /* * The transaction object */ struct py_sss_transaction { PySssLocalObject *self; struct ops_ctx *ops; bool transaction_done; int error; }; /* * Error reporting */ static void PyErr_SetSssErrorWithMessage(int ret, const char *message) { PyObject *exc = Py_BuildValue(discard_const_p(char, "(is)"), ret, message); PyErr_SetObject(PyExc_IOError, exc); Py_XDECREF(exc); } static void PyErr_SetSssError(int ret) { PyErr_SetSssErrorWithMessage(ret, strerror(ret)); } /* * Common init of all methods */ struct tools_ctx *init_ctx(TALLOC_CTX *mem_ctx, PySssLocalObject *self) { struct ops_ctx *octx = NULL; struct tools_ctx *tctx = NULL; tctx = talloc_zero(self->mem_ctx, struct tools_ctx); if (tctx == NULL) { return NULL; } tctx->confdb = self->confdb; tctx->sysdb = self->sysdb; tctx->local = self->local; /* tctx->nctx is NULL here, which is OK since we don't parse domains * in the python bindings (yet?) */ octx = talloc_zero(tctx, struct ops_ctx); if (octx == NULL) { PyErr_NoMemory(); return NULL; } octx->domain = self->local; tctx->octx = octx; return tctx; } /* * Add a user */ PyDoc_STRVAR(py_sss_useradd__doc__, "Add a user named ``username``.\n\n" ":param username: name of the user\n\n" ":param kwargs: Keyword arguments that customize the operation\n\n" "* useradd can be customized further with keyword arguments:\n" " * ``uid``: The UID of the user\n" " * ``gid``: The GID of the user\n" " * ``gecos``: The comment string\n" " * ``homedir``: Home directory\n" " * ``shell``: Login shell\n" " * ``skel``: Specify an alternative skeleton directory\n" " * ``create_home``: (bool) Force creation of home directory on or off\n" " * ``groups``: List of groups the user is member of\n"); static PyObject *py_sss_useradd(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; unsigned long uid = 0; unsigned long gid = 0; const char *gecos = NULL; const char *home = NULL; const char *shell = NULL; const char *skel = NULL; char *username = NULL; int ret; const char * const kwlist[] = { "username", "uid", "gid", "gecos", "homedir", "shell", "skel", "create_home", "groups", NULL }; PyObject *py_groups = Py_None; PyObject *py_create_home = Py_None; int create_home = 0; /* parse arguments */ if (!PyArg_ParseTupleAndKeywords(args, kwds, discard_const_p(char, "s|kkssssO!O!"), discard_const_p(char *, kwlist), &username, &uid, &gid, &gecos, &home, &shell, &skel, &PyBool_Type, &py_create_home, &PyList_Type, &py_groups)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } if (py_groups != Py_None) { tctx->octx->addgroups = PyList_AsStringList(tctx, py_groups, "groups"); if (!tctx->octx->addgroups) { PyErr_NoMemory(); return NULL; } } /* user-wise the parameter is only bool - do or don't, * however we must have a third state - undecided, pick default */ if (py_create_home == Py_True) { create_home = DO_CREATE_HOME; } else if (py_create_home == Py_False) { create_home = DO_NOT_CREATE_HOME; } tctx->octx->name = username; tctx->octx->uid = uid; /* fill in defaults */ ret = useradd_defaults(tctx, self->confdb, tctx->octx, gecos, home, shell, create_home, skel); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } /* Add the user within a transaction */ tctx->error = sysdb_transaction_start(tctx->sysdb); if (tctx->error != EOK) { PyErr_SetSssError(tctx->error); goto fail; } /* useradd */ tctx->error = useradd(tctx, tctx->sysdb, tctx->octx); if (tctx->error) { /* cancel transaction */ sysdb_transaction_cancel(tctx->sysdb); PyErr_SetSssError(tctx->error); goto fail; } tctx->error = sysdb_transaction_commit(tctx->sysdb); if (tctx->error) { PyErr_SetSssError(tctx->error); goto fail; } /* Create user's home directory and/or mail spool */ if (tctx->octx->create_homedir) { /* We need to know the UID and GID of the user, if * sysdb did assign it automatically, do a lookup */ if (tctx->octx->uid == 0 || tctx->octx->gid == 0) { ret = sysdb_getpwnam_sync(tctx, tctx->sysdb, tctx->octx->name, tctx->local, tctx->octx); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } } ret = create_homedir(tctx, tctx->octx->skeldir, tctx->octx->home, tctx->octx->name, tctx->octx->uid, tctx->octx->gid, tctx->octx->umask); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } /* failure here should not be fatal */ create_mail_spool(tctx, tctx->octx->name, tctx->octx->maildir, tctx->octx->uid, tctx->octx->gid); } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /* * Delete a user */ PyDoc_STRVAR(py_sss_userdel__doc__, "Remove the user named ``username``.\n\n" ":param username: Name of user being removed\n" ":param kwargs: Keyword arguments that customize the operation\n\n" "* userdel can be customized further with keyword arguments:\n" " * ``force``: (bool) Force removal of files not owned by the user\n" " * ``remove``: (bool) Toggle removing home directory and mail spool\n"); static PyObject *py_sss_userdel(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; char *username = NULL; int ret; PyObject *py_remove = Py_None; int remove_home = 0; PyObject *py_force = Py_None; const char * const kwlist[] = { "username", "remove", "force", NULL }; if(!PyArg_ParseTupleAndKeywords(args, kwds, discard_const_p(char, "s|O!O!"), discard_const_p(char *, kwlist), &username, &PyBool_Type, &py_remove, &PyBool_Type, &py_force)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } tctx->octx->name = username; if (py_remove == Py_True) { remove_home = DO_REMOVE_HOME; } else if (py_remove == Py_False) { remove_home = DO_NOT_REMOVE_HOME; } /* * Fills in defaults for ops_ctx user did not specify. */ ret = userdel_defaults(tctx, tctx->confdb, tctx->octx, remove_home); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } ret = run_userdel_cmd(tctx); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } if (tctx->octx->remove_homedir) { ret = sysdb_getpwnam_sync(tctx, tctx->sysdb, tctx->octx->name, tctx->local, tctx->octx); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } } /* Delete the user */ ret = userdel(tctx, self->sysdb, tctx->octx); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } if (tctx->octx->remove_homedir) { ret = remove_homedir(tctx, tctx->octx->home, tctx->octx->maildir, tctx->octx->name, tctx->octx->uid, (py_force == Py_True)); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /* * Modify a user */ PyDoc_STRVAR(py_sss_usermod__doc__, "Modify a user.\n\n" ":param username: Name of user being modified\n\n" ":param kwargs: Keyword arguments that customize the operation\n\n" "* usermod can be customized further with keyword arguments:\n" " * ``uid``: The UID of the user\n" " * ``gid``: The GID of the user\n" " * ``gecos``: The comment string\n" " * ``homedir``: Home directory\n" " * ``shell``: Login shell\n" " * ``addgroups``: List of groups to add the user to\n" " * ``rmgroups``: List of groups to remove the user from\n" " * ``lock``: Lock or unlock the account\n"); static PyObject *py_sss_usermod(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; PyObject *py_addgroups = Py_None; PyObject *py_rmgroups = Py_None; unsigned long uid = 0; unsigned long gid = 0; char *gecos = NULL; char *home = NULL; char *shell = NULL; char *username = NULL; unsigned long lock = 0; const char * const kwlist[] = { "username", "uid", "gid", "lock", "gecos", "homedir", "shell", "addgroups", "rmgroups", NULL }; /* parse arguments */ if (!PyArg_ParseTupleAndKeywords(args, kwds, discard_const_p(char, "s|kkksssO!O!"), discard_const_p(char *, kwlist), &username, &uid, &gid, &lock, &gecos, &home, &shell, &PyList_Type, &py_addgroups, &PyList_Type, &py_rmgroups)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } if (lock && lock != DO_LOCK && lock != DO_UNLOCK) { PyErr_SetString(PyExc_ValueError, "Unkown value for lock parameter"); goto fail; } if (py_addgroups != Py_None) { tctx->octx->addgroups = PyList_AsStringList(tctx, py_addgroups, "addgroups"); if (!tctx->octx->addgroups) { return NULL; } } if (py_rmgroups != Py_None) { tctx->octx->rmgroups = PyList_AsStringList(tctx, py_rmgroups, "rmgroups"); if (!tctx->octx->rmgroups) { return NULL; } } tctx->octx->name = username; tctx->octx->uid = uid; tctx->octx->gid = gid; tctx->octx->gecos = gecos; tctx->octx->home = home; tctx->octx->shell = shell; tctx->octx->lock = lock; /* Modify the user within a transaction */ tctx->error = sysdb_transaction_start(tctx->sysdb); if (tctx->error != EOK) { PyErr_SetSssError(tctx->error); goto fail; } /* usermod */ tctx->error = usermod(tctx, tctx->sysdb, tctx->octx); if (tctx->error) { /* cancel transaction */ sysdb_transaction_cancel(tctx->sysdb); PyErr_SetSssError(tctx->error); goto fail; } tctx->error = sysdb_transaction_commit(tctx->sysdb); if (tctx->error) { PyErr_SetSssError(tctx->error); goto fail; } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /* * Add a group */ PyDoc_STRVAR(py_sss_groupadd__doc__, "Add a group.\n\n" ":param groupname: Name of group being added\n\n" ":param kwargs: Keyword arguments ro customize the operation\n\n" "* groupmod can be customized further with keyword arguments:\n" " * ``gid``: The GID of the group\n"); static PyObject *py_sss_groupadd(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; char *groupname; unsigned long gid = 0; const char * const kwlist[] = { "groupname", "gid", NULL }; /* parse arguments */ if (!PyArg_ParseTupleAndKeywords(args, kwds, discard_const_p(char, "s|k"), discard_const_p(char *, kwlist), &groupname, &gid)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } tctx->octx->name = groupname; tctx->octx->gid = gid; /* Add the group within a transaction */ tctx->error = sysdb_transaction_start(tctx->sysdb); if (tctx->error != EOK) { PyErr_SetSssError(tctx->error); goto fail; } /* groupadd */ tctx->error = groupadd(tctx, tctx->sysdb, tctx->octx); if (tctx->error) { /* cancel transaction */ sysdb_transaction_cancel(tctx->sysdb); PyErr_SetSssError(tctx->error); goto fail; } tctx->error = sysdb_transaction_commit(tctx->sysdb); if (tctx->error) { PyErr_SetSssError(tctx->error); goto fail; } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /* * Delete a group */ PyDoc_STRVAR(py_sss_groupdel__doc__, "Remove a group.\n\n" ":param groupname: Name of group being removed\n"); static PyObject *py_sss_groupdel(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; char *groupname = NULL; int ret; if(!PyArg_ParseTuple(args, discard_const_p(char, "s"), &groupname)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } tctx->octx->name = groupname; /* Remove the group */ ret = groupdel(tctx, self->sysdb, tctx->octx); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /* * Modify a group */ PyDoc_STRVAR(py_sss_groupmod__doc__, "Modify a group.\n\n" ":param groupname: Name of group being modified\n\n" ":param kwargs: Keyword arguments ro customize the operation\n\n" "* groupmod can be customized further with keyword arguments:\n" " * ``gid``: The GID of the group\n\n" " * ``addgroups``: Groups to add the group to\n\n" " * ``rmgroups``: Groups to remove the group from\n\n"); static PyObject *py_sss_groupmod(PySssLocalObject *self, PyObject *args, PyObject *kwds) { struct tools_ctx *tctx = NULL; PyObject *py_addgroups = Py_None; PyObject *py_rmgroups = Py_None; unsigned long gid = 0; char *groupname = NULL; const char * const kwlist[] = { "groupname", "gid", "addgroups", "rmgroups", NULL }; /* parse arguments */ if (!PyArg_ParseTupleAndKeywords(args, kwds, discard_const_p(char, "s|kO!O!"), discard_const_p(char *, kwlist), &groupname, &gid, &PyList_Type, &py_addgroups, &PyList_Type, &py_rmgroups)) { goto fail; } tctx = init_ctx(self->mem_ctx, self); if (!tctx) { PyErr_NoMemory(); return NULL; } if (py_addgroups != Py_None) { tctx->octx->addgroups = PyList_AsStringList(tctx, py_addgroups, "addgroups"); if (!tctx->octx->addgroups) { return NULL; } } if (py_rmgroups != Py_None) { tctx->octx->rmgroups = PyList_AsStringList(tctx, py_rmgroups, "rmgroups"); if (!tctx->octx->rmgroups) { return NULL; } } tctx->octx->name = groupname; tctx->octx->gid = gid; /* Modify the group within a transaction */ tctx->error = sysdb_transaction_start(tctx->sysdb); if (tctx->error != EOK) { PyErr_SetSssError(tctx->error); goto fail; } /* groupmod */ tctx->error = groupmod(tctx, tctx->sysdb, tctx->octx); if (tctx->error) { /* cancel transaction */ sysdb_transaction_cancel(tctx->sysdb); PyErr_SetSssError(tctx->error); goto fail; } tctx->error = sysdb_transaction_commit(tctx->sysdb); if (tctx->error) { PyErr_SetSssError(tctx->error); goto fail; } talloc_zfree(tctx); Py_RETURN_NONE; fail: talloc_zfree(tctx); return NULL; } /*** python plumbing begins here ***/ /* * The sss.local destructor */ static void PySssLocalObject_dealloc(PySssLocalObject *self) { talloc_free(self->mem_ctx); self->ob_type->tp_free((PyObject*) self); } /* * The sss.local constructor */ static PyObject *PySssLocalObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { TALLOC_CTX *mem_ctx; PySssLocalObject *self; char *confdb_path; int ret; mem_ctx = talloc_new(NULL); if (mem_ctx == NULL) { PyErr_NoMemory(); return NULL; } self = (PySssLocalObject *) type->tp_alloc(type, 0); if (self == NULL) { talloc_free(mem_ctx); PyErr_NoMemory(); return NULL; } self->mem_ctx = mem_ctx; confdb_path = talloc_asprintf(self->mem_ctx, "%s/%s", DB_PATH, CONFDB_FILE); if (confdb_path == NULL) { talloc_free(mem_ctx); PyErr_NoMemory(); return NULL; } /* Connect to the conf db */ ret = confdb_init(self->mem_ctx, &self->confdb, confdb_path); if (ret != EOK) { talloc_free(mem_ctx); PyErr_SetSssErrorWithMessage(ret, "Could not initialize connection to the confdb\n"); return NULL; } ret = confdb_get_domain(self->confdb, "local", &self->local); if (ret != EOK) { talloc_free(mem_ctx); PyErr_SetSssErrorWithMessage(ret, "Cannot get local domain"); return NULL; } /* open 'local' sysdb at default path */ ret = sysdb_domain_init(self->mem_ctx, self->local, DB_PATH, &self->sysdb); if (ret != EOK) { talloc_free(mem_ctx); PyErr_SetSssErrorWithMessage(ret, "Could not initialize connection to the sysdb\n"); return NULL; } self->lock = DO_LOCK; self->unlock = DO_UNLOCK; return (PyObject *) self; } /* * sss.local object methods */ static PyMethodDef sss_local_methods[] = { { "useradd", (PyCFunction) py_sss_useradd, METH_KEYWORDS, py_sss_useradd__doc__ }, { "userdel", (PyCFunction) py_sss_userdel, METH_KEYWORDS, py_sss_userdel__doc__ }, { "usermod", (PyCFunction) py_sss_usermod, METH_KEYWORDS, py_sss_usermod__doc__ }, { "groupadd", (PyCFunction) py_sss_groupadd, METH_KEYWORDS, py_sss_groupadd__doc__ }, { "groupdel", (PyCFunction) py_sss_groupdel, METH_KEYWORDS, py_sss_groupdel__doc__ }, { "groupmod", (PyCFunction) py_sss_groupmod, METH_KEYWORDS, py_sss_groupmod__doc__ }, {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyMemberDef sss_local_members[] = { { discard_const_p(char, "lock"), T_INT, offsetof(PySssLocalObject, lock), RO, NULL}, { discard_const_p(char, "unlock"), T_INT, offsetof(PySssLocalObject, unlock), RO, NULL}, {NULL, 0, 0, 0, NULL} /* Sentinel */ }; /* * sss.local object properties */ static PyTypeObject pysss_local_type = { PyObject_HEAD_INIT(NULL) .tp_name = "sss.local", .tp_basicsize = sizeof(PySssLocalObject), .tp_new = PySssLocalObject_new, .tp_dealloc = (destructor) PySssLocalObject_dealloc, .tp_methods = sss_local_methods, .tp_members = sss_local_members, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = "SSS DB manipulation", }; /* ==================== obfuscation python wrappers ========================*/ /* * The sss.local object */ typedef struct { PyObject_HEAD int aes_256; } PySssPasswordObject; PyDoc_STRVAR(py_sss_encrypt__doc__, "Obfuscate a password\n\n" ":param password: The password to obfuscate\n\n" ":param method: The obfuscation method\n\n"); static PyObject *py_sss_encrypt(PySssPasswordObject *self, PyObject *args, PyObject *kwds) { char *password = NULL; int plen; /* may contain NULL bytes */ char *obfpwd = NULL; TALLOC_CTX *tctx = NULL; int ret; int mode; PyObject *retval = NULL; /* parse arguments */ if (!PyArg_ParseTuple(args, discard_const_p(char, "s#i"), &password, &plen, &mode)) { return NULL; } tctx = talloc_new(NULL); if (!tctx) { PyErr_NoMemory(); return NULL; } ret = sss_password_encrypt(tctx, password, plen+1, mode, &obfpwd); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } retval = Py_BuildValue("s", obfpwd); if (retval == NULL) { goto fail; } fail: talloc_zfree(tctx); return retval; } #if 0 PyDoc_STRVAR(py_sss_decrypt__doc__, "Deobfuscate a password\n\n" ":param obfpwd: The password to convert back to clear text\n\n"); static PyObject *py_sss_decrypt(PySssPasswordObject *self, PyObject *args, PyObject *kwds) { char *password = NULL; char *obfpwd = NULL; TALLOC_CTX *tctx = NULL; int ret; PyObject *retval = NULL; /* parse arguments */ if (!PyArg_ParseTuple(args, discard_const_p(char, "s"), &obfpwd)) { return NULL; } tctx = talloc_new(NULL); if (!tctx) { PyErr_NoMemory(); return NULL; } ret = sss_password_decrypt(tctx, obfpwd, &password); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; } retval = Py_BuildValue("s", password); if (retval == NULL) { goto fail; } fail: talloc_zfree(tctx); return retval; } #endif /* * The sss.password destructor */ static void PySssPasswordObject_dealloc(PySssPasswordObject *self) { self->ob_type->tp_free((PyObject*) self); } /* * The sss.password constructor */ static PyObject *PySssPasswordObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PySssPasswordObject *self; self = (PySssPasswordObject *) type->tp_alloc(type, 0); if (self == NULL) { PyErr_NoMemory(); return NULL; } self->aes_256 = AES_256; return (PyObject *) self; } /* * sss.password object methods */ static PyMethodDef sss_password_methods[] = { { "encrypt", (PyCFunction) py_sss_encrypt, METH_VARARGS | METH_STATIC, py_sss_encrypt__doc__ }, #if 0 { "decrypt", (PyCFunction) py_sss_decrypt, METH_VARARGS | METH_STATIC, py_sss_decrypt__doc__ }, #endif {NULL, NULL, 0, NULL} /* Sentinel */ }; /* * sss.password object members */ static PyMemberDef sss_password_members[] = { { discard_const_p(char, "AES_256"), T_INT, offsetof(PySssPasswordObject, aes_256), RO, NULL}, {NULL, 0, 0, 0, NULL} /* Sentinel */ }; /* * sss.password object properties */ static PyTypeObject pysss_password_type = { PyObject_HEAD_INIT(NULL) .tp_name = "sss.password", .tp_basicsize = sizeof(PySssPasswordObject), .tp_new = PySssPasswordObject_new, .tp_dealloc = (destructor) PySssPasswordObject_dealloc, .tp_methods = sss_password_methods, .tp_members = sss_password_members, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = "SSS password obfuscation", }; /* ==================== the sss module initialization =======================*/ /* * Module methods */ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; /* * Module initialization */ PyMODINIT_FUNC initpysss(void) { PyObject *m; if (PyType_Ready(&pysss_local_type) < 0) return; if (PyType_Ready(&pysss_password_type) < 0) return; m = Py_InitModule(discard_const_p(char, "pysss"), module_methods); if (m == NULL) return; Py_INCREF(&pysss_local_type); PyModule_AddObject(m, discard_const_p(char, "local"), (PyObject *)&pysss_local_type); Py_INCREF(&pysss_password_type); PyModule_AddObject(m, discard_const_p(char, "password"), (PyObject *)&pysss_password_type); }