/* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_STRING_H #include #else #include #endif int establish_context(); int connect_to_server(); int call_server(); int client_establish_context(); int send_token(); int recv_token(); void read_file(); int deleg_flag; void display_status(); extern FILE *display_file; void usage() { fprintf(stderr, "Usage: gss-client [-port port] [-d] [-v2] host service \ msg\n"); exit(1); } int main(argc, argv) int argc; char **argv; { char *service_name, *server_host, *msg; u_short port = 4444; int v2 = 0; int use_file = 0; display_file = stdout; deleg_flag = 0; /* Parse arguments. */ argc--; argv++; while (argc) { if (strcmp(*argv, "-port") == 0) { argc--; argv++; if (!argc) usage(); port = atoi(*argv); } else if (strcmp(*argv, "-v2") == 0) { v2 = 1; } else if (strcmp(*argv, "-d") == 0) { deleg_flag = GSS_C_DELEG_FLAG; } else if (strcmp(*argv, "-f") == 0) { use_file = 1; } else break; argc--; argv++; } if (argc != 3) usage(); server_host = *argv++; service_name = *argv++; msg = *argv++; if (call_server(server_host, port, v2, service_name, msg, use_file) < 0) exit(1); return 0; } /* * Function: call_server * * Purpose: Call the "sign" service. * * Arguments: * * host (r) the host providing the service * port (r) the port to connect to on host * service_name (r) the GSS-API service name to authenticate to * msg (r) the message to have "signed" * * Returns: 0 on success, -1 on failure * * Effects: * * call_server opens a TCP connection to and establishes a * GSS-API context with service_name over the connection. It then * seals msg in a GSS-API token with gss_seal, sends it to the server, * reads back a GSS-API signature block for msg from the server, and * verifies it with gss_verify. -1 is returned if any step fails, * otherwise 0 is returned. */ int call_server(host, port, dov2, service_name, msg, use_file) char *host; u_short port; int dov2; char *service_name; char *msg; int use_file; { gss_ctx_id_t context; gss_buffer_desc in_buf, out_buf, context_token; int s, state; OM_uint32 maj_stat, min_stat; gss_name_t src_name, targ_name; gss_buffer_desc sname, tname; OM_uint32 lifetime; gss_OID mechanism, name_type; int is_local; #ifdef GSSAPI_V2 OM_uint32 context_flags; int is_open; gss_qop_t qop_state; gss_OID_set mech_names; gss_buffer_desc oid_name; #else /* GSSAPI_V2 */ int context_flags; #endif /* GSSAPI_V2 */ /* Open connection */ if ((s = connect_to_server(host, port)) < 0) return -1; /* Establish context */ if (client_establish_context(s, service_name, &context) < 0) return -1; #ifdef GSSAPI_V2 if (dov2) { /* * Attempt to save and then restore the context. */ maj_stat = gss_export_sec_context(&min_stat, &context, &context_token); if (maj_stat != GSS_S_COMPLETE) { display_status("exporting context", maj_stat, min_stat); return -1; } maj_stat = gss_import_sec_context(&min_stat, &context_token, &context); if (maj_stat != GSS_S_COMPLETE) { display_status("importing context", maj_stat, min_stat); return -1; } (void) gss_release_buffer(&min_stat, &context_token); } #endif /* GSSAPI_V2 */ /* Get context information */ maj_stat = gss_inquire_context(&min_stat, context, &src_name, &targ_name, &lifetime, &mechanism, &context_flags, &is_local #ifdef GSSAPI_V2 , &is_open #endif /* GSSAPI_V2 */ ); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring context", maj_stat, min_stat); return -1; } maj_stat = gss_display_name(&min_stat, src_name, &sname, &name_type); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying context", maj_stat, min_stat); return -1; } maj_stat = gss_display_name(&min_stat, targ_name, &tname, (gss_OID *) NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying context", maj_stat, min_stat); return -1; } fprintf(stderr, "\"%s\" to \"%s\", lifetime %d, flags %x, %s", (char *) sname.value, (char *) tname.value, lifetime, context_flags, (is_local) ? "locally initiated" : "remotely initiated"); #ifdef GSSAPI_V2 fprintf(stderr, " %s", (is_open) ? "open" : "closed"); #endif /* GSSAPI_V2 */ fprintf(stderr, "\n"); (void) gss_release_name(&min_stat, &src_name); (void) gss_release_name(&min_stat, &targ_name); (void) gss_release_buffer(&min_stat, &sname); (void) gss_release_buffer(&min_stat, &tname); #ifdef GSSAPI_V2 if (dov2) { size_t i; maj_stat = gss_oid_to_str(&min_stat, name_type, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, "Name type of source name is %s.\n", (char *) oid_name.value); (void) gss_release_buffer(&min_stat, &oid_name); (void) gss_release_oid(&min_stat, &name_type); /* Now get the names supported by the mechanism */ maj_stat = gss_inquire_names_for_mech(&min_stat, mechanism, &mech_names); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring mech names", maj_stat, min_stat); return -1; } maj_stat = gss_oid_to_str(&min_stat, mechanism, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, "Mechanism %s supports %d names\n", (char *) oid_name.value, mech_names->count); (void) gss_release_buffer(&min_stat, &oid_name); for (i=0; icount; i++) { gss_OID tmpoid; int is_present; maj_stat = gss_oid_to_str(&min_stat, &mech_names->elements[i], &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, "%d: %s\n", i, (char *) oid_name.value); maj_stat = gss_str_to_oid(&min_stat, &oid_name, &tmpoid); if (maj_stat != GSS_S_COMPLETE) { display_status("converting string->oid", maj_stat, min_stat); return -1; } maj_stat = gss_test_oid_set_member(&min_stat, tmpoid, mech_names, &is_present); if (maj_stat != GSS_S_COMPLETE) { display_status("testing oid presence", maj_stat, min_stat); return -1; } if (!is_present) { fprintf(stderr, "%s is not present in list?\n", (char *) oid_name.value); } (void) gss_release_oid(&min_stat, &tmpoid); (void) gss_release_buffer(&min_stat, &oid_name); } (void) gss_release_oid_set(&min_stat, &mech_names); (void) gss_release_oid(&min_stat, &mechanism); } #endif /* GSSAPI_V2 */ if (use_file) { read_file(msg, &in_buf); } else { /* Seal the message */ in_buf.value = msg; in_buf.length = strlen(msg) + 1; } #ifdef GSSAPI_V2 if (dov2) maj_stat = gss_wrap(&min_stat, context, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); else #endif /* GSSAPI_V2 */ maj_stat = gss_seal(&min_stat, context, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("sealing message", maj_stat, min_stat); return -1; } else if (! state) { fprintf(stderr, "Warning! Message not encrypted.\n"); } if (use_file) free(in_buf.value); /* Send to server */ if (send_token(s, &out_buf) < 0) return -1; (void) gss_release_buffer(&min_stat, &out_buf); /* Read signature block into out_buf */ if (recv_token(s, &out_buf) < 0) return -1; /* Verify signature block */ #ifdef GSSAPI_V2 if (dov2) maj_stat = gss_verify_mic(&min_stat, context, &in_buf, &out_buf, &qop_state); else #endif /* GSSAPI_V2 */ maj_stat = gss_verify(&min_stat, context, &in_buf, &out_buf, &state); if (maj_stat != GSS_S_COMPLETE) { display_status("verifying signature", maj_stat, min_stat); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); printf("Signature verified.\n"); /* Delete context */ maj_stat = gss_delete_sec_context(&min_stat, &context, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("deleting context", maj_stat, min_stat); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); return 0; } /* * Function: connect_to_server * * Purpose: Opens a TCP connection to the name host and port. * * Arguments: * * host (r) the target host name * port (r) the target port, in host byte order * * Returns: the established socket file desciptor, or -1 on failure * * Effects: * * The host name is resolved with gethostbyname(), and the socket is * opened and connected. If an error occurs, an error message is * displayed and -1 is returned. */ int connect_to_server(host, port) char *host; u_short port; { struct sockaddr_in saddr; struct hostent *hp; int s; if ((hp = gethostbyname(host)) == NULL) { fprintf(stderr, "Unknown host: %s\n", host); return -1; } saddr.sin_family = hp->h_addrtype; memcpy((char *)&saddr.sin_addr, hp->h_addr, hp->h_length); saddr.sin_port = htons(port); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("creating socket"); return -1; } if (connect(s, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { perror("connecting to server"); return -1; } return s; } /* * Function: client_establish_context * * Purpose: establishes a GSS-API context with a specified service and * returns the context handle * * Arguments: * * s (r) an established TCP connection to the service * service_name (r) the ASCII service name of the service * context (w) the established GSS-API context * * Returns: 0 on success, -1 on failure * * Effects: * * service_name is imported as a GSS-API name and a GSS-API context is * established with the corresponding service; the service should be * listening on the TCP connection s. The default GSS-API mechanism * is used, and mutual authentication and replay detection are * requested. * * If successful, the context handle is returned in context. If * unsuccessful, the GSS-API error messages are displayed on stderr * and -1 is returned. */ int client_establish_context(s, service_name, gss_context) int s; char *service_name; gss_ctx_id_t *gss_context; { gss_buffer_desc send_tok, recv_tok, *token_ptr; gss_name_t target_name; OM_uint32 maj_stat, min_stat; /* * Import the name into target_name. Use send_tok to save * local variable space. */ send_tok.value = service_name; send_tok.length = strlen(service_name) + 1; maj_stat = gss_import_name(&min_stat, &send_tok, (gss_OID) gss_nt_service_name, &target_name); if (maj_stat != GSS_S_COMPLETE) { display_status("parsing name", maj_stat, min_stat); return -1; } /* * Perform the context-establishement loop. * * On each pass through the loop, token_ptr points to the token * to send to the server (or GSS_C_NO_BUFFER on the first pass). * Every generated token is stored in send_tok which is then * transmitted to the server; every received token is stored in * recv_tok, which token_ptr is then set to, to be processed by * the next call to gss_init_sec_context. * * GSS-API guarantees that send_tok's length will be non-zero * if and only if the server is expecting another token from us, * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if * and only if the server has another token to send us. */ token_ptr = GSS_C_NO_BUFFER; *gss_context = GSS_C_NO_CONTEXT; do { maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, gss_context, target_name, GSS_C_NULL_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | deleg_flag, 0, NULL, /* no channel bindings */ token_ptr, NULL, /* ignore mech type */ &send_tok, NULL, /* ignore ret_flags */ NULL); /* ignore time_rec */ if (token_ptr != GSS_C_NO_BUFFER) (void) gss_release_buffer(&min_stat, &recv_tok); if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) { display_status("initializing context", maj_stat, min_stat); (void) gss_release_name(&min_stat, &target_name); return -1; } if (send_tok.length != 0) { printf("Sending init_sec_context token (size=%d)...", send_tok.length); if (send_token(s, &send_tok) < 0) { (void) gss_release_buffer(&min_stat, &send_tok); (void) gss_release_name(&min_stat, &target_name); return -1; } } (void) gss_release_buffer(&min_stat, &send_tok); if (maj_stat == GSS_S_CONTINUE_NEEDED) { printf("continue needed..."); if (recv_token(s, &recv_tok) < 0) { (void) gss_release_name(&min_stat, &target_name); return -1; } token_ptr = &recv_tok; } printf("\n"); } while (maj_stat == GSS_S_CONTINUE_NEEDED); (void) gss_release_name(&min_stat, &target_name); return 0; } void read_file(file_name, in_buf) char *file_name; gss_buffer_t in_buf; { int fd, bytes_in, count; struct stat stat_buf; if ((fd = open(file_name, O_RDONLY, 0)) < 0) { perror("open"); fprintf(stderr, "Couldn't open file %s\n", file_name); exit(1); } if (fstat(fd, &stat_buf) < 0) { perror("fstat"); exit(1); } in_buf->length = stat_buf.st_size; in_buf->value = malloc(in_buf->length); if (in_buf->value == 0) { fprintf(stderr, "Couldn't allocate %d byte buffer for reading file\n", in_buf->length); exit(1); } memset(in_buf->value, 0, in_buf->length); for (bytes_in = 0; bytes_in < in_buf->length; bytes_in += count) { count = read(fd, in_buf->value, in_buf->length); if (count < 0) { perror("read"); exit(1); } if (count == 0) break; } if (bytes_in != count) fprintf(stderr, "Warning, only read in %d bytes, expected %d\n", bytes_in, count); }