Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : Samba python bindings to s3 libnet library
4 :
5 : Copyright (C) David Mulder <dmulder@samba.org>
6 :
7 : This program is free software; you can redistribute it and/or modify
8 : it under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : This program is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program. If not, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "lib/replace/system/python.h"
22 : #include "includes.h"
23 : #include <pytalloc.h>
24 : #include "python/modules.h"
25 : #include "python/py3compat.h"
26 : #include "rpc_client/rpc_client.h"
27 : #include <sys/socket.h>
28 : #include "net.h"
29 : #include "auth/credentials/credentials.h"
30 : #include "auth/credentials/pycredentials.h"
31 : #include "lib/cmdline_contexts.h"
32 : #include "param/loadparm.h"
33 : #include "param/s3_param.h"
34 : #include "param/pyparam.h"
35 : #include "py_net.h"
36 : #include "librpc/gen_ndr/libnet_join.h"
37 : #include "libnet/libnet_join.h"
38 : #include "libcli/security/dom_sid.h"
39 : #include "dynconfig/dynconfig.h"
40 :
41 8 : static WERROR check_ads_config(struct loadparm_context *lp_ctx)
42 : {
43 8 : if (lpcfg_server_role(lp_ctx) != ROLE_DOMAIN_MEMBER ) {
44 0 : d_printf(_("Host is not configured as a member server.\n"));
45 0 : return WERR_INVALID_DOMAIN_ROLE;
46 : }
47 :
48 8 : if (strlen(lpcfg_netbios_name(lp_ctx)) > 15) {
49 0 : d_printf(_("Our netbios name can be at most 15 chars long, "
50 : "\"%s\" is %u chars long\n"), lpcfg_netbios_name(lp_ctx),
51 0 : (unsigned int)strlen(lpcfg_netbios_name(lp_ctx)));
52 0 : return WERR_INVALID_COMPUTERNAME;
53 : }
54 :
55 8 : if ( lpcfg_security(lp_ctx) == SEC_ADS && !*lpcfg_realm(lp_ctx)) {
56 0 : d_fprintf(stderr, _("realm must be set in %s for ADS "
57 : "join to succeed.\n"), get_dyn_CONFIGFILE());
58 0 : return WERR_INVALID_PARAMETER;
59 : }
60 :
61 8 : return WERR_OK;
62 : }
63 :
64 8 : static PyObject *py_net_join_member(py_net_Object *self, PyObject *args, PyObject *kwargs)
65 : {
66 8 : struct libnet_JoinCtx *r = NULL;
67 0 : struct net_context *c;
68 0 : WERROR werr;
69 0 : PyObject *result;
70 0 : TALLOC_CTX *mem_ctx;
71 8 : int no_dns_updates = false, debug = false;
72 8 : bool modify_config = lp_config_backend_is_registry();
73 8 : const char *kwnames[] = { "dnshostname", "createupn", "createcomputer",
74 : "osName", "osVer", "osServicePack",
75 : "machinepass", "debug", "noDnsUpdates", NULL };
76 :
77 8 : mem_ctx = talloc_new(self->mem_ctx);
78 8 : if (mem_ctx == NULL) {
79 0 : PyErr_NoMemory();
80 0 : return NULL;
81 : }
82 8 : c = talloc_zero(mem_ctx, struct net_context);
83 8 : c->msg_ctx = mem_ctx;
84 :
85 8 : werr = libnet_init_JoinCtx(mem_ctx, &r);
86 8 : if (!W_ERROR_IS_OK(werr)) {
87 0 : PyErr_NoMemory();
88 0 : return NULL;
89 : }
90 :
91 8 : if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|sssssszpp:Join",
92 : discard_const_p(char *, kwnames),
93 8 : &r->in.dnshostname,
94 8 : &r->in.upn,
95 8 : &r->in.account_ou,
96 8 : &r->in.os_name,
97 8 : &r->in.os_version,
98 8 : &r->in.os_servicepack,
99 8 : &r->in.machine_password,
100 : &debug,
101 : &no_dns_updates)) {
102 0 : talloc_free(mem_ctx);
103 0 : PyErr_FromString(_("Invalid arguments\n"));
104 0 : return NULL;
105 : }
106 :
107 8 : if (!modify_config) {
108 8 : werr = check_ads_config(self->lp_ctx);
109 8 : if (!W_ERROR_IS_OK(werr)) {
110 0 : PyErr_SetWERROR_and_string(werr,
111 : _("Invalid configuration. Exiting....\n"));
112 0 : talloc_free(mem_ctx);
113 0 : return NULL;
114 : }
115 : }
116 :
117 8 : r->in.domain_name = lpcfg_realm(self->lp_ctx);
118 8 : r->in.domain_name_type = JoinDomNameTypeDNS;
119 8 : r->in.create_upn = r->in.upn != NULL ? true : false;
120 8 : r->in.dc_name = self->server_address;
121 8 : r->in.admin_credentials = self->creds;
122 8 : r->in.modify_config = modify_config;
123 8 : r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
124 : WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE |
125 : WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED;
126 8 : r->in.msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
127 8 : r->in.debug = debug;
128 :
129 8 : c->creds = self->creds;
130 8 : c->explicit_credentials = true;
131 :
132 8 : werr = libnet_Join(mem_ctx, r);
133 8 : if (W_ERROR_EQUAL(werr, WERR_NERR_DCNOTFOUND)) {
134 0 : r->in.domain_name = lpcfg_workgroup(self->lp_ctx);
135 0 : r->in.domain_name_type = JoinDomNameTypeNBT;
136 0 : werr = libnet_Join(mem_ctx, r);
137 : }
138 8 : if (!W_ERROR_IS_OK(werr)) {
139 0 : PyErr_SetWERROR_and_string(werr,
140 : r->out.error_string
141 : ? r->out.error_string
142 : : get_friendly_werror_msg(werr));
143 0 : talloc_free(mem_ctx);
144 0 : return NULL;
145 : }
146 :
147 : /*
148 : * Check the short name of the domain
149 : */
150 :
151 8 : if (!modify_config && !strequal(lpcfg_workgroup(self->lp_ctx), r->out.netbios_domain_name)) {
152 0 : d_printf(_("The workgroup in %s does not match the short\n"
153 : "domain name obtained from the server.\n"
154 : "Using the name [%s] from the server.\n"
155 : "You should set \"workgroup = %s\" in %s.\n"),
156 0 : get_dyn_CONFIGFILE(), r->out.netbios_domain_name,
157 0 : r->out.netbios_domain_name, get_dyn_CONFIGFILE());
158 : }
159 :
160 : /*
161 : * We try doing the dns update (if it was compiled in
162 : * and if it was not disabled on the command line).
163 : * If the dns update fails, we still consider the join
164 : * operation as succeeded if we came this far.
165 : */
166 8 : if (!no_dns_updates) {
167 8 : net_ads_join_dns_updates(c, mem_ctx, r);
168 : }
169 :
170 8 : result = Py_BuildValue("ss", dom_sid_string(mem_ctx, r->out.domain_sid),
171 8 : r->out.dns_domain_name);
172 :
173 8 : talloc_free(mem_ctx);
174 :
175 8 : return result;
176 : }
177 :
178 : static const char py_net_join_member_doc[] = "join_member(dnshostname, createupn, createcomputer, osName, osVer, osServicePack, machinepass) -> (domain_sid, domain_name)\n\n" \
179 : "Join the domain with the specified name.";
180 :
181 2 : static PyObject *py_net_leave(py_net_Object *self, PyObject *args, PyObject *kwargs)
182 : {
183 2 : struct libnet_UnjoinCtx *r = NULL;
184 0 : WERROR werr;
185 0 : TALLOC_CTX *mem_ctx;
186 2 : int keep_account = false, debug = false;
187 2 : const char *kwnames[] = { "keepAccount", "debug", NULL };
188 :
189 2 : mem_ctx = talloc_new(self->mem_ctx);
190 2 : if (mem_ctx == NULL) {
191 0 : PyErr_NoMemory();
192 0 : return NULL;
193 : }
194 :
195 2 : if (!*lpcfg_realm(self->lp_ctx)) {
196 0 : PyErr_FromString(_("No realm set, are we joined ?\n"));
197 0 : return NULL;
198 : }
199 :
200 2 : werr = libnet_init_UnjoinCtx(mem_ctx, &r);
201 2 : if (!W_ERROR_IS_OK(werr)) {
202 0 : PyErr_SetWERROR_and_string(werr,
203 : _("Could not initialise unjoin context.\n"));
204 0 : return NULL;
205 : }
206 :
207 2 : if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|pp:Leave",
208 : discard_const_p(char *, kwnames),
209 : &keep_account, &debug)) {
210 0 : talloc_free(mem_ctx);
211 0 : PyErr_FromString(_("Invalid arguments\n"));
212 0 : return NULL;
213 : }
214 :
215 2 : r->in.dc_name = self->server_address;
216 2 : r->in.domain_name = lpcfg_realm(self->lp_ctx);
217 2 : r->in.admin_credentials = self->creds;
218 2 : r->in.modify_config = lp_config_backend_is_registry();
219 2 : r->in.debug = debug;
220 :
221 : /*
222 : * Try to delete it, but if that fails, disable it. The
223 : * WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE really means "disable"
224 : */
225 2 : r->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
226 : WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE;
227 2 : if (keep_account) {
228 0 : r->in.delete_machine_account = false;
229 : } else {
230 2 : r->in.delete_machine_account = true;
231 : }
232 :
233 2 : r->in.msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
234 :
235 2 : werr = libnet_Unjoin(mem_ctx, r);
236 2 : if (!W_ERROR_IS_OK(werr)) {
237 0 : PyErr_SetWERROR_and_string(werr,
238 : r->out.error_string
239 : ? r->out.error_string
240 : : get_friendly_werror_msg(werr));
241 0 : Py_RETURN_FALSE;
242 : }
243 :
244 2 : if (r->out.deleted_machine_account) {
245 2 : d_printf(_("Deleted account for '%s' in realm '%s'\n"),
246 2 : r->in.machine_name, r->out.dns_domain_name);
247 2 : Py_RETURN_TRUE;
248 : }
249 :
250 0 : if (r->out.disabled_machine_account) {
251 0 : d_printf(_("Disabled account for '%s' in realm '%s'\n"),
252 0 : r->in.machine_name, r->out.dns_domain_name);
253 0 : werr = WERR_OK;
254 0 : Py_RETURN_TRUE;
255 : }
256 :
257 : /*
258 : * Based on what we requested, we shouldn't get here, but if
259 : * we did, it means the secrets were removed, and therefore
260 : * we have left the domain.
261 : */
262 0 : d_fprintf(stderr, _("Machine '%s' Left domain '%s'\n"),
263 0 : r->in.machine_name, r->out.dns_domain_name);
264 0 : Py_RETURN_TRUE;
265 : }
266 :
267 : static const char py_net_leave_doc[] = "leave(keepAccount) -> success\n\n" \
268 : "Leave the joined domain.";
269 :
270 : static PyMethodDef net_obj_methods[] = {
271 : {
272 : .ml_name = "join_member",
273 : .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction,
274 : py_net_join_member),
275 : .ml_flags = METH_VARARGS|METH_KEYWORDS,
276 : .ml_doc = py_net_join_member_doc
277 : },
278 : {
279 : .ml_name = "leave",
280 : .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction,
281 : py_net_leave),
282 : .ml_flags = METH_VARARGS|METH_KEYWORDS,
283 : .ml_doc = py_net_leave_doc
284 : },
285 : { .ml_name = NULL }
286 : };
287 :
288 8 : static void py_net_dealloc(py_net_Object *self)
289 : {
290 8 : talloc_free(self->mem_ctx);
291 8 : PyObject_Del(self);
292 8 : }
293 :
294 8 : static PyObject *net_obj_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
295 : {
296 8 : PyObject *py_creds, *py_lp = Py_None;
297 8 : const char *kwnames[] = { "creds", "lp", "server", NULL };
298 0 : py_net_Object *ret;
299 8 : const char *server_address = NULL;
300 :
301 8 : if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Oz",
302 : discard_const_p(char *, kwnames), &py_creds, &py_lp,
303 : &server_address)) {
304 0 : PyErr_FromString(_("Invalid arguments\n"));
305 0 : return NULL;
306 : }
307 :
308 8 : ret = PyObject_New(py_net_Object, type);
309 8 : if (ret == NULL) {
310 0 : return NULL;
311 : }
312 :
313 8 : ret->ev = samba_tevent_context_init(NULL);
314 8 : ret->mem_ctx = talloc_stackframe();
315 :
316 8 : ret->lp_ctx = lpcfg_from_py_object(ret->mem_ctx, py_lp);
317 8 : if (ret->lp_ctx == NULL) {
318 0 : Py_DECREF(ret);
319 0 : return NULL;
320 : }
321 :
322 8 : ret->server_address = server_address;
323 :
324 8 : ret->creds = cli_credentials_from_py_object(py_creds);
325 8 : if (ret->creds == NULL) {
326 0 : PyErr_SetString(PyExc_TypeError, "Expected credentials object");
327 0 : Py_DECREF(ret);
328 0 : return NULL;
329 : }
330 :
331 8 : return (PyObject *)ret;
332 : }
333 :
334 :
335 : PyTypeObject py_net_Type = {
336 : PyVarObject_HEAD_INIT(NULL, 0)
337 : .tp_name = "net_s3.Net",
338 : .tp_basicsize = sizeof(py_net_Object),
339 : .tp_dealloc = (destructor)py_net_dealloc,
340 : .tp_methods = net_obj_methods,
341 : .tp_new = net_obj_new,
342 : };
343 :
344 : static struct PyModuleDef moduledef = {
345 : PyModuleDef_HEAD_INIT,
346 : .m_name = "net",
347 : .m_size = -1,
348 : };
349 :
350 788 : MODULE_INIT_FUNC(net_s3)
351 : {
352 34 : PyObject *m;
353 :
354 788 : if (PyType_Ready(&py_net_Type) < 0)
355 0 : return NULL;
356 :
357 788 : m = PyModule_Create(&moduledef);
358 788 : if (m == NULL)
359 0 : return NULL;
360 :
361 592 : Py_INCREF(&py_net_Type);
362 788 : PyModule_AddObject(m, "Net", (PyObject *)&py_net_Type);
363 :
364 788 : return m;
365 : }
|