2 Samba Unix/Linux SMB client library
4 Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
5 Copyright (C) 2001 Remus Koos (remuskoos@yahoo.com)
6 Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "../utils/net.h"
28 int net_ads_usage(int argc, const char **argv)
31 "\nnet ads join <org_unit>"\
32 "\n\tjoins the local machine to a ADS realm\n"\
34 "\n\tremoves the local machine from a ADS realm\n"\
36 "\n\ttests that an exiting join is OK\n"\
38 "\n\tlist, add, or delete users in the realm\n"\
40 "\n\tlist, add, or delete groups in the realm\n"\
42 "\n\tshows some info on the server\n"\
44 "\n\tdump the machine account details to stdout\n"
45 "\nnet ads password <username@realm> -Uadmin_username@realm%%admin_pass"\
46 "\n\tchange a user's password using an admin account"\
47 "\n\t(note: use realm in UPPERCASE)\n"\
48 "\nnet ads chostpass"\
49 "\n\tchange the trust account password of this machine in the AD tree\n"\
50 "\nnet ads printer [info | publish | remove] <printername> <servername>"\
51 "\n\t lookup, add, or remove directory entry for a printer\n"\
53 "\n\tperform a raw LDAP search and dump the results\n"
60 this implements the CLDAP based netlogon lookup requests
61 for finding the domain controller of a ADS domain
63 static int net_ads_lookup(int argc, const char **argv)
67 ads = ads_init(NULL, NULL, opt_host);
69 ads->auth.flags |= ADS_AUTH_NO_BIND;
74 if (!ads || !ads->config.realm) {
75 d_printf("Didn't find the cldap server!\n");
79 return ads_cldap_netlogon(ads);
84 static int net_ads_info(int argc, const char **argv)
88 ads = ads_init(NULL, NULL, opt_host);
91 ads->auth.flags |= ADS_AUTH_NO_BIND;
96 if (!ads || !ads->config.realm) {
97 d_printf("Didn't find the ldap server!\n");
101 d_printf("LDAP server: %s\n", inet_ntoa(ads->ldap_ip));
102 d_printf("LDAP server name: %s\n", ads->config.ldap_server_name);
103 d_printf("Realm: %s\n", ads->config.realm);
104 d_printf("Bind Path: %s\n", ads->config.bind_path);
105 d_printf("LDAP port: %d\n", ads->ldap_port);
106 d_printf("Server time: %s\n", http_timestring(ads->config.current_time));
112 static ADS_STRUCT *ads_startup(void)
116 BOOL need_password = False;
117 BOOL second_time = False;
119 ads = ads_init(NULL, NULL, opt_host);
121 if (!opt_user_name) {
122 opt_user_name = "administrator";
125 if (opt_user_specified)
126 need_password = True;
129 if (!opt_password && need_password) {
131 asprintf(&prompt,"%s password: ", opt_user_name);
132 opt_password = getpass(prompt);
137 ads->auth.password = strdup(opt_password);
139 ads->auth.user_name = strdup(opt_user_name);
141 status = ads_connect(ads);
142 if (!ADS_ERR_OK(status)) {
143 if (!need_password && !second_time) {
144 need_password = True;
148 DEBUG(1,("ads_connect: %s\n", ads_errstr(status)));
157 Check to see if connection can be made via ads.
158 ads_startup() stores the password in opt_password if it needs to so
159 that rpc or rap can use it without re-prompting.
161 int net_ads_check(void)
173 determine the netbios workgroup name for a domain
175 static int net_ads_workgroup(int argc, const char **argv)
181 if (!(ads = ads_startup())) return -1;
183 if (!(ctx = talloc_init_named("net_ads_workgroup"))) {
187 if (!ADS_ERR_OK(ads_workgroup_name(ads, ctx, &workgroup))) {
188 d_printf("Failed to find workgroup for realm '%s'\n",
194 d_printf("Workgroup: %s\n", workgroup);
203 static BOOL usergrp_display(char *field, void **values, void *data_area)
205 char **disp_fields = (char **) data_area;
207 if (!field) { /* must be end of record */
208 if (!strchr_m(disp_fields[0], '$')) {
210 d_printf("%-21.21s %-50.50s\n",
211 disp_fields[0], disp_fields[1]);
213 d_printf("%s\n", disp_fields[0]);
215 SAFE_FREE(disp_fields[0]);
216 SAFE_FREE(disp_fields[1]);
219 if (!values) /* must be new field, indicate string field */
221 if (StrCaseCmp(field, "sAMAccountName") == 0) {
222 disp_fields[0] = strdup((char *) values[0]);
224 if (StrCaseCmp(field, "description") == 0)
225 disp_fields[1] = strdup((char *) values[0]);
229 static int net_ads_user_usage(int argc, const char **argv)
231 return net_help_user(argc, argv);
234 static int ads_user_add(int argc, const char **argv)
242 if (argc < 1) return net_ads_user_usage(argc, argv);
244 if (!(ads = ads_startup())) return -1;
246 status = ads_find_user_acct(ads, &res, argv[0]);
248 if (!ADS_ERR_OK(status)) {
249 d_printf("ads_user_add: %s\n", ads_errstr(status));
253 if (ads_count_replies(ads, res)) {
254 d_printf("ads_user_add: User %s already exists\n", argv[0]);
258 status = ads_add_user_acct(ads, argv[0], opt_comment);
260 if (!ADS_ERR_OK(status)) {
261 d_printf("Could not add user %s: %s\n", argv[0],
266 /* if no password is to be set, we're done */
268 d_printf("User %s added\n", argv[0]);
273 /* try setting the password */
274 asprintf(&upn, "%s@%s", argv[0], ads->config.realm);
275 status = krb5_set_password(ads->auth.kdc_server, upn, argv[1], ads->auth.time_offset);
277 if (ADS_ERR_OK(status)) {
278 d_printf("User %s added\n", argv[0]);
283 /* password didn't set, delete account */
284 d_printf("Could not add user %s. Error setting password %s\n",
285 argv[0], ads_errstr(status));
286 ads_msgfree(ads, res);
287 status=ads_find_user_acct(ads, &res, argv[0]);
288 if (ADS_ERR_OK(status)) {
289 userdn = ads_get_dn(ads, res);
290 ads_del_dn(ads, userdn);
291 ads_memfree(ads, userdn);
296 ads_msgfree(ads, res);
301 static int ads_user_info(int argc, const char **argv)
306 const char *attrs[] = {"memberOf", NULL};
307 char *searchstring=NULL;
310 if (argc < 1) return net_ads_user_usage(argc, argv);
312 if (!(ads = ads_startup())) return -1;
314 asprintf(&searchstring, "(sAMAccountName=%s)", argv[0]);
315 rc = ads_search(ads, &res, searchstring, attrs);
316 safe_free(searchstring);
318 if (!ADS_ERR_OK(rc)) {
319 d_printf("ads_search: %s\n", ads_errstr(rc));
323 grouplist = ldap_get_values(ads->ld, res, "memberOf");
328 for (i=0;grouplist[i];i++) {
329 groupname = ldap_explode_dn(grouplist[i], 1);
330 d_printf("%s\n", groupname[0]);
331 ldap_value_free(groupname);
333 ldap_value_free(grouplist);
336 ads_msgfree(ads, res);
342 static int ads_user_delete(int argc, const char **argv)
349 if (argc < 1) return net_ads_user_usage(argc, argv);
351 if (!(ads = ads_startup())) return -1;
353 rc = ads_find_user_acct(ads, &res, argv[0]);
354 if (!ADS_ERR_OK(rc)) {
355 DEBUG(0, ("User %s does not exist\n", argv[0]));
358 userdn = ads_get_dn(ads, res);
359 ads_msgfree(ads, res);
360 rc = ads_del_dn(ads, userdn);
361 ads_memfree(ads, userdn);
362 if (!ADS_ERR_OK(rc)) {
363 d_printf("User %s deleted\n", argv[0]);
366 d_printf("Error deleting user %s: %s\n", argv[0],
371 int net_ads_user(int argc, const char **argv)
373 struct functable func[] = {
374 {"ADD", ads_user_add},
375 {"INFO", ads_user_info},
376 {"DELETE", ads_user_delete},
381 const char *shortattrs[] = {"sAMAccountName", NULL};
382 const char *longattrs[] = {"sAMAccountName", "description", NULL};
383 char *disp_fields[2] = {NULL, NULL};
386 if (!(ads = ads_startup())) return -1;
388 if (opt_long_list_entries)
389 d_printf("\nUser name Comment"\
390 "\n-----------------------------\n");
392 rc = ads_do_search_all_fn(ads, ads->config.bind_path,
394 "(objectclass=user)",
395 opt_long_list_entries ? longattrs :
396 shortattrs, usergrp_display,
402 return net_run_function(argc, argv, func, net_ads_user_usage);
405 static int net_ads_group_usage(int argc, const char **argv)
407 return net_help_group(argc, argv);
410 static int ads_group_add(int argc, const char **argv)
417 if (argc < 1) return net_ads_group_usage(argc, argv);
419 if (!(ads = ads_startup())) return -1;
421 status = ads_find_user_acct(ads, &res, argv[0]);
423 if (!ADS_ERR_OK(status)) {
424 d_printf("ads_group_add: %s\n", ads_errstr(status));
428 if (ads_count_replies(ads, res)) {
429 d_printf("ads_group_add: Group %s already exists\n", argv[0]);
430 ads_msgfree(ads, res);
434 status = ads_add_group_acct(ads, argv[0], opt_comment);
436 if (ADS_ERR_OK(status)) {
437 d_printf("Group %s added\n", argv[0]);
440 d_printf("Could not add group %s: %s\n", argv[0],
446 ads_msgfree(ads, res);
451 static int ads_group_delete(int argc, const char **argv)
458 if (argc < 1) return net_ads_group_usage(argc, argv);
460 if (!(ads = ads_startup())) return -1;
462 rc = ads_find_user_acct(ads, &res, argv[0]);
463 if (!ADS_ERR_OK(rc)) {
464 DEBUG(0, ("Group %s does not exist\n", argv[0]));
467 groupdn = ads_get_dn(ads, res);
468 ads_msgfree(ads, res);
469 rc = ads_del_dn(ads, groupdn);
470 ads_memfree(ads, groupdn);
471 if (!ADS_ERR_OK(rc)) {
472 d_printf("Group %s deleted\n", argv[0]);
475 d_printf("Error deleting group %s: %s\n", argv[0],
480 int net_ads_group(int argc, const char **argv)
482 struct functable func[] = {
483 {"ADD", ads_group_add},
484 {"DELETE", ads_group_delete},
489 const char *shortattrs[] = {"sAMAccountName", NULL};
490 const char *longattrs[] = {"sAMAccountName", "description", NULL};
491 char *disp_fields[2] = {NULL, NULL};
494 if (!(ads = ads_startup())) return -1;
496 if (opt_long_list_entries)
497 d_printf("\nGroup name Comment"\
498 "\n-----------------------------\n");
499 rc = ads_do_search_all_fn(ads, ads->config.bind_path,
501 "(objectclass=group)",
502 opt_long_list_entries ? longattrs :
503 shortattrs, usergrp_display,
509 return net_run_function(argc, argv, func, net_ads_group_usage);
512 static int net_ads_status(int argc, const char **argv)
516 extern pstring global_myname;
519 if (!(ads = ads_startup())) return -1;
521 rc = ads_find_machine_acct(ads, &res, global_myname);
522 if (!ADS_ERR_OK(rc)) {
523 d_printf("ads_find_machine_acct: %s\n", ads_errstr(rc));
527 if (ads_count_replies(ads, res) == 0) {
528 d_printf("No machine account for '%s' found\n", global_myname);
537 static int net_ads_leave(int argc, const char **argv)
539 ADS_STRUCT *ads = NULL;
541 extern pstring global_myname;
543 if (!secrets_init()) {
544 DEBUG(1,("Failed to initialise secrets database\n"));
549 asprintf(&opt_user_name, "%s$", global_myname);
550 opt_password = secrets_fetch_machine_password();
553 if (!(ads = ads_startup())) {
557 rc = ads_leave_realm(ads, global_myname);
558 if (!ADS_ERR_OK(rc)) {
559 d_printf("Failed to delete host '%s' from the '%s' realm.\n",
560 global_myname, ads->config.realm);
564 d_printf("Removed '%s' from realm '%s'\n", global_myname, ads->config.realm);
569 static int net_ads_join_ok(void)
571 ADS_STRUCT *ads = NULL;
572 extern pstring global_myname;
574 if (!secrets_init()) {
575 DEBUG(1,("Failed to initialise secrets database\n"));
579 asprintf(&opt_user_name, "%s$", global_myname);
580 opt_password = secrets_fetch_machine_password();
582 if (!(ads = ads_startup())) {
591 check that an existing join is OK
593 int net_ads_testjoin(int argc, const char **argv)
595 /* Display success or failure */
596 if (net_ads_join_ok() != 0) {
597 fprintf(stderr,"Join to domain is not valid\n");
601 printf("Join is OK\n");
606 join a domain using ADS
608 int net_ads_join(int argc, const char **argv)
614 extern pstring global_myname;
615 const char *org_unit = "Computers";
621 if (argc > 0) org_unit = argv[0];
623 if (!secrets_init()) {
624 DEBUG(1,("Failed to initialise secrets database\n"));
628 tmp_password = generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH);
629 password = strdup(tmp_password);
631 if (!(ads = ads_startup())) return -1;
633 ou_str = ads_ou_string(org_unit);
634 asprintf(&dn, "%s,%s", ou_str, ads->config.bind_path);
637 rc = ads_search_dn(ads, &res, dn, NULL);
638 ads_msgfree(ads, res);
640 if (rc.error_type == ADS_ERROR_LDAP && rc.err.rc == LDAP_NO_SUCH_OBJECT) {
641 d_printf("ads_join_realm: organizational unit %s does not exist (dn:%s)\n",
647 if (!ADS_ERR_OK(rc)) {
648 d_printf("ads_join_realm: %s\n", ads_errstr(rc));
652 rc = ads_join_realm(ads, global_myname, org_unit);
653 if (!ADS_ERR_OK(rc)) {
654 d_printf("ads_join_realm: %s\n", ads_errstr(rc));
658 rc = ads_domain_sid(ads, &dom_sid);
659 if (!ADS_ERR_OK(rc)) {
660 d_printf("ads_domain_sid: %s\n", ads_errstr(rc));
664 rc = ads_set_machine_password(ads, global_myname, password);
665 if (!ADS_ERR_OK(rc)) {
666 d_printf("ads_set_machine_password: %s\n", ads_errstr(rc));
670 if (!secrets_store_domain_sid(lp_workgroup(), &dom_sid)) {
671 DEBUG(1,("Failed to save domain sid\n"));
675 if (!secrets_store_machine_password(password)) {
676 DEBUG(1,("Failed to save machine password\n"));
680 d_printf("Joined '%s' to realm '%s'\n", global_myname, ads->config.realm);
687 int net_ads_printer_usage(int argc, const char **argv)
690 "\nnet ads printer info <printer> <server>"
691 "\n\tlookup info in directory for printer on server"
692 "\n\t(note: printer defaults to \"*\", server defaults to local)\n"
693 "\nnet ads printer publish <printername>"
694 "\n\tpublish printer in directory"
695 "\n\t(note: printer name is required)\n"
696 "\nnet ads printer remove <printername>"
697 "\n\tremove printer from directory"
698 "\n\t(note: printer name is required)\n");
702 static int net_ads_printer_info(int argc, const char **argv)
706 const char *servername, *printername;
707 extern pstring global_myname;
710 if (!(ads = ads_startup())) return -1;
713 printername = argv[0];
718 servername = argv[1];
720 servername = global_myname;
722 rc = ads_find_printer_on_server(ads, &res, printername, servername);
724 if (!ADS_ERR_OK(rc)) {
725 d_printf("ads_find_printer_on_server: %s\n", ads_errstr(rc));
726 ads_msgfree(ads, res);
730 if (ads_count_replies(ads, res) == 0) {
731 d_printf("Printer '%s' not found\n", printername);
732 ads_msgfree(ads, res);
737 ads_msgfree(ads, res);
742 void do_drv_upgrade_printer(int msg_type, pid_t src, void *buf, size_t len)
747 static int net_ads_printer_publish(int argc, const char **argv)
751 char *uncname, *servername;
752 ADS_PRINTER_ENTRY prt;
753 extern pstring global_myname;
754 char *ports[2] = {"Samba", NULL};
757 these const strings are only here as an example. The attributes
758 they represent are not implemented yet
760 const char *bins[] = {"Tray 21", NULL};
761 const char *media[] = {"Letter", NULL};
762 const char *orients[] = {"PORTRAIT", NULL};
764 if (!(ads = ads_startup())) return -1;
767 return net_ads_printer_usage(argc, argv);
769 memset(&prt, 0, sizeof(ADS_PRINTER_ENTRY));
771 /* we don't sue the servername or unc name provided by
772 get_a_printer, because the server name might be
773 localhost or an ip address */
774 prt.printerName = argv[0];
775 asprintf(&servername, "%s.%s", global_myname, ads->config.realm);
776 prt.serverName = servername;
777 prt.shortServerName = global_myname;
778 prt.versionNumber = "4";
779 asprintf(&uncname, "\\\\%s\\%s", global_myname, argv[0]);
781 prt.printBinNames = (char **) bins;
782 prt.printMediaSupported = (char **) media;
783 prt.printOrientationsSupported = (char **) orients;
784 prt.portName = (char **) ports;
785 prt.printSpooling = "PrintAfterSpooled";
787 rc = ads_add_printer(ads, &prt);
788 if (!ADS_ERR_OK(rc)) {
789 d_printf("ads_publish_printer: %s\n", ads_errstr(rc));
793 d_printf("published printer\n");
798 static int net_ads_printer_remove(int argc, const char **argv)
802 char *servername, *prt_dn;
803 extern pstring global_myname;
806 if (!(ads = ads_startup())) return -1;
809 return net_ads_printer_usage(argc, argv);
812 servername = argv[1];
814 servername = global_myname;
816 rc = ads_find_printer_on_server(ads, &res, argv[0], servername);
818 if (!ADS_ERR_OK(rc)) {
819 d_printf("ads_find_printer_on_server: %s\n", ads_errstr(rc));
820 ads_msgfree(ads, res);
824 if (ads_count_replies(ads, res) == 0) {
825 d_printf("Printer '%s' not found\n", argv[1]);
826 ads_msgfree(ads, res);
830 prt_dn = ads_get_dn(ads, res);
831 ads_msgfree(ads, res);
832 rc = ads_del_dn(ads, prt_dn);
833 ads_memfree(ads, prt_dn);
835 if (!ADS_ERR_OK(rc)) {
836 d_printf("ads_del_dn: %s\n", ads_errstr(rc));
843 static int net_ads_printer(int argc, const char **argv)
845 struct functable func[] = {
846 {"INFO", net_ads_printer_info},
847 {"PUBLISH", net_ads_printer_publish},
848 {"REMOVE", net_ads_printer_remove},
852 return net_run_function(argc, argv, func, net_ads_printer_usage);
856 static int net_ads_password(int argc, const char **argv)
859 char *auth_principal = opt_user_name;
860 char *auth_password = opt_password;
862 char *new_password = NULL;
868 if ((argc != 1) || (opt_user_name == NULL) ||
869 (opt_password == NULL) || (strchr(opt_user_name, '@') == NULL) ||
870 (strchr(argv[0], '@') == NULL)) {
871 return net_ads_usage(argc, argv);
874 c = strchr(auth_principal, '@');
877 /* use the realm so we can eventually change passwords for users
878 in realms other than default */
879 if (!(ads = ads_init(realm, NULL, NULL))) return -1;
881 asprintf(&prompt, "Enter new password for %s:", argv[0]);
883 new_password = getpass(prompt);
885 ret = kerberos_set_password(ads->auth.kdc_server, auth_principal,
886 auth_password, argv[0], new_password, ads->auth.time_offset);
887 if (!ADS_ERR_OK(ret)) {
888 d_printf("Password change failed :-( ...\n");
894 d_printf("Password change for %s completed.\n", argv[0]);
902 static int net_ads_change_localhost_pass(int argc, const char **argv)
905 extern pstring global_myname;
906 char *host_principal;
910 if (!secrets_init()) {
911 DEBUG(1,("Failed to initialise secrets database\n"));
915 asprintf(&opt_user_name, "%s$", global_myname);
916 opt_password = secrets_fetch_machine_password();
918 if (!(ads = ads_startup())) {
922 hostname = strdup(global_myname);
924 asprintf(&host_principal, "%s@%s", hostname, ads->config.realm);
926 d_printf("Changing password for principal: HOST/%s\n", host_principal);
928 ret = ads_change_trust_account_password(ads, host_principal);
930 if (!ADS_ERR_OK(ret)) {
931 d_printf("Password change failed :-( ...\n");
933 SAFE_FREE(host_principal);
937 d_printf("Password change for principal HOST/%s succeeded.\n", host_principal);
939 SAFE_FREE(host_principal);
945 help for net ads search
947 static int net_ads_search_usage(int argc, const char **argv)
950 "\nnet ads search <expression> <attributes...>\n"\
951 "\nperform a raw LDAP search on a ADS server and dump the results\n"\
952 "The expression is a standard LDAP search expression, and the\n"\
953 "attributes are a list of LDAP fields to show in the results\n\n"\
954 "Example: net ads search '(objectCategory=group)' sAMAccountName\n\n"
956 net_common_flags_usage(argc, argv);
962 general ADS search function. Useful in diagnosing problems in ADS
964 static int net_ads_search(int argc, const char **argv)
973 return net_ads_search_usage(argc, argv);
976 if (!(ads = ads_startup())) {
983 rc = ads_do_search_all(ads, ads->config.bind_path,
986 if (!ADS_ERR_OK(rc)) {
987 d_printf("search failed: %s\n", ads_errstr(rc));
991 d_printf("Got %d replies\n\n", ads_count_replies(ads, res));
993 /* dump the results */
996 ads_msgfree(ads, res);
1003 int net_ads_help(int argc, const char **argv)
1005 struct functable func[] = {
1006 {"USER", net_ads_user_usage},
1007 {"GROUP", net_ads_group_usage},
1008 {"PRINTER", net_ads_printer_usage},
1009 {"SEARCH", net_ads_search_usage},
1011 {"INFO", net_ads_info},
1012 {"JOIN", net_ads_join},
1013 {"LEAVE", net_ads_leave},
1014 {"STATUS", net_ads_status},
1015 {"PASSWORD", net_ads_password},
1016 {"CHOSTPASS", net_ads_change_localhost_pass},
1021 return net_run_function(argc, argv, func, net_ads_usage);
1024 int net_ads(int argc, const char **argv)
1026 struct functable func[] = {
1027 {"INFO", net_ads_info},
1028 {"JOIN", net_ads_join},
1029 {"TESTJOIN", net_ads_testjoin},
1030 {"LEAVE", net_ads_leave},
1031 {"STATUS", net_ads_status},
1032 {"USER", net_ads_user},
1033 {"GROUP", net_ads_group},
1034 {"PASSWORD", net_ads_password},
1035 {"CHOSTPASS", net_ads_change_localhost_pass},
1036 {"PRINTER", net_ads_printer},
1037 {"SEARCH", net_ads_search},
1038 {"WORKGROUP", net_ads_workgroup},
1039 {"LOOKUP", net_ads_lookup},
1040 {"HELP", net_ads_help},
1044 return net_run_function(argc, argv, func, net_ads_usage);
1049 static int net_ads_noads(void)
1051 d_printf("ADS support not compiled in\n");
1055 int net_ads_usage(int argc, const char **argv)
1057 return net_ads_noads();
1060 int net_ads_help(int argc, const char **argv)
1062 return net_ads_noads();
1065 int net_ads_join(int argc, const char **argv)
1067 return net_ads_noads();
1070 int net_ads_user(int argc, const char **argv)
1072 return net_ads_noads();
1075 int net_ads_group(int argc, const char **argv)
1077 return net_ads_noads();
1080 /* this one shouldn't display a message */
1081 int net_ads_check(void)
1086 int net_ads(int argc, const char **argv)
1088 return net_ads_usage(argc, argv);