/* * Copyright (c) 2006 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Portions Copyright (c) 2009 - 2010, 2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of KTH nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import <Foundation/Foundation.h> #include <config.h> #include <stdio.h> #include <gssapi.h> #include <gssapi_krb5.h> #include <gssapi_spnego.h> #include <gssapi_ntlm.h> #include <gssapi_oid.h> #include <gssapi_spi.h> #include <heim-ipc.h> #include <err.h> #include <roken.h> #include <getarg.h> #include <rtbl.h> #include <hex.h> #include <gss-commands.h> #include <krb5.h> #ifdef __APPLE__ #include <heimcred.h> #endif #include <parse_time.h> #include "crypto-headers.h" static void usage (int ret) __attribute__((noreturn)); static int version_flag = 0; static int help_flag = 0; static struct getargs args[] = { {"version", 0, arg_flag, &version_flag, "print version", NULL }, {"help", 0, arg_flag, &help_flag, NULL, NULL } }; static void usage (int ret) { arg_printusage (args, sizeof(args)/sizeof(*args), NULL, "service@host"); exit (ret); } #define COL_DESC "Description" #define COL_ENABLED "Enabled" #define COL_EXPIRE "Expire" #define COL_MECH "Mech" #define COL_NAME "Name" #define COL_OID "OID" #define COL_OPTION "Option" #define COL_SASL "SASL" #define COL_VALUE "Value" #define COL_UUID "UUID" int supported_mechanisms(struct supported_mechanisms_options *opt, int argc, char **argv) { OM_uint32 maj_stat, min_stat; gss_OID_set mechs; rtbl_t ct; size_t i; maj_stat = gss_indicate_mechs(&min_stat, &mechs); if (maj_stat != GSS_S_COMPLETE) errx(1, "gss_indicate_mechs failed"); printf("Supported mechanisms:\n"); ct = rtbl_create(); if (ct == NULL) errx(1, "rtbl_create"); rtbl_set_separator(ct, " "); rtbl_add_column(ct, COL_OID, 0); rtbl_add_column(ct, COL_NAME, 0); rtbl_add_column(ct, COL_SASL, 0); if (opt->options_flag) { rtbl_add_column(ct, COL_OPTION, 0); rtbl_add_column(ct, COL_ENABLED, 0); } for (i = 0; i < mechs->count; i++) { gss_buffer_desc str; const char *name = NULL; maj_stat = gss_oid_to_str(&min_stat, &mechs->elements[i], &str); if (maj_stat != GSS_S_COMPLETE) errx(1, "gss_oid_to_str failed"); rtbl_add_column_entryv(ct, COL_OID, "%.*s", (int)str.length, (char *)str.value); gss_release_buffer(&min_stat, &str); name = gss_oid_to_name(&mechs->elements[i]); if (name) rtbl_add_column_entry(ct, COL_NAME, name); else rtbl_add_column_entry(ct, COL_NAME, ""); maj_stat = gss_inquire_saslname_for_mech(&min_stat, &mechs->elements[i], &str, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) errx(1, "gss_inquire_saslname_for_mech failed"); rtbl_add_column_entryv(ct, COL_SASL, "%.*s", (int)str.length, (char *)str.value); gss_release_buffer(&min_stat, &str); if (opt->options_flag) { gss_OID_set options = GSS_C_NO_OID_SET; gss_buffer_desc oname; size_t n; int ena; gss_mo_list(&mechs->elements[i], &options); rtbl_add_column_entry(ct, COL_OPTION, ""); rtbl_add_column_entry(ct, COL_ENABLED, ""); for (n = 0; options && n < options->count; n++) { maj_stat = gss_mo_name(&mechs->elements[i], &options->elements[n], &oname); if (maj_stat != GSS_S_COMPLETE) continue; rtbl_add_column_entry(ct, COL_OID, ""); rtbl_add_column_entry(ct, COL_NAME, ""); rtbl_add_column_entry(ct, COL_SASL, ""); ena = gss_mo_get(&mechs->elements[i], &options->elements[n], NULL); rtbl_add_column_entryv(ct, COL_OPTION, "%.*s", (int)oname.length, (char *)oname.value); rtbl_add_column_entry(ct, COL_ENABLED, ena ? "yes" : "no"); gss_release_buffer(&min_stat, &oname); } } } gss_release_oid_set(&min_stat, &mechs); rtbl_format(ct, stdout); rtbl_destroy(ct); return 0; } int acquire_credential(struct acquire_credential_options *opt, int argc, char **argv) { char password[512]; OM_uint32 maj_stat, min_stat; gss_const_OID mech = NULL; gss_OID nametype = GSS_C_NT_USER_NAME; gss_name_t name = GSS_C_NO_NAME; gss_buffer_desc buffer; gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; CFMutableDictionaryRef attributes; CFStringRef pw; CFErrorRef error = NULL; CFMutableDictionaryRef sourceAppDictionary; sourceAppDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); /* * mech */ attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (attributes == NULL) errx(1, "out of memory"); if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "No such mech: %s", opt->mech_string); } else { mech = GSS_KRB5_MECHANISM; } /* * user */ if (opt->user_string == NULL && argc < 1) errx(1, "no user string"); if (opt->user_string) { buffer.value = rk_UNCONST(opt->user_string); buffer.length = strlen(opt->user_string); } else { buffer.value = argv[0]; buffer.length = strlen(argv[0]); } maj_stat = gss_import_name(&min_stat, &buffer, nametype, &name); if (maj_stat) errx(1, "failed to import name"); if (opt->certificate_persistant_string) { size_t slen = strlen(opt->certificate_persistant_string); CFMutableDataRef data = CFDataCreateMutable(NULL, slen); ssize_t dlen; CFDataSetLength(data, slen); dlen = hex_decode(opt->certificate_persistant_string, CFDataGetMutableBytePtr(data), slen); if (dlen < 0) errx(1, "failed to hex decode reference"); CFDataSetLength(data, dlen); CFDictionarySetValue(attributes, kGSSICCertificate, data); CFRelease(data); } else { /* * password */ if (UI_UTIL_read_pw_string(password, sizeof(password), "Password: ", 0) != 0) errx(1, "failed reading password"); pw = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8); CFDictionarySetValue(attributes, kGSSICPassword, pw); CFRelease(pw); } if (opt->validate_flag) CFDictionarySetValue(attributes, kGSSICVerifyCredential, kCFBooleanTrue); if (opt->server_hostname_string) { CFStringRef hn = CFStringCreateWithCString(NULL, opt->server_hostname_string, kCFStringEncodingUTF8); if (hn == NULL) errx(1, "CFStringCreateWithCString"); // GSS_C_NT_HOSTBASED_SERVICE gss_name_t gname = GSSCreateName(hn, GSS_KRB5_NT_PRINCIPAL_NAME, NULL); if (gname == NULL) errx(1, "GSSCreateName"); CFDictionarySetValue(attributes, kGSSICVerifyCredentialAcceptorName, gname); CFRelease(hn); CFRelease(gname); } if (opt->kdc_hostname_string) { CFStringRef hn = CFStringCreateWithCString(NULL, opt->kdc_hostname_string, kCFStringEncodingUTF8); if (hn == NULL) errx(1, "CFStringCreateWithCString"); CFDictionarySetValue(attributes, kGSSICLKDCHostname, hn); CFRelease(hn); } if (opt->source_app_identifier_string) { CFStringRef sai = CFStringCreateWithCString(NULL, opt->source_app_identifier_string, kCFStringEncodingUTF8); if (sai == NULL) errx(1, "CFStringCreateWithCString"); CFDictionarySetValue(sourceAppDictionary, kGSSICAppleSourceAppSigningIdentity, sai); CFRelease(sai); } if (opt->source_app_audit_token_string) { NSData *tokenData = [NSData dataWithContentsOfFile:[NSString stringWithCString:opt->source_app_audit_token_string encoding:NSUTF8StringEncoding]]; CFDictionarySetValue(sourceAppDictionary, kGSSICAppleSourceAppAuditToken, (__bridge const void *)(tokenData)); } if (opt->source_app_identifier_string || opt->source_app_audit_token_string) { CFDictionarySetValue(attributes, kGSSICAppleSourceApp, sourceAppDictionary); } maj_stat = gss_aapl_initial_cred(name, mech, attributes, &cred, &error); if (maj_stat != GSS_S_COMPLETE) { char *msg = NULL; if (error) { CFStringRef m; m = CFErrorCopyDescription(error); if (m) { msg = rk_cfstring2cstring(m); CFRelease(m); } } errx(1, "gss_aapl_initial_cred: %s: %d", msg ? msg : "", (int)maj_stat); } gss_release_cred(&min_stat, &cred); gss_release_name(&min_stat, &name); CFRelease(attributes); if (error) CFRelease(error); return 0; } struct print_cred { rtbl_t t; }; static void print_cred(void *ctx, gss_const_OID oid, gss_cred_id_t cred) { struct print_cred *pc = ctx; gss_buffer_set_t data_set; OM_uint32 major, junk; gss_buffer_desc buffer; gss_name_t name; const char *str; OM_uint32 expire; if (cred == NULL) return; major = gss_inquire_cred(&junk, cred, NULL, &expire, NULL, NULL); if (major == GSS_S_CREDENTIALS_EXPIRED) expire = 0; else if (major) goto out; major = gss_inquire_cred_by_mech(&junk, cred, (gss_OID)oid, &name, NULL, NULL, NULL); if (major) goto out; major = gss_display_name(&junk, name, &buffer, NULL); gss_release_name(&junk, &name); if (major) goto out; rtbl_add_column_entryv(pc->t, COL_NAME, "%.*s", (int)buffer.length, (char *)buffer.value); gss_release_buffer(&junk, &buffer); str = gss_oid_to_name(oid); if (str) rtbl_add_column_entry(pc->t, COL_MECH, str); if (expire == GSS_C_INDEFINITE) rtbl_add_column_entryv(pc->t, COL_EXPIRE, "never"); else if (expire == 0) rtbl_add_column_entryv(pc->t, COL_EXPIRE, "expired"); else { char life[80]; unparse_time_approx(expire, life, sizeof(life)); rtbl_add_column_entry(pc->t, COL_EXPIRE, life); } major = gss_inquire_cred_by_oid(&junk, cred, GSS_C_NT_UUID, &data_set); if (major == GSS_S_COMPLETE && data_set->count == 1) rtbl_add_column_entryv(pc->t, COL_UUID, "%.*s\n", (int)data_set->elements[0].length, (const char *)data_set->elements[0].value); else rtbl_add_column_entry(pc->t, COL_UUID, ""); gss_release_buffer_set(&junk, &data_set); out: gss_release_cred(&junk, &cred); } static void diag_cred(void *ctx, gss_const_OID oid, gss_cred_id_t cred) { const char *delim = "----------------"; gss_buffer_set_t data_set; gss_buffer_desc buffer; OM_uint32 major, junk; gss_name_t name; const char *mech; size_t n; if (cred == NULL) return; major = gss_inquire_cred_by_mech(&junk, cred, (gss_OID)oid, &name, NULL, NULL, NULL); if (major) return; major = gss_display_name(&junk, name, &buffer, NULL); gss_release_name(&junk, &name); if (major) return; mech = gss_oid_to_name(oid); printf("@GSSCred\n%s\n%s: %.*s\n", delim, mech ? mech : "<unknown-mech>", (int)buffer.length, buffer.value); gss_release_buffer(&junk, &buffer); major = gss_inquire_cred_by_oid(&junk, cred, GSS_C_CRED_DIAG, &data_set); if (major) return; for (n = 0; n < data_set->count; n++) printf("%s\n%.*s\n", delim, (int)data_set->elements[n].length, data_set->elements[n].value); printf("%s\n", delim); gss_release_buffer_set(&junk, &data_set); major = gss_inquire_cred_by_oid(&junk, cred, GSS_C_NT_UUID, &data_set); if (major == GSS_S_COMPLETE || data_set->count == 1) { printf("UUID: %.*s\n", (int)data_set->elements[0].length, (const char *)data_set->elements[0].value); printf("%s\n", delim); } gss_release_buffer_set(&junk, &data_set); } int list_credentials(struct list_credentials_options *opt, int argc, char **argv) { struct print_cred pc; gss_const_OID mech = NULL; OM_uint32 junk; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "No such mech: %s", opt->mech_string); } if (opt->verbose_flag) { gss_iter_creds_f(&junk, 0, mech, NULL, diag_cred); } else { pc.t = rtbl_create(); if (pc.t == NULL) errx(1, "rtbl_create"); rtbl_set_separator(pc.t, " "); rtbl_add_column(pc.t, COL_NAME, 0); rtbl_add_column(pc.t, COL_EXPIRE, 0); rtbl_add_column(pc.t, COL_MECH, 0); rtbl_add_column(pc.t, COL_UUID, 0); gss_iter_creds_f(&junk, 0, mech, &pc, print_cred); rtbl_format(pc.t, stdout); rtbl_destroy(pc.t); } return 0; } static gss_cred_id_t acquire_cred(const char *name_string, gss_const_OID mech, gss_const_OID nametype) { OM_uint32 maj_stat, min_stat; gss_OID_set mechset = NULL; gss_cred_id_t cred = NULL; gss_buffer_desc buffer; gss_name_t name; buffer.value = rk_UNCONST(name_string); buffer.length = strlen(name_string); maj_stat = gss_import_name(&min_stat, &buffer, nametype, &name); if (maj_stat) errx(1, "failed to import name"); if (mech) { gss_create_empty_oid_set(&min_stat, &mechset); gss_add_oid_set_member(&min_stat, mech, &mechset); } maj_stat = gss_acquire_cred(&min_stat, name, GSS_C_INDEFINITE, mechset, GSS_C_INITIATE, &cred, NULL, NULL); gss_release_name(&min_stat, &name); gss_release_oid_set(&min_stat, &mechset); if (maj_stat || cred == NULL) errx(1, "acquire_cred failed"); return cred; } static void destroy_cred(void *arg1, gss_const_OID oid, gss_cred_id_t cred) { OM_uint32 junk; gss_destroy_cred(&junk, &cred); } int destroy(struct destroy_options *opt, int argc, char **argv) { gss_const_OID mech = NULL; OM_uint32 junk; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "No such mech: %s", opt->mech_string); } if (opt->all_flag) { gss_iter_creds_f(&junk, 0, mech, NULL, destroy_cred); } else if (opt->uuid_string) { CFStringRef uuidString = CFStringCreateWithCString(NULL, opt->uuid_string, kCFStringEncodingUTF8); CFUUIDRef uuid = CFUUIDCreateFromString(NULL, uuidString); CFRelease(uuidString); if (uuid) { HeimCredDeleteByUUID(uuid); CFRelease(uuid); } else { errx(1, "invalid uuid: %s", opt->uuid_string); } } else { gss_cred_id_t cred; if (argc < 1) { printf("%s: missing name\n", getprogname()); return 1; } cred = acquire_cred(argv[0], mech, GSS_C_NT_USER_NAME); gss_destroy_cred(&junk, &cred); } return 0; } /* * */ static int common_hold(OM_uint32 (*func)(OM_uint32 *, gss_cred_id_t), const char *mech_string, int argc, char **argv) { OM_uint32 min_stat, maj_stat; gss_const_OID mech = GSS_C_NO_OID; gss_cred_id_t cred; if (argc < 1) { printf("missing username to (un)hold\n"); return 1; } if (mech_string) { mech = gss_name_to_oid(mech_string); if (mech == NULL) errx(1, "No such mech: %s", mech_string); } cred = acquire_cred(argv[0], mech, GSS_C_NT_USER_NAME); maj_stat = func(&min_stat, cred); if (maj_stat != GSS_S_COMPLETE) errx(1, "(un)hold failed"); gss_release_cred(&min_stat, &cred); return 0; } int hold(struct hold_options *opt, int argc, char **argv) { return common_hold(gss_cred_hold, opt->mech_string, argc, argv); } int unhold(struct unhold_options *opt, int argc, char **argv) { return common_hold(gss_cred_unhold, opt->mech_string, argc, argv); } int get_label(struct get_label_options *opt, int argc, char **argv) { OM_uint32 min_stat, maj_stat; gss_const_OID mech = GSS_C_NO_OID; gss_cred_id_t cred; gss_buffer_desc buf; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "No such mech: %s", opt->mech_string); } cred = acquire_cred(argv[0], mech, GSS_C_NT_USER_NAME); maj_stat = gss_cred_label_get(&min_stat, cred, argv[1], &buf); if (maj_stat != GSS_S_COMPLETE) errx(1, "label get failed"); printf("value: %.*s\n", (int)buf.length, (char *)buf.value); gss_release_buffer(&min_stat, &buf); gss_release_cred(&min_stat, &cred); return 0; } int set_label(struct set_label_options *opt, int argc, char **argv) { OM_uint32 min_stat, maj_stat; gss_const_OID mech = GSS_C_NO_OID; gss_cred_id_t cred; gss_buffer_desc buf; gss_buffer_t bufp = NULL; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "No such mech: %s", opt->mech_string); } cred = acquire_cred(argv[0], mech, GSS_C_NT_USER_NAME); if (argc > 2) { buf.value = argv[2]; buf.length = strlen(argv[2]); bufp = &buf; } maj_stat = gss_cred_label_set(&min_stat, cred, argv[1], bufp); if (maj_stat != GSS_S_COMPLETE) errx(1, "label get failed"); gss_release_cred(&min_stat, &cred); return 0; } /* * */ static void print_mech_attr(const char *mechname, gss_const_OID mech, gss_OID_set set) { gss_buffer_desc name, desc; OM_uint32 major, minor; rtbl_t ct; size_t n; ct = rtbl_create(); if (ct == NULL) errx(1, "rtbl_create"); rtbl_set_separator(ct, " "); rtbl_add_column(ct, COL_OID, 0); rtbl_add_column(ct, COL_DESC, 0); if (mech) rtbl_add_column(ct, COL_VALUE, 0); for (n = 0; n < set->count; n++) { major = gss_display_mech_attr(&minor, &set->elements[n], &name, &desc, NULL); if (major) continue; rtbl_add_column_entryv(ct, COL_OID, "%.*s", (int)name.length, (char *)name.value); rtbl_add_column_entryv(ct, COL_DESC, "%.*s", (int)desc.length, (char *)desc.value); if (mech) { gss_buffer_desc value; if (gss_mo_get(mech, &set->elements[n], &value) != 0) value.length = 0; if (value.length) rtbl_add_column_entryv(ct, COL_VALUE, "%.*s", (int)value.length, (char *)value.value); else rtbl_add_column_entryv(ct, COL_VALUE, "<>"); gss_release_buffer(&minor, &value); } gss_release_buffer(&minor, &name); gss_release_buffer(&minor, &desc); } printf("attributes for: %s\n", mechname); rtbl_format(ct, stdout); rtbl_destroy(ct); } int attrs_for_mech(struct attrs_for_mech_options *opt, int argc, char **argv) { gss_OID_set mech_attr = NULL, known_mech_attrs = NULL; gss_const_OID mech = GSS_C_NO_OID; OM_uint32 major, minor; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "mech %s is unknown", opt->mech_string); } major = gss_inquire_attrs_for_mech(&minor, mech, &mech_attr, &known_mech_attrs); if (major) errx(1, "gss_inquire_attrs_for_mech"); if (mech) print_mech_attr(opt->mech_string, mech, mech_attr); if (opt->all_flag) print_mech_attr("all mechs", NULL, known_mech_attrs); gss_release_oid_set(&minor, &mech_attr); gss_release_oid_set(&minor, &known_mech_attrs); return 0; } int display_status(struct display_status_options *opt, int argc, char **argv) { int status_type = GSS_C_GSS_CODE; gss_const_OID mech = GSS_C_NO_OID; if (opt->minor_status_flag) status_type = GSS_C_MECH_CODE; if (opt->mech_string) { mech = gss_name_to_oid(opt->mech_string); if (mech == NULL) errx(1, "mech %s is unknown", opt->mech_string); } for (;argc; argc--, argv++) { OM_uint32 ret, new_stat, msg_ctx; gss_buffer_desc status_string; long num; num = atol(argv[0]); if (num == 0) { warnx("failed to parse %s as a number", argv[0]); continue; } msg_ctx = 0; do { ret = gss_display_status(&new_stat, (OM_uint32)num, status_type, (gss_OID)mech, &msg_ctx, &status_string); if (!GSS_ERROR(ret)) { printf("%s: %.*s\n", argv[0], (int)status_string.length, (char *)status_string.value); gss_release_buffer(&new_stat, &status_string); } } while (!GSS_ERROR(ret) && msg_ctx != 0); } return 0; } /* * */ int credentials_status(struct credentials_status_options *opt, int argc, char **argv) { #ifdef __APPLE__ @autoreleasepool { NSDictionary *status = CFBridgingRelease(HeimCredCopyStatus(NULL)); if (status) { NSError *error; NSData *plist = [NSPropertyListSerialization dataWithPropertyList:status format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; if (!plist && error!=nil) { printf("error converting to plist: %s", [[error localizedDescription] UTF8String]); } NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput]; [stdout writeData:plist]; } else { printf("no credentials to dump\n"); return 1; } } #endif return 0; } int delete_all(void* opt, int argc, char **argv) { CFErrorRef error = NULL; CFStringRef altDsid = NULL; char *dsid = NULL; if (argc == 1) { dsid = argv[0]; altDsid = CFStringCreateWithCString(NULL, dsid, kCFStringEncodingUTF8); } if (altDsid && !HeimCredDeleteAll(altDsid, &error)) { char *msg = NULL; if (error) { CFStringRef m; m = CFErrorCopyDescription(error); if (m) { msg = rk_cfstring2cstring(m); CFRelease(m); } } CFRelease(altDsid); errx(1, "error deleting all for dsid: %s: %s", dsid ? dsid : "", msg ? msg : ""); } if (altDsid) { CFRelease(altDsid); } return 0; } /* * */ int help(void *opt, int argc, char **argv) { sl_slc_help(commands, argc, argv); return 0; } int main(int argc, char **argv) { int optidx = 0; setprogname(argv[0]); if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx)) usage(1); if (help_flag) usage (0); if(version_flag){ print_version(NULL); exit(0); } argc -= optidx; argv += optidx; if (argc == 0) { help(NULL, argc, argv); return 1; } return sl_command (commands, argc, argv); }