13d0c6d2b5da5c9021bd1987afff89cd65d7e24e
[mspang/pyceo.git] / src / gss.c
1 #include <string.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <grp.h>
5
6 #include "util.h"
7 #include "gss.h"
8 #include "net.h"
9 #include "strbuf.h"
10
11 static gss_cred_id_t my_creds = GSS_C_NO_CREDENTIAL;
12 static gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
13 static gss_name_t peer_name = GSS_C_NO_NAME;
14 static gss_name_t imported_service = GSS_C_NO_NAME;
15 static char *peer_principal;
16 static char *peer_username;
17 static OM_uint32 ret_flags;
18 static int complete;
19 char service_name[128];
20
21 void free_gss(void) {
22     OM_uint32 maj_stat, min_stat;
23
24     if (peer_name) {
25         maj_stat = gss_release_name(&min_stat, &peer_name);
26         if (maj_stat != GSS_S_COMPLETE)
27             gss_fatal("gss_release_name", maj_stat, min_stat);
28     }
29
30     if (imported_service) {
31         maj_stat = gss_release_name(&min_stat, &imported_service);
32         if (maj_stat != GSS_S_COMPLETE)
33             gss_fatal("gss_release_name", maj_stat, min_stat);
34     }
35
36     if (context_handle) {
37         maj_stat = gss_delete_sec_context(&min_stat, &context_handle, GSS_C_NO_BUFFER);
38         if (maj_stat != GSS_S_COMPLETE)
39             gss_fatal("gss_delete_sec_context", maj_stat, min_stat);
40     }
41
42     if (my_creds) {
43         maj_stat = gss_release_cred(&min_stat, &my_creds);
44         if (maj_stat != GSS_S_COMPLETE)
45             gss_fatal("gss_release_creds", maj_stat, min_stat);
46     }
47
48     free(peer_principal);
49     free(peer_username);
50 }
51
52 static void display_status(char *prefix, OM_uint32 code, int type) {
53     OM_uint32 maj_stat, min_stat;
54     gss_buffer_desc msg;
55     OM_uint32 msg_ctx = 0;
56
57     maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID,
58                                   &msg_ctx, &msg);
59     logmsg(LOG_ERR, "%s: %s", prefix, (char *)msg.value);
60     gss_release_buffer(&min_stat, &msg);
61
62     while (msg_ctx) {
63         maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID,
64                                       &msg_ctx, &msg);
65         logmsg(LOG_ERR, "additional: %s", (char *)msg.value);
66         gss_release_buffer(&min_stat, &msg);
67     }
68 }
69
70 void gss_fatal(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat) {
71     logmsg(LOG_ERR, "fatal: %s", msg);
72     display_status("major", maj_stat, GSS_C_GSS_CODE);
73     display_status("minor", min_stat, GSS_C_MECH_CODE);
74     exit(1);
75 }
76
77 static void import_service(const char *service, const char *hostname) {
78     OM_uint32 maj_stat, min_stat;
79     gss_buffer_desc buf_desc;
80
81     if (snprintf(service_name, sizeof(service_name),
82                  "%s@%s", service, hostname) >= sizeof(service_name))
83         fatal("service name too long");
84
85     buf_desc.value = service_name;
86     buf_desc.length = strlen(service_name);
87
88     maj_stat = gss_import_name(&min_stat, &buf_desc,
89                                GSS_C_NT_HOSTBASED_SERVICE, &imported_service);
90     if (maj_stat != GSS_S_COMPLETE)
91         gss_fatal("gss_import_name", maj_stat, min_stat);
92 }
93
94 static void check_services(OM_uint32 flags) {
95     debug("gss services: %sconf %sinteg %smutual %sreplay %ssequence",
96             flags & GSS_C_CONF_FLAG     ? "+" : "-",
97             flags & GSS_C_INTEG_FLAG    ? "+" : "-",
98             flags & GSS_C_MUTUAL_FLAG   ? "+" : "-",
99             flags & GSS_C_REPLAY_FLAG   ? "+" : "-",
100             flags & GSS_C_SEQUENCE_FLAG ? "+" : "-");
101     if (~flags & GSS_C_CONF_FLAG)
102         fatal("confidentiality service required");
103     if (~flags & GSS_C_INTEG_FLAG)
104         fatal("integrity service required");
105     if (~flags & GSS_C_MUTUAL_FLAG)
106         fatal("mutual authentication required");
107 }
108
109 void server_acquire_creds(const char *service) {
110     OM_uint32 maj_stat, min_stat;
111     OM_uint32 time_rec;
112
113     if (!strlen(fqdn.buf))
114         fatal("empty fqdn");
115
116     import_service(service, fqdn.buf);
117
118     notice("acquiring credentials for %s", service_name);
119
120     maj_stat = gss_acquire_cred(&min_stat, imported_service, GSS_C_INDEFINITE,
121                                 GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &my_creds,
122                                 NULL, &time_rec);
123     if (maj_stat != GSS_S_COMPLETE)
124         gss_fatal("gss_acquire_cred", maj_stat, min_stat);
125
126     if (time_rec != GSS_C_INDEFINITE)
127         fatal("credentials valid for %d seconds (oops)", time_rec);
128 }
129
130 void client_acquire_creds(const char *service, const char *hostname) {
131     import_service(service, hostname);
132 }
133
134 static char *princ_to_username(char *princ) {
135     char *ret = xstrdup(princ);
136     char *c = strchr(ret, '@');
137     if (c)
138         *c = '\0';
139     return ret;
140 }
141
142 int process_server_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) {
143     OM_uint32 maj_stat, min_stat;
144     OM_uint32 time_rec;
145     gss_OID name_type;
146     gss_buffer_desc peer_princ;
147
148     if (complete)
149         fatal("unexpected %zd-byte token from peer", incoming_tok->length);
150
151     maj_stat = gss_accept_sec_context(&min_stat, &context_handle, my_creds,
152             incoming_tok, GSS_C_NO_CHANNEL_BINDINGS, &peer_name, NULL,
153             outgoing_tok, &ret_flags, &time_rec, NULL);
154     if (maj_stat == GSS_S_COMPLETE) {
155         check_services(ret_flags);
156
157         complete = 1;
158
159         maj_stat = gss_display_name(&min_stat, peer_name, &peer_princ, &name_type);
160         if (maj_stat != GSS_S_COMPLETE)
161             gss_fatal("gss_display_name", maj_stat, min_stat);
162
163         peer_principal = xstrdup((char *)peer_princ.value);
164         peer_username = princ_to_username((char *)peer_princ.value);
165
166         notice("client authenticated as %s", peer_principal);
167         debug("context expires in %d seconds", time_rec);
168
169         maj_stat = gss_release_buffer(&min_stat, &peer_princ);
170         if (maj_stat != GSS_S_COMPLETE)
171             gss_fatal("gss_release_buffer", maj_stat, min_stat);
172
173     } else if (maj_stat != GSS_S_CONTINUE_NEEDED) {
174         gss_fatal("gss_accept_sec_context", maj_stat, min_stat);
175     }
176
177     return complete;
178 }
179
180 int process_client_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) {
181     OM_uint32 maj_stat, min_stat;
182     OM_uint32 time_rec;
183     gss_OID_desc krb5 = *gss_mech_krb5;
184
185     if (complete)
186         fatal("unexpected token from peer");
187
188     maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context_handle,
189                                     imported_service, &krb5, GSS_C_MUTUAL_FLAG |
190                                     GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
191                                     GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
192                                     incoming_tok, NULL, outgoing_tok, &ret_flags,
193                                     &time_rec);
194     if (maj_stat == GSS_S_COMPLETE) {
195         notice("server authenticated as %s", service_name);
196         notice("context expires in %d seconds", time_rec);
197
198         check_services(ret_flags);
199
200         complete = 1;
201
202     } else if (maj_stat != GSS_S_CONTINUE_NEEDED) {
203         gss_fatal("gss_init_sec_context", maj_stat, min_stat);
204     }
205
206     return complete;
207 }
208
209 int initial_client_token(gss_buffer_t outgoing_tok) {
210     return process_client_token(GSS_C_NO_BUFFER, outgoing_tok);
211 }
212
213 char *client_principal(void) {
214     return peer_principal;
215 }
216
217 char *client_username(void) {
218     return peer_username;
219 }
220