9f252e51e90875a86ffe1c7f5985364caa286ab7
[metze/samba/wip.git] / source3 / utils / net_offlinejoin.c
1 /*
2    Samba Unix/Linux SMB client library
3    net join commands
4    Copyright (C) 2021 Guenther Deschner (gd@samba.org)
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "utils/net.h"
22 #include <netapi.h>
23 #include "netapi/netapi_net.h"
24 #include "libcli/registry/util_reg.h"
25 #include "libcli/security/dom_sid.h"
26 #include "lib/cmdline/cmdline.h"
27
28 int net_offlinejoin_usage(struct net_context *c, int argc, const char **argv)
29 {
30         d_printf(_("\nnet offlinejoin [misc. options]\n"
31                    "\tjoins a computer to a domain\n"));
32         d_printf(_("Valid commands:\n"));
33         d_printf(_("\tprovision\t\t\tProvision machine account in AD\n"));
34         d_printf(_("\trequestodj\t\t\tRequest offline domain join\n"));
35         d_printf(_("\tcomposeodj\t\t\tCompose offline domain join blob\n"));
36         net_common_flags_usage(c, argc, argv);
37         return -1;
38 }
39
40 int net_offlinejoin(struct net_context *c, int argc, const char **argv)
41 {
42         int ret;
43         NET_API_STATUS status;
44
45         if ((argc > 0) && (strcasecmp_m(argv[0], "HELP") == 0)) {
46                 net_offlinejoin_usage(c, argc, argv);
47                 return 0;
48         }
49
50         if (argc == 0) {
51                 net_offlinejoin_usage(c, argc, argv);
52                 return -1;
53         }
54
55         net_warn_member_options();
56
57         status = libnetapi_net_init(&c->netapi_ctx);
58         if (status != 0) {
59                 return -1;
60         }
61
62         status = libnetapi_set_creds(c->netapi_ctx, c->creds);
63         if (status != 0) {
64                 return -1;
65         }
66
67         if (c->opt_kerberos) {
68                 libnetapi_set_use_kerberos(c->netapi_ctx);
69         }
70
71         if (strcasecmp_m(argv[0], "provision") == 0) {
72                 ret = net_offlinejoin_provision(c, argc, argv);
73                 if (ret != 0) {
74                         return ret;
75                 }
76         }
77
78         if (strcasecmp_m(argv[0], "requestodj") == 0) {
79                 ret = net_offlinejoin_requestodj(c, argc, argv);
80                 if (ret != 0) {
81                         return ret;
82                 }
83         }
84
85         if (strcasecmp_m(argv[0], "composeodj") == 0) {
86                 ret = net_offlinejoin_composeodj(c, argc, argv);
87                 if (ret != 0) {
88                         return ret;
89                 }
90         }
91
92         return 0;
93 }
94
95 static int net_offlinejoin_provision_usage(struct net_context *c, int argc, const char **argv)
96 {
97         d_printf(_("\nnet offlinejoin provision [misc. options]\n"
98                    "\tProvisions machine account in AD\n"));
99         d_printf(_("Valid options:\n"));
100         d_printf(_("\tdomain=<DOMAIN>\t\t\t\tDefines AD Domain to join\n"));
101         d_printf(_("\tmachine_name=<MACHINE_NAME>\t\tDefines the machine account name\n"));
102         d_printf(_("\tmachine_account_ou=<OU>\t\t\tDefines the machine account organizational unit DN\n"));
103         d_printf(_("\tdcname=<DCNAME>\t\t\t\tSpecifices a Domain Controller to join to\n"));
104         d_printf(_("\tdefpwd\t\t\t\t\tUse default machine account password\n"));
105         d_printf(_("\treuse\t\t\t\t\tReuse existing machine account in AD\n"));
106         d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
107         d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
108         net_common_flags_usage(c, argc, argv);
109         return -1;
110 }
111
112 int net_offlinejoin_provision(struct net_context *c,
113                               int argc, const char **argv)
114 {
115         NET_API_STATUS status;
116         const char *dcname = NULL;
117         const char *domain = NULL;
118         const char *machine_name = NULL;
119         const char *machine_account_ou = NULL;
120         const char *provision_text_data = NULL;
121         uint32_t options = 0;
122         const char *savefile = NULL;
123         bool printblob = false;
124         int i;
125
126         if (c->display_usage || argc == 1) {
127                 return net_offlinejoin_provision_usage(c, argc, argv);
128         }
129
130         /* process additional command line args */
131
132         for (i = 0; i < argc; i++) {
133
134                 if (strnequal(argv[i], "domain", strlen("domain"))) {
135                         domain = get_string_param(argv[i]);
136                         if (domain == NULL) {
137                                 return -1;
138                         }
139                 }
140                 if (strnequal(argv[i], "machine_name", strlen("machine_name"))) {
141                         machine_name = get_string_param(argv[i]);
142                         if (machine_name == NULL) {
143                                 return -1;
144                         }
145                 }
146                 if (strnequal(argv[i], "machine_account_ou", strlen("machine_account_ou"))) {
147                         machine_account_ou = get_string_param(argv[i]);
148                         if (machine_account_ou == NULL) {
149                                 return -1;
150                         }
151                 }
152                 if (strnequal(argv[i], "dcname", strlen("dcname"))) {
153                         dcname = get_string_param(argv[i]);
154                         if (dcname == NULL) {
155                                 return -1;
156                         }
157                 }
158                 if (strnequal(argv[i], "defpwd", strlen("defpwd"))) {
159                         options |= NETSETUP_PROVISION_USE_DEFAULT_PASSWORD;
160                 }
161                 if (strnequal(argv[i], "reuse", strlen("reuse"))) {
162                         options |= NETSETUP_PROVISION_REUSE_ACCOUNT;
163                 }
164                 if (strnequal(argv[i], "savefile", strlen("savefile"))) {
165                         savefile = get_string_param(argv[i]);
166                         if (savefile == NULL) {
167                                 return -1;
168                         }
169                 }
170                 if (strnequal(argv[i], "printblob", strlen("printblob"))) {
171                         printblob = true;
172                 }
173         }
174
175         if (domain == NULL) {
176                 d_printf("Failed to provision computer account: %s\n",
177                          libnetapi_errstr(W_ERROR_V(WERR_INVALID_DOMAINNAME)));
178                 return -1;
179         }
180
181         if (machine_name == NULL) {
182                 d_printf("Failed to provision computer account: %s\n",
183                          libnetapi_errstr(W_ERROR_V(WERR_INVALID_COMPUTERNAME)));
184                 return -1;
185         }
186
187         status = NetProvisionComputerAccount(domain,
188                                              machine_name,
189                                              machine_account_ou,
190                                              dcname,
191                                              options,
192                                              NULL,
193                                              0,
194                                              &provision_text_data);
195         if (status != 0) {
196                 d_printf("Failed to provision computer account: %s\n",
197                         libnetapi_get_error_string(c->netapi_ctx, status));
198                 return status;
199         }
200
201         if (savefile != NULL) {
202
203                 DATA_BLOB ucs2_blob, blob;
204                 bool ok;
205
206                 /*
207                  * Windows produces and consumes UTF16/UCS2 encoded blobs
208                  * so we also do it for compatibility. Someone may provision an
209                  * account for a Windows machine with samba.
210                  */
211                 ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
212                 if (!ok) {
213                         return -1;
214                 }
215
216                 /* Add the unicode BOM mark */
217                 blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
218
219                 blob.data[0] = 0xff;
220                 blob.data[1] = 0xfe;
221
222                 memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
223
224                 ok = file_save(savefile, blob.data, blob.length);
225                 if (!ok) {
226                         d_printf("Failed to save %s: %s\n", savefile,
227                                         strerror(errno));
228                         return -1;
229                 }
230         }
231
232         d_printf("Successfully provisioned computer '%s' in domain '%s'\n",
233                         machine_name, domain);
234
235         if (printblob) {
236                 printf("%s\n", provision_text_data);
237         }
238
239         return 0;
240 }
241
242 static int net_offlinejoin_requestodj_usage(struct net_context *c, int argc, const char **argv)
243 {
244         d_printf(_("\nnet offlinejoin requestodj [misc. options]\n"
245                    "\tRequests offline domain join\n"));
246         d_printf(_("Valid options:\n"));
247         d_printf(_("\tloadfile=<FILENAME>\t\t\tFile that provides the ODJ data\n"));
248         /*d_printf(_("\tlocalos\t\t\t\t\tModify the local machine\n"));*/
249         net_common_flags_usage(c, argc, argv);
250         return -1;
251 }
252
253 int net_offlinejoin_requestodj(struct net_context *c,
254                                int argc, const char **argv)
255 {
256         NET_API_STATUS status;
257         uint8_t *provision_bin_data = NULL;
258         size_t provision_bin_data_size = 0;
259         uint32_t options = NETSETUP_PROVISION_ONLINE_CALLER;
260         const char *loadfile = NULL;
261         const char *windows_path = NULL;
262         int i;
263
264         if (c->display_usage || argc == 1) {
265                 return net_offlinejoin_requestodj_usage(c, argc, argv);
266         }
267
268         /* process additional command line args */
269
270         for (i = 0; i < argc; i++) {
271
272                 if (strnequal(argv[i], "loadfile", strlen("loadfile"))) {
273                         loadfile = get_string_param(argv[i]);
274                         if (loadfile == NULL) {
275                                 return -1;
276                         }
277                 }
278 #if 0
279                 if (strnequal(argv[i], "localos", strlen("localos"))) {
280                         options |= NETSETUP_PROVISION_ONLINE_CALLER;
281                 }
282 #endif
283         }
284
285         provision_bin_data =
286                 (uint8_t *)file_load(loadfile, &provision_bin_data_size, 0, c);
287         if (provision_bin_data == NULL) {
288                 d_printf("Failed to read loadfile: %s\n", loadfile);
289                 return -1;
290         }
291         if (provision_bin_data_size > UINT32_MAX) {
292                 d_printf("provision binary data size too big: %zu\n",
293                          provision_bin_data_size);
294                 return -1;
295         }
296
297         status = NetRequestOfflineDomainJoin(provision_bin_data,
298                                              provision_bin_data_size,
299                                              options,
300                                              windows_path);
301         if (status != 0 && status != 0x00000a99) {
302                 /* NERR_JoinPerformedMustRestart */
303                 printf("Failed to call NetRequestOfflineDomainJoin: %s\n",
304                         libnetapi_get_error_string(c->netapi_ctx, status));
305                 return -1;
306         }
307
308         d_printf("Successfully requested Offline Domain Join\n");
309
310         return 0;
311 }
312
313 static int net_offlinejoin_composeodj_usage(struct net_context *c,
314                                             int argc,
315                                             const char **argv)
316 {
317         d_printf(_("\nnet offlinejoin composeodj [misc. options]\n"
318                    "\tComposes offline domain join blob\n"));
319         d_printf(_("Valid options:\n"));
320         d_printf(_("\tdomain_sid=<SID>\t\t\tThe domain SID\n"));
321         d_printf(_("\tdomain_guid=<GUID>\t\t\tThe domain GUID\n"));
322         d_printf(_("\tforest_name=<NAME>\t\t\tThe forest name\n"));
323         d_printf(_("\tdomain_is_nt4\t\t\t\tThe domain not AD but NT4\n"));
324         d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
325         d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
326         net_common_flags_usage(c, argc, argv);
327         d_printf(_("Example:\n"));
328         d_printf("\tnet offlinejoin composeodj --realm=<realm> "
329                  "--workgroup=<domain> domain_sid=<sid> domain_guid=<guid> "
330                  "forest_name=<name> -S <dc name> -I <dc address> "
331                  "--password=<password> printblob\n");
332         return -1;
333 }
334
335 int net_offlinejoin_composeodj(struct net_context *c,
336                                int argc,
337                                const char **argv)
338 {
339         struct cli_credentials *creds = samba_cmdline_get_creds();
340         NET_API_STATUS status;
341         const char *dns_domain_name = NULL;
342         const char *netbios_domain_name = NULL;
343         const char *machine_account_name = NULL;
344         const char *machine_account_password = NULL;
345         const char *domain_sid_str = NULL;
346         const char *domain_guid_str = NULL;
347         struct dom_sid domain_sid;
348         struct GUID domain_guid;
349         const char *forest_name = NULL;
350         const char *dc_name = NULL;
351         char dc_address[INET6_ADDRSTRLEN] = { 0 };
352         bool domain_is_ad = true;
353         const char *provision_text_data = NULL;
354         const char *savefile = NULL;
355         bool printblob = false;
356         enum credentials_obtained obtained;
357         bool ok;
358         NTSTATUS ntstatus;
359         int i;
360
361         if (c->display_usage || argc < 4) {
362                 return net_offlinejoin_composeodj_usage(c, argc, argv);
363         }
364
365         dns_domain_name = cli_credentials_get_realm(creds);
366         netbios_domain_name = cli_credentials_get_domain(creds);
367
368         machine_account_name = cli_credentials_get_username_and_obtained(creds, &obtained);
369         if (obtained < CRED_CALLBACK_RESULT) {
370                 const char *netbios_name = cli_credentials_get_workstation(creds);
371                 cli_credentials_set_username(
372                         creds,
373                         talloc_asprintf(c, "%s$", netbios_name),
374                         CRED_SPECIFIED);
375         }
376
377         machine_account_name = cli_credentials_get_username(creds);
378         machine_account_password = cli_credentials_get_password(creds);
379         dc_name = c->opt_host;
380
381         if (c->opt_have_ip) {
382                 struct sockaddr_in *in4 = NULL;
383                 struct sockaddr_in6 *in6 = NULL;
384                 const char *p = NULL;
385
386                 switch(c->opt_dest_ip.ss_family) {
387                 case AF_INET:
388                         in4 = (struct sockaddr_in *)&c->opt_dest_ip;
389                         p = inet_ntop(AF_INET, &in4->sin_addr, dc_address, sizeof(dc_address));
390                         break;
391                 case AF_INET6:
392                         in6 = (struct sockaddr_in6 *)&c->opt_dest_ip;
393                         p = inet_ntop(AF_INET6, &in6->sin6_addr, dc_address, sizeof(dc_address));
394                         break;
395                 default:
396                         d_printf("Unknown IP address family\n");
397                         return -1;
398                 }
399
400                 if (p == NULL) {
401                         d_fprintf(stderr, "Failed to parse IP address: %s\n", strerror(errno));
402                         return -1;
403                 }
404         }
405
406         /* process additional command line args */
407
408         for (i = 0; i < argc; i++) {
409                 if (strnequal(argv[i], "domain_sid", strlen("domain_sid"))) {
410                         domain_sid_str = get_string_param(argv[i]);
411                         if (domain_sid_str == NULL) {
412                                 return -1;
413                         }
414                 }
415
416                 if (strnequal(argv[i], "domain_guid", strlen("domain_guid"))) {
417                         domain_guid_str = get_string_param(argv[i]);
418                         if (domain_guid_str == NULL) {
419                                 return -1;
420                         }
421                 }
422
423                 if (strnequal(argv[i], "forest_name", strlen("forest_name"))) {
424                         forest_name = get_string_param(argv[i]);
425                         if (forest_name == NULL) {
426                                 return -1;
427                         }
428                 }
429
430                 if (strnequal(argv[i], "savefile", strlen("savefile"))) {
431                         savefile = get_string_param(argv[i]);
432                         if (savefile == NULL) {
433                                 return -1;
434                         }
435                 }
436
437                 if (strnequal(argv[i], "printblob", strlen("printblob"))) {
438                         printblob = true;
439                 }
440
441                 if (strnequal(argv[i], "domain_is_nt4", strlen("domain_is_nt4"))) {
442                         domain_is_ad = false;
443                 }
444         }
445
446         /* Check command line arguments */
447
448         if (savefile == NULL && !printblob) {
449                 d_printf("Choose either save the blob to a file or print it\n");
450                 return -1;
451         }
452
453         if (dns_domain_name == NULL) {
454                 d_printf("Please provide a valid realm parameter (--realm)\n");
455                 return -1;
456         }
457
458         if (netbios_domain_name == NULL) {
459                 d_printf("Please provide a valid domain parameter (--workgroup)\n");
460                 return -1;
461         }
462
463         if (dc_name == NULL) {
464                 d_printf("Please provide a valid DC name parameter (-S)\n");
465                 return -1;
466         }
467
468         if (strlen(dc_address) == 0) {
469                 d_printf("Please provide a valid domain controller address parameter (-I)\n");
470                 return -1;
471         }
472
473         if (machine_account_name == NULL) {
474                 d_printf("Please provide a valid netbios name parameter\n");
475                 return -1;
476         }
477
478         if (machine_account_password == NULL) {
479                 d_printf("Please provide a valid password parameter\n");
480                 return -1;
481         }
482
483         if (domain_sid_str == NULL) {
484                 d_printf("Please provide a valid <domain_sid> parameter\n");
485                 return -1;
486         }
487
488         if (domain_guid_str == NULL) {
489                 d_printf("Please provide a valid <domain_guid> parameter\n");
490                 return -1;
491         }
492
493         if (forest_name == NULL) {
494                 d_printf("Please provide a valid <forest_name> parameter\n");
495                 return -1;
496         }
497
498         ok = dom_sid_parse(domain_sid_str, &domain_sid);
499         if (!ok) {
500                 d_fprintf(stderr, _("Failed to parse domain SID\n"));
501                 return -1;
502         }
503
504         ntstatus = GUID_from_string(domain_guid_str, &domain_guid);
505         if (NT_STATUS_IS_ERR(ntstatus)) {
506                 d_fprintf(stderr, _("Failed to parse domain GUID\n"));
507                 return -1;
508         }
509
510         status = NetComposeOfflineDomainJoin(dns_domain_name,
511                                              netbios_domain_name,
512                                              (struct domsid *)&domain_sid,
513                                              &domain_guid,
514                                              forest_name,
515                                              machine_account_name,
516                                              machine_account_password,
517                                              dc_name,
518                                              dc_address,
519                                              domain_is_ad,
520                                              NULL,
521                                              0,
522                                              &provision_text_data);
523         if (status != 0) {
524                 d_printf("Failed to compose offline domain join blob: %s\n",
525                         libnetapi_get_error_string(c->netapi_ctx, status));
526                 return status;
527         }
528
529         if (savefile != NULL) {
530                 DATA_BLOB ucs2_blob, blob;
531
532                 /*
533                  * Windows produces and consumes UTF16/UCS2 encoded blobs
534                  * so we also do it for compatibility. Someone may provision an
535                  * account for a Windows machine with samba.
536                  */
537                 ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
538                 if (!ok) {
539                         return -1;
540                 }
541
542                 /* Add the unicode BOM mark */
543                 blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
544
545                 blob.data[0] = 0xff;
546                 blob.data[1] = 0xfe;
547
548                 memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
549
550                 ok = file_save(savefile, blob.data, blob.length);
551                 if (!ok) {
552                         d_printf("Failed to save %s: %s\n", savefile,
553                                         strerror(errno));
554                         return -1;
555                 }
556         }
557
558         if (printblob) {
559                 printf("%s\n", provision_text_data);
560         }
561
562         return 0;
563 }