Path: blob/main/crypto/krb5/src/appl/gss-sample/gss-server.c
34907 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 1994 by OpenVision Technologies, Inc.3*4* Permission to use, copy, modify, distribute, and sell this software5* and its documentation for any purpose is hereby granted without fee,6* provided that the above copyright notice appears in all copies and7* that both that copyright notice and this permission notice appear in8* supporting documentation, and that the name of OpenVision not be used9* in advertising or publicity pertaining to distribution of the software10* without specific, written prior permission. OpenVision makes no11* representations about the suitability of this software for any12* purpose. It is provided "as is" without express or implied warranty.13*14* OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,15* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO16* EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR17* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF18* USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR19* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR20* PERFORMANCE OF THIS SOFTWARE.21*/22/*23* Copyright (C) 2004,2005 by the Massachusetts Institute of Technology.24* All rights reserved.25*26* Export of this software from the United States of America may27* require a specific license from the United States Government.28* It is the responsibility of any person or organization contemplating29* export to obtain such a license before exporting.30*31* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and32* distribute this software and its documentation for any purpose and33* without fee is hereby granted, provided that the above copyright34* notice appear in all copies and that both that copyright notice and35* this permission notice appear in supporting documentation, and that36* the name of M.I.T. not be used in advertising or publicity pertaining37* to distribution of the software without specific, written prior38* permission. Furthermore if you modify this software you must label39* your software as modified software and not distribute it in such a40* fashion that it might be confused with the original M.I.T. software.41* M.I.T. makes no representations about the suitability of42* this software for any purpose. It is provided "as is" without express43* or implied warranty.44*/4546#include <stdio.h>47#ifdef _WIN3248#include <windows.h>49#include <winsock.h>50#else51#include "port-sockets.h"52#endif53#ifdef HAVE_UNISTD_H54#include <unistd.h>55#endif56#include <stdlib.h>57#include <ctype.h>5859#include <gssapi/gssapi_generic.h>60#include <gssapi/gssapi_krb5.h>61#include "gss-misc.h"6263#ifdef HAVE_STRING_H64#include <string.h>65#else66#include <strings.h>67#endif6869static OM_uint3270enumerateAttributes(OM_uint32 *minor, gss_name_t name, int noisy);71static OM_uint3272showLocalIdentity(OM_uint32 *minor, gss_name_t name);7374static void75usage(void)76{77fprintf(stderr, "Usage: gss-server [-port port] [-verbose] [-once]");78#ifdef _WIN3279fprintf(stderr, " [-threads num]");80#endif81fprintf(stderr, "\n");82fprintf(stderr,83" [-inetd] [-export] [-logfile file] [-keytab keytab]\n"84" service_name\n");85exit(1);86}8788static FILE *logfile;8990int verbose = 0;9192/*93* Function: server_acquire_creds94*95* Purpose: imports a service name and acquires credentials for it96*97* Arguments:98*99* service_name (r) the ASCII service name100* mech (r) the desired mechanism (or GSS_C_NO_OID)101* server_creds (w) the GSS-API service credentials102*103* Returns: 0 on success, -1 on failure104*105* Effects:106*107* The service name is imported with gss_import_name, and service108* credentials are acquired with gss_acquire_cred. If either operation109* fails, an error message is displayed and -1 is returned; otherwise,110* 0 is returned. If mech is given, credentials are acquired for the111* specified mechanism.112*/113114static int115server_acquire_creds(char *service_name, gss_OID mech,116gss_cred_id_t *server_creds)117{118gss_buffer_desc name_buf;119gss_name_t server_name;120OM_uint32 maj_stat, min_stat;121gss_OID_set_desc mechlist;122gss_OID_set mechs = GSS_C_NO_OID_SET;123124name_buf.value = service_name;125name_buf.length = strlen(name_buf.value) + 1;126maj_stat = gss_import_name(&min_stat, &name_buf,127(gss_OID) gss_nt_service_name, &server_name);128if (maj_stat != GSS_S_COMPLETE) {129display_status("importing name", maj_stat, min_stat);130return -1;131}132133if (mech != GSS_C_NO_OID) {134mechlist.count = 1;135mechlist.elements = mech;136mechs = &mechlist;137}138maj_stat = gss_acquire_cred(&min_stat, server_name, 0, mechs, GSS_C_ACCEPT,139server_creds, NULL, NULL);140(void) gss_release_name(&min_stat, &server_name);141if (maj_stat != GSS_S_COMPLETE) {142display_status("acquiring credentials", maj_stat, min_stat);143return -1;144}145146return 0;147}148149/*150* Function: server_establish_context151*152* Purpose: establishses a GSS-API context as a specified service with153* an incoming client, and returns the context handle and associated154* client name155*156* Arguments:157*158* s (r) an established TCP connection to the client159* service_creds (r) server credentials, from gss_acquire_cred160* context (w) the established GSS-API context161* client_name (w) the client's ASCII name162*163* Returns: 0 on success, -1 on failure164*165* Effects:166*167* Any valid client request is accepted. If a context is established,168* its handle is returned in context and the client name is returned169* in client_name and 0 is returned. If unsuccessful, an error170* message is displayed and -1 is returned.171*/172static int173server_establish_context(int s, gss_cred_id_t server_creds,174gss_ctx_id_t *context, gss_buffer_t client_name,175OM_uint32 *ret_flags)176{177gss_buffer_desc send_tok, recv_tok;178gss_name_t client;179gss_OID doid;180OM_uint32 maj_stat, min_stat, acc_sec_min_stat;181gss_buffer_desc oid_name;182int token_flags;183184if (recv_token(s, &token_flags, &recv_tok) < 0)185return -1;186187if (recv_tok.value) {188free(recv_tok.value);189recv_tok.value = NULL;190}191192if (!(token_flags & TOKEN_NOOP)) {193if (logfile)194fprintf(logfile, "Expected NOOP token, got %d token instead\n",195token_flags);196return -1;197}198199*context = GSS_C_NO_CONTEXT;200201if (token_flags & TOKEN_CONTEXT_NEXT) {202do {203if (recv_token(s, &token_flags, &recv_tok) < 0)204return -1;205206if (verbose && logfile) {207fprintf(logfile, "Received token (size=%d): \n",208(int) recv_tok.length);209print_token(&recv_tok);210}211212maj_stat = gss_accept_sec_context(&acc_sec_min_stat, context,213server_creds, &recv_tok,214GSS_C_NO_CHANNEL_BINDINGS,215&client, &doid, &send_tok,216ret_flags,217NULL, /* time_rec */218NULL); /* del_cred_handle */219220if (recv_tok.value) {221free(recv_tok.value);222recv_tok.value = NULL;223}224225if (send_tok.length != 0) {226if (verbose && logfile) {227fprintf(logfile,228"Sending accept_sec_context token (size=%d):\n",229(int) send_tok.length);230print_token(&send_tok);231}232if (send_token(s, TOKEN_CONTEXT, &send_tok) < 0) {233if (logfile)234fprintf(logfile, "failure sending token\n");235return -1;236}237238(void) gss_release_buffer(&min_stat, &send_tok);239}240if (maj_stat != GSS_S_COMPLETE241&& maj_stat != GSS_S_CONTINUE_NEEDED) {242display_status("accepting context", maj_stat,243acc_sec_min_stat);244if (*context != GSS_C_NO_CONTEXT)245gss_delete_sec_context(&min_stat, context,246GSS_C_NO_BUFFER);247return -1;248}249250if (verbose && logfile) {251if (maj_stat == GSS_S_CONTINUE_NEEDED)252fprintf(logfile, "continue needed...\n");253else254fprintf(logfile, "\n");255fflush(logfile);256}257} while (maj_stat == GSS_S_CONTINUE_NEEDED);258259/* display the flags */260display_ctx_flags(*ret_flags);261262if (verbose && logfile) {263maj_stat = gss_oid_to_str(&min_stat, doid, &oid_name);264if (maj_stat != GSS_S_COMPLETE) {265display_status("converting oid->string", maj_stat, min_stat);266return -1;267}268fprintf(logfile, "Accepted connection using mechanism OID %.*s.\n",269(int) oid_name.length, (char *) oid_name.value);270(void) gss_release_buffer(&min_stat, &oid_name);271}272273maj_stat = gss_display_name(&min_stat, client, client_name, &doid);274if (maj_stat != GSS_S_COMPLETE) {275display_status("displaying name", maj_stat, min_stat);276return -1;277}278enumerateAttributes(&min_stat, client, TRUE);279showLocalIdentity(&min_stat, client);280maj_stat = gss_release_name(&min_stat, &client);281if (maj_stat != GSS_S_COMPLETE) {282display_status("releasing name", maj_stat, min_stat);283return -1;284}285} else {286client_name->length = *ret_flags = 0;287288if (logfile)289fprintf(logfile, "Accepted unauthenticated connection.\n");290}291292return 0;293}294295/*296* Function: create_socket297*298* Purpose: Opens a listening TCP socket.299*300* Arguments:301*302* port (r) the port number on which to listen303*304* Returns: the listening socket file descriptor, or -1 on failure305*306* Effects:307*308* A listening socket on the specified port and created and returned.309* On error, an error message is displayed and -1 is returned.310*/311static int312create_socket(u_short port)313{314struct sockaddr_in saddr;315int s;316int on = 1;317318saddr.sin_family = AF_INET;319saddr.sin_port = htons(port);320saddr.sin_addr.s_addr = INADDR_ANY;321322if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {323perror("creating socket");324return -1;325}326/* Let the socket be reused right away */327(void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));328if (bind(s, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) {329perror("binding socket");330(void) closesocket(s);331return -1;332}333if (listen(s, 5) < 0) {334perror("listening on socket");335(void) closesocket(s);336return -1;337}338return s;339}340341static float342timeval_subtract(struct timeval *tv1, struct timeval *tv2)343{344return ((tv1->tv_sec - tv2->tv_sec) +345((float) (tv1->tv_usec - tv2->tv_usec)) / 1000000);346}347348/*349* Yes, yes, this isn't the best place for doing this test.350* DO NOT REMOVE THIS UNTIL A BETTER TEST HAS BEEN WRITTEN, THOUGH.351* -TYT352*/353static int354test_import_export_context(gss_ctx_id_t *context)355{356OM_uint32 min_stat, maj_stat;357gss_buffer_desc context_token, copied_token;358struct timeval tm1, tm2;359360/*361* Attempt to save and then restore the context.362*/363gettimeofday(&tm1, (struct timezone *) 0);364maj_stat = gss_export_sec_context(&min_stat, context, &context_token);365if (maj_stat != GSS_S_COMPLETE) {366display_status("exporting context", maj_stat, min_stat);367return 1;368}369gettimeofday(&tm2, (struct timezone *) 0);370if (verbose && logfile)371fprintf(logfile, "Exported context: %d bytes, %7.4f seconds\n",372(int) context_token.length, timeval_subtract(&tm2, &tm1));373copied_token.length = context_token.length;374copied_token.value = malloc(context_token.length);375if (copied_token.value == 0) {376if (logfile)377fprintf(logfile,378"Couldn't allocate memory to copy context token.\n");379return 1;380}381memcpy(copied_token.value, context_token.value, copied_token.length);382maj_stat = gss_import_sec_context(&min_stat, &copied_token, context);383if (maj_stat != GSS_S_COMPLETE) {384display_status("importing context", maj_stat, min_stat);385return 1;386}387free(copied_token.value);388gettimeofday(&tm1, (struct timezone *) 0);389if (verbose && logfile)390fprintf(logfile, "Importing context: %7.4f seconds\n",391timeval_subtract(&tm1, &tm2));392(void) gss_release_buffer(&min_stat, &context_token);393return 0;394}395396/*397* Function: sign_server398*399* Purpose: Performs the "sign" service.400*401* Arguments:402*403* s (r) a TCP socket on which a connection has been404* accept()ed405* service_name (r) the ASCII name of the GSS-API service to406* establish a context as407* export (r) whether to test context exporting408*409* Returns: -1 on error410*411* Effects:412*413* sign_server establishes a context, and performs a single sign request.414*415* A sign request is a single GSS-API sealed token. The token is416* unsealed and a signature block, produced with gss_sign, is returned417* to the sender. The context is the destroyed and the connection418* closed.419*420* If any error occurs, -1 is returned.421*/422static int423sign_server(int s, gss_cred_id_t server_creds, int export)424{425gss_buffer_desc client_name, recv_buf, unwrap_buf, mic_buf, *msg_buf, *send_buf;426gss_ctx_id_t context;427OM_uint32 maj_stat, min_stat;428int i, conf_state;429OM_uint32 ret_flags;430char *cp;431int token_flags;432int send_flags;433434/* Establish a context with the client */435if (server_establish_context(s, server_creds, &context,436&client_name, &ret_flags) < 0)437return (-1);438439if (context == GSS_C_NO_CONTEXT) {440printf("Accepted unauthenticated connection.\n");441} else {442printf("Accepted connection: \"%.*s\"\n",443(int) client_name.length, (char *) client_name.value);444(void) gss_release_buffer(&min_stat, &client_name);445446if (export) {447for (i = 0; i < 3; i++)448if (test_import_export_context(&context))449return -1;450}451}452453do {454/* Receive the message token */455if (recv_token(s, &token_flags, &recv_buf) < 0)456return (-1);457458if (token_flags & TOKEN_NOOP) {459if (logfile)460fprintf(logfile, "NOOP token\n");461if (recv_buf.value) {462free(recv_buf.value);463recv_buf.value = 0;464}465break;466}467468if (verbose && logfile) {469fprintf(logfile, "Message token (flags=%d):\n", token_flags);470print_token(&recv_buf);471}472473if ((context == GSS_C_NO_CONTEXT) &&474(token_flags & (TOKEN_WRAPPED | TOKEN_ENCRYPTED | TOKEN_SEND_MIC)))475{476if (logfile)477fprintf(logfile,478"Unauthenticated client requested authenticated services!\n");479if (recv_buf.value) {480free(recv_buf.value);481recv_buf.value = 0;482}483return (-1);484}485486if (token_flags & TOKEN_WRAPPED) {487maj_stat = gss_unwrap(&min_stat, context, &recv_buf, &unwrap_buf,488&conf_state, (gss_qop_t *) NULL);489if (maj_stat != GSS_S_COMPLETE) {490display_status("unsealing message", maj_stat, min_stat);491if (recv_buf.value) {492free(recv_buf.value);493recv_buf.value = 0;494}495return (-1);496} else if (!conf_state && (token_flags & TOKEN_ENCRYPTED)) {497fprintf(stderr, "Warning! Message not encrypted.\n");498}499500if (recv_buf.value) {501free(recv_buf.value);502recv_buf.value = 0;503}504msg_buf = &unwrap_buf;505} else {506unwrap_buf.value = NULL;507unwrap_buf.length = 0;508msg_buf = &recv_buf;509}510511if (logfile) {512fprintf(logfile, "Received message: ");513cp = msg_buf->value;514if ((isprint((int) cp[0]) || isspace((int) cp[0])) &&515(isprint((int) cp[1]) || isspace((int) cp[1]))) {516fprintf(logfile, "\"%.*s\"\n", (int) msg_buf->length,517(char *) msg_buf->value);518} else {519fprintf(logfile, "\n");520print_token(msg_buf);521}522}523524if (token_flags & TOKEN_SEND_MIC) {525/* Produce a signature block for the message */526maj_stat = gss_get_mic(&min_stat, context, GSS_C_QOP_DEFAULT,527msg_buf, &mic_buf);528if (maj_stat != GSS_S_COMPLETE) {529display_status("signing message", maj_stat, min_stat);530return (-1);531}532send_flags = TOKEN_MIC;533send_buf = &mic_buf;534} else {535mic_buf.value = NULL;536mic_buf.length = 0;537send_flags = TOKEN_NOOP;538send_buf = empty_token;539}540if (recv_buf.value) {541free(recv_buf.value);542recv_buf.value = NULL;543}544if (unwrap_buf.value) {545gss_release_buffer(&min_stat, &unwrap_buf);546}547548/* Send the signature block or NOOP to the client */549if (send_token(s, send_flags, send_buf) < 0)550return (-1);551552if (mic_buf.value) {553gss_release_buffer(&min_stat, &mic_buf);554}555} while (1 /* loop will break if NOOP received */ );556557if (context != GSS_C_NO_CONTEXT) {558/* Delete context */559maj_stat = gss_delete_sec_context(&min_stat, &context, NULL);560if (maj_stat != GSS_S_COMPLETE) {561display_status("deleting context", maj_stat, min_stat);562return (-1);563}564}565566if (logfile)567fflush(logfile);568569return (0);570}571572static int max_threads = 1;573574#ifdef _WIN32575static thread_count = 0;576static HANDLE hMutex = NULL;577static HANDLE hEvent = NULL;578579void580InitHandles(void)581{582hMutex = CreateMutex(NULL, FALSE, NULL);583hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);584}585586void587CleanupHandles(void)588{589CloseHandle(hMutex);590CloseHandle(hEvent);591}592593BOOL594WaitAndIncrementThreadCounter(void)595{596for (;;) {597if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {598if (thread_count < max_threads) {599thread_count++;600ReleaseMutex(hMutex);601return TRUE;602} else {603ReleaseMutex(hMutex);604605if (WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0) {606continue;607} else {608return FALSE;609}610}611} else {612return FALSE;613}614}615}616617BOOL618DecrementAndSignalThreadCounter(void)619{620if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {621if (thread_count == max_threads)622ResetEvent(hEvent);623thread_count--;624ReleaseMutex(hMutex);625return TRUE;626} else {627return FALSE;628}629}630#endif631632struct _work_plan633{634int s;635gss_cred_id_t server_creds;636int export;637};638639static void640worker_bee(void *param)641{642struct _work_plan *work = (struct _work_plan *) param;643644/* this return value is not checked, because there's645* not really anything to do if it fails646*/647sign_server(work->s, work->server_creds, work->export);648closesocket(work->s);649free(work);650651#ifdef _WIN32652if (max_threads > 1)653DecrementAndSignalThreadCounter();654#endif655}656657int658main(int argc, char **argv)659{660char *service_name;661gss_cred_id_t server_creds;662gss_OID mech = GSS_C_NO_OID;663OM_uint32 min_stat;664u_short port = 4444;665int once = 0;666int do_inetd = 0;667int export = 0;668669logfile = stdout;670display_file = stdout;671argc--;672argv++;673while (argc) {674if (strcmp(*argv, "-port") == 0) {675argc--;676argv++;677if (!argc)678usage();679port = atoi(*argv);680}681#ifdef _WIN32682else if (strcmp(*argv, "-threads") == 0) {683argc--;684argv++;685if (!argc)686usage();687max_threads = atoi(*argv);688}689#endif690else if (strcmp(*argv, "-verbose") == 0) {691verbose = 1;692} else if (strcmp(*argv, "-once") == 0) {693once = 1;694} else if (strcmp(*argv, "-inetd") == 0) {695do_inetd = 1;696} else if (strcmp(*argv, "-export") == 0) {697export = 1;698} else if (strcmp(*argv, "-logfile") == 0) {699argc--;700argv++;701if (!argc)702usage();703/* Gross hack, but it makes it unnecessary to add an704* extra argument to disable logging, and makes the code705* more efficient because it doesn't actually write data706* to /dev/null. */707if (!strcmp(*argv, "/dev/null")) {708logfile = display_file = NULL;709} else {710logfile = fopen(*argv, "a");711display_file = logfile;712if (!logfile) {713perror(*argv);714exit(1);715}716}717} else if (strcmp(*argv, "-keytab") == 0) {718argc--;719argv++;720if (!argc)721usage();722if (krb5_gss_register_acceptor_identity(*argv)) {723fprintf(stderr, "failed to register keytab\n");724exit(1);725}726} else if (strcmp(*argv, "-iakerb") == 0) {727mech = (gss_OID)gss_mech_iakerb;728} else729break;730argc--;731argv++;732}733if (argc != 1)734usage();735736if ((*argv)[0] == '-')737usage();738739#ifdef _WIN32740if (max_threads < 1) {741fprintf(stderr, "warning: there must be at least one thread\n");742max_threads = 1;743}744745if (max_threads > 1 && do_inetd)746fprintf(stderr,747"warning: one thread may be used in conjunction with inetd\n");748749InitHandles();750#endif751752service_name = *argv;753754if (server_acquire_creds(service_name, mech, &server_creds) < 0)755return -1;756757if (do_inetd) {758close(1);759close(2);760761sign_server(0, server_creds, export);762close(0);763} else {764int stmp;765766if ((stmp = create_socket(port)) >= 0) {767fprintf(stderr, "starting...\n");768769do {770struct _work_plan *work = malloc(sizeof(struct _work_plan));771772if (work == NULL) {773fprintf(stderr, "fatal error: out of memory");774break;775}776777/* Accept a TCP connection */778if ((work->s = accept(stmp, NULL, 0)) < 0) {779perror("accepting connection");780free(work);781continue;782}783784work->server_creds = server_creds;785work->export = export;786787if (max_threads == 1) {788worker_bee((void *) work);789}790#ifdef _WIN32791else {792if (WaitAndIncrementThreadCounter()) {793uintptr_t handle =794_beginthread(worker_bee, 0, (void *) work);795if (handle == (uintptr_t) - 1) {796closesocket(work->s);797free(work);798}799} else {800fprintf(stderr,801"fatal error incrementing thread counter");802closesocket(work->s);803free(work);804break;805}806}807#endif808} while (!once);809810closesocket(stmp);811}812}813814(void) gss_release_cred(&min_stat, &server_creds);815816#ifdef _WIN32817CleanupHandles();818#endif819820return 0;821}822823static void824dumpAttribute(OM_uint32 *minor,825gss_name_t name,826gss_buffer_t attribute,827int noisy)828{829OM_uint32 major, tmp;830gss_buffer_desc value;831gss_buffer_desc display_value;832int authenticated = 0;833int complete = 0;834int more = -1;835unsigned int i;836837while (more != 0) {838value.value = NULL;839display_value.value = NULL;840841major = gss_get_name_attribute(minor, name, attribute, &authenticated,842&complete, &value, &display_value,843&more);844if (GSS_ERROR(major)) {845display_status("gss_get_name_attribute", major, *minor);846break;847}848849printf("Attribute %.*s %s %s\n\n%.*s\n",850(int)attribute->length, (char *)attribute->value,851authenticated ? "Authenticated" : "",852complete ? "Complete" : "",853(int)display_value.length, (char *)display_value.value);854855if (noisy) {856for (i = 0; i < value.length; i++) {857if ((i % 32) == 0)858printf("\n");859printf("%02x", ((char *)value.value)[i] & 0xFF);860}861printf("\n\n");862}863864gss_release_buffer(&tmp, &value);865gss_release_buffer(&tmp, &display_value);866}867}868869static OM_uint32870enumerateAttributes(OM_uint32 *minor,871gss_name_t name,872int noisy)873{874OM_uint32 major, tmp;875int name_is_MN;876gss_OID mech = GSS_C_NO_OID;877gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;878unsigned int i;879880major = gss_inquire_name(minor, name, &name_is_MN, &mech, &attrs);881if (GSS_ERROR(major)) {882display_status("gss_inquire_name", major, *minor);883return major;884}885886if (attrs != GSS_C_NO_BUFFER_SET) {887for (i = 0; i < attrs->count; i++)888dumpAttribute(minor, name, &attrs->elements[i], noisy);889}890891gss_release_oid(&tmp, &mech);892gss_release_buffer_set(&tmp, &attrs);893894return major;895}896897static OM_uint32898showLocalIdentity(OM_uint32 *minor, gss_name_t name)899{900OM_uint32 major;901gss_buffer_desc buf;902903major = gss_localname(minor, name, GSS_C_NO_OID, &buf);904if (major == GSS_S_COMPLETE)905printf("localname: %-*s\n", (int)buf.length, (char *)buf.value);906else if (major != GSS_S_UNAVAILABLE)907display_status("gss_localname", major, *minor);908gss_release_buffer(minor, &buf);909return major;910}911912913