s3:net: Allow to load ODJ blob from stdin
[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\tSpecify 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(_("\t-i\t\t\t\t\tRead ODJ data from STDIN\n"));
248         d_printf(_("\tloadfile=<FILENAME>\t\t\tFile that provides the ODJ data\n"));
249         /*d_printf(_("\tlocalos\t\t\t\t\tModify the local machine\n"));*/
250         net_common_flags_usage(c, argc, argv);
251         return -1;
252 }
253
254 int net_offlinejoin_requestodj(struct net_context *c,
255                                int argc, const char **argv)
256 {
257         NET_API_STATUS status;
258         uint8_t *provision_bin_data = NULL;
259         size_t provision_bin_data_size = 0;
260         uint32_t options = NETSETUP_PROVISION_ONLINE_CALLER;
261         const char *windows_path = NULL;
262         int i;
263
264         if (c->display_usage) {
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                         const char *loadfile = NULL;
274
275                         loadfile = get_string_param(argv[i]);
276                         if (loadfile == NULL) {
277                                 return -1;
278                         }
279
280                         provision_bin_data =
281                                 (uint8_t *)file_load(loadfile,
282                                                      &provision_bin_data_size,
283                                                      0,
284                                                      c);
285                         if (provision_bin_data == NULL) {
286                                 d_printf("Failed to read loadfile: %s\n",
287                                 loadfile);
288                                 return -1;
289                         }
290                 }
291 #if 0
292                 if (strnequal(argv[i], "localos", strlen("localos"))) {
293                         options |= NETSETUP_PROVISION_ONLINE_CALLER;
294                 }
295 #endif
296         }
297
298         if (c->opt_stdin) {
299                 if (isatty(STDIN_FILENO) == 1) {
300                         d_fprintf(stderr,
301                                   "hint: stdin waiting for ODJ blob, end "
302                                   "with <crtl-D>.\n");
303                 }
304                 provision_bin_data =
305                         (uint8_t *)fd_load(STDIN_FILENO,
306                                            &provision_bin_data_size, 0, c);
307                 if (provision_bin_data == NULL) {
308                         d_printf("Failed to read ODJ blob from stdin\n");
309                         return -1;
310                 }
311
312                 /* Strip last newline */
313                 if (provision_bin_data[provision_bin_data_size - 1] == '\n') {
314                         provision_bin_data[provision_bin_data_size - 1] = '\0';
315                 }
316         }
317
318         if (provision_bin_data == NULL || provision_bin_data_size == 0) {
319                 d_printf("Please provide provision data either from file "
320                          "(using loadfile parameter) of from stdin (-i)\n");
321                 return -1;
322         }
323         if (provision_bin_data_size > UINT32_MAX) {
324                 d_printf("provision binary data size too big: %zu\n",
325                          provision_bin_data_size);
326                 return -1;
327         }
328
329         status = NetRequestOfflineDomainJoin(provision_bin_data,
330                                              provision_bin_data_size,
331                                              options,
332                                              windows_path);
333         if (status != 0 && status != 0x00000a99) {
334                 /* NERR_JoinPerformedMustRestart */
335                 printf("Failed to call NetRequestOfflineDomainJoin: %s\n",
336                         libnetapi_get_error_string(c->netapi_ctx, status));
337                 return -1;
338         }
339
340         d_printf("Successfully requested Offline Domain Join\n");
341
342         return 0;
343 }
344
345 static int net_offlinejoin_composeodj_usage(struct net_context *c,
346                                             int argc,
347                                             const char **argv)
348 {
349         d_printf(_("\nnet offlinejoin composeodj [misc. options]\n"
350                    "\tComposes offline domain join blob\n"));
351         d_printf(_("Valid options:\n"));
352         d_printf(_("\tdomain_sid=<SID>\t\t\tThe domain SID\n"));
353         d_printf(_("\tdomain_guid=<GUID>\t\t\tThe domain GUID\n"));
354         d_printf(_("\tforest_name=<NAME>\t\t\tThe forest name\n"));
355         d_printf(_("\tdomain_is_nt4\t\t\t\tThe domain not AD but NT4\n"));
356         d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
357         d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
358         net_common_flags_usage(c, argc, argv);
359         d_printf(_("Example:\n"));
360         d_printf("\tnet offlinejoin composeodj --realm=<realm> "
361                  "--workgroup=<domain> domain_sid=<sid> domain_guid=<guid> "
362                  "forest_name=<name> -S <dc name> -I <dc address> "
363                  "--password=<password> printblob\n");
364         return -1;
365 }
366
367 int net_offlinejoin_composeodj(struct net_context *c,
368                                int argc,
369                                const char **argv)
370 {
371         struct cli_credentials *creds = samba_cmdline_get_creds();
372         NET_API_STATUS status;
373         const char *dns_domain_name = NULL;
374         const char *netbios_domain_name = NULL;
375         const char *machine_account_name = NULL;
376         const char *machine_account_password = NULL;
377         const char *domain_sid_str = NULL;
378         const char *domain_guid_str = NULL;
379         struct dom_sid domain_sid;
380         struct GUID domain_guid;
381         const char *forest_name = NULL;
382         const char *dc_name = NULL;
383         char dc_address[INET6_ADDRSTRLEN] = { 0 };
384         bool domain_is_ad = true;
385         const char *provision_text_data = NULL;
386         const char *savefile = NULL;
387         bool printblob = false;
388         enum credentials_obtained obtained;
389         bool ok;
390         NTSTATUS ntstatus;
391         int i;
392
393         if (c->display_usage || argc < 4) {
394                 return net_offlinejoin_composeodj_usage(c, argc, argv);
395         }
396
397         dns_domain_name = cli_credentials_get_realm(creds);
398         netbios_domain_name = cli_credentials_get_domain(creds);
399
400         machine_account_name = cli_credentials_get_username_and_obtained(creds, &obtained);
401         if (obtained < CRED_CALLBACK_RESULT) {
402                 const char *netbios_name = cli_credentials_get_workstation(creds);
403                 cli_credentials_set_username(
404                         creds,
405                         talloc_asprintf(c, "%s$", netbios_name),
406                         CRED_SPECIFIED);
407         }
408
409         machine_account_name = cli_credentials_get_username(creds);
410         machine_account_password = cli_credentials_get_password(creds);
411         dc_name = c->opt_host;
412
413         if (c->opt_have_ip) {
414                 struct sockaddr_in *in4 = NULL;
415                 struct sockaddr_in6 *in6 = NULL;
416                 const char *p = NULL;
417
418                 switch(c->opt_dest_ip.ss_family) {
419                 case AF_INET:
420                         in4 = (struct sockaddr_in *)&c->opt_dest_ip;
421                         p = inet_ntop(AF_INET, &in4->sin_addr, dc_address, sizeof(dc_address));
422                         break;
423                 case AF_INET6:
424                         in6 = (struct sockaddr_in6 *)&c->opt_dest_ip;
425                         p = inet_ntop(AF_INET6, &in6->sin6_addr, dc_address, sizeof(dc_address));
426                         break;
427                 default:
428                         d_printf("Unknown IP address family\n");
429                         return -1;
430                 }
431
432                 if (p == NULL) {
433                         d_fprintf(stderr, "Failed to parse IP address: %s\n", strerror(errno));
434                         return -1;
435                 }
436         }
437
438         /* process additional command line args */
439
440         for (i = 0; i < argc; i++) {
441                 if (strnequal(argv[i], "domain_sid", strlen("domain_sid"))) {
442                         domain_sid_str = get_string_param(argv[i]);
443                         if (domain_sid_str == NULL) {
444                                 return -1;
445                         }
446                 }
447
448                 if (strnequal(argv[i], "domain_guid", strlen("domain_guid"))) {
449                         domain_guid_str = get_string_param(argv[i]);
450                         if (domain_guid_str == NULL) {
451                                 return -1;
452                         }
453                 }
454
455                 if (strnequal(argv[i], "forest_name", strlen("forest_name"))) {
456                         forest_name = get_string_param(argv[i]);
457                         if (forest_name == NULL) {
458                                 return -1;
459                         }
460                 }
461
462                 if (strnequal(argv[i], "savefile", strlen("savefile"))) {
463                         savefile = get_string_param(argv[i]);
464                         if (savefile == NULL) {
465                                 return -1;
466                         }
467                 }
468
469                 if (strnequal(argv[i], "printblob", strlen("printblob"))) {
470                         printblob = true;
471                 }
472
473                 if (strnequal(argv[i], "domain_is_nt4", strlen("domain_is_nt4"))) {
474                         domain_is_ad = false;
475                 }
476         }
477
478         /* Check command line arguments */
479
480         if (savefile == NULL && !printblob) {
481                 d_printf("Choose either save the blob to a file or print it\n");
482                 return -1;
483         }
484
485         if (dns_domain_name == NULL) {
486                 d_printf("Please provide a valid realm parameter (--realm)\n");
487                 return -1;
488         }
489
490         if (netbios_domain_name == NULL) {
491                 d_printf("Please provide a valid domain parameter (--workgroup)\n");
492                 return -1;
493         }
494
495         if (dc_name == NULL) {
496                 d_printf("Please provide a valid DC name parameter (-S)\n");
497                 return -1;
498         }
499
500         if (strlen(dc_address) == 0) {
501                 d_printf("Please provide a valid domain controller address parameter (-I)\n");
502                 return -1;
503         }
504
505         if (machine_account_name == NULL) {
506                 d_printf("Please provide a valid netbios name parameter\n");
507                 return -1;
508         }
509
510         if (machine_account_password == NULL) {
511                 d_printf("Please provide a valid password parameter\n");
512                 return -1;
513         }
514
515         if (domain_sid_str == NULL) {
516                 d_printf("Please provide a valid <domain_sid> parameter\n");
517                 return -1;
518         }
519
520         if (domain_guid_str == NULL) {
521                 d_printf("Please provide a valid <domain_guid> parameter\n");
522                 return -1;
523         }
524
525         if (forest_name == NULL) {
526                 d_printf("Please provide a valid <forest_name> parameter\n");
527                 return -1;
528         }
529
530         ok = dom_sid_parse(domain_sid_str, &domain_sid);
531         if (!ok) {
532                 d_fprintf(stderr, _("Failed to parse domain SID\n"));
533                 return -1;
534         }
535
536         ntstatus = GUID_from_string(domain_guid_str, &domain_guid);
537         if (NT_STATUS_IS_ERR(ntstatus)) {
538                 d_fprintf(stderr, _("Failed to parse domain GUID\n"));
539                 return -1;
540         }
541
542         status = NetComposeOfflineDomainJoin(dns_domain_name,
543                                              netbios_domain_name,
544                                              (struct domsid *)&domain_sid,
545                                              &domain_guid,
546                                              forest_name,
547                                              machine_account_name,
548                                              machine_account_password,
549                                              dc_name,
550                                              dc_address,
551                                              domain_is_ad,
552                                              NULL,
553                                              0,
554                                              &provision_text_data);
555         if (status != 0) {
556                 d_printf("Failed to compose offline domain join blob: %s\n",
557                         libnetapi_get_error_string(c->netapi_ctx, status));
558                 return status;
559         }
560
561         if (savefile != NULL) {
562                 DATA_BLOB ucs2_blob, blob;
563
564                 /*
565                  * Windows produces and consumes UTF16/UCS2 encoded blobs
566                  * so we also do it for compatibility. Someone may provision an
567                  * account for a Windows machine with samba.
568                  */
569                 ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
570                 if (!ok) {
571                         return -1;
572                 }
573
574                 /* Add the unicode BOM mark */
575                 blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
576
577                 blob.data[0] = 0xff;
578                 blob.data[1] = 0xfe;
579
580                 memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
581
582                 ok = file_save(savefile, blob.data, blob.length);
583                 if (!ok) {
584                         d_printf("Failed to save %s: %s\n", savefile,
585                                         strerror(errno));
586                         return -1;
587                 }
588         }
589
590         if (printblob) {
591                 printf("%s\n", provision_text_data);
592         }
593
594         return 0;
595 }