1/*
2 * gssServerSample.c - gssSample server program
3 *
4 * Copyright 2004-2005 Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
8 * distribute this software and its documentation for any purpose and
9 * without fee is hereby granted, provided that the above copyright
10 * notice appear in all copies and that both that copyright notice and
11 * this permission notice appear in supporting documentation, and that
12 * the name of M.I.T. not be used in advertising or publicity pertaining
13 * to distribution of the software without specific, written prior
14 * permission.  Furthermore if you modify this software you must label
15 * your software as modified software and not distribute it in such a
16 * fashion that it might be confused with the original M.I.T. software.
17 * M.I.T. makes no representations about the suitability of
18 * this software for any purpose.  It is provided "as is" without express
19 * or implied warranty.
20 */
21
22
23#include <sys/types.h>
24#include <stdio.h>
25#include <unistd.h>
26#include <string.h>
27#include <errno.h>
28#include <sys/socket.h>
29#include <netinet/in.h>
30#include <Kerberos/Kerberos.h>
31#include "test-gss-common.h"
32
33const char *gServiceName = NULL;
34
35/* --------------------------------------------------------------------------- */
36
37static int SetupListeningSocket (int inPort, int *outFD)
38{
39    int err = 0;
40    int fd = -1;
41
42    if (!err) {
43        fd = socket (AF_INET, SOCK_STREAM, 0);
44        if (fd < 0) { err = errno; }
45    }
46
47    if (!err) {
48        struct sockaddr_storage addressStorage;
49        struct sockaddr_in *saddr = (struct sockaddr_in *) &addressStorage;
50
51        saddr->sin_port = htons (inPort);
52        saddr->sin_len = sizeof (struct sockaddr_in);
53        saddr->sin_family = AF_INET;
54        saddr->sin_addr.s_addr = INADDR_ANY;
55
56        err = bind (fd, (struct sockaddr *) saddr, saddr->sin_len);
57        if (err < 0) { err = errno; }
58    }
59
60    if (!err) {
61        err = listen (fd, 5);
62        if (err < 0) { err = errno; }
63    }
64
65    if (!err) {
66        printf ("listening on port %d\n", inPort);
67        *outFD = fd;
68        fd = -1; /* only close on error */
69    } else {
70        printError (err, "SetupListeningSocket failed");
71    }
72
73    if (fd >= 0) { close (fd); }
74
75    return err;
76}
77
78/* --------------------------------------------------------------------------- */
79
80static int Authenticate (int inSocket, gss_ctx_id_t *outContext)
81{
82    int err = 0;
83    OM_uint32 majorStatus;
84    OM_uint32 minorStatus = 0;
85    gss_ctx_id_t context = GSS_C_NO_CONTEXT;
86
87    char *inputTokenBuffer = NULL;
88    size_t inputTokenBufferLength = 0;
89    gss_buffer_desc inputToken;  /* buffer received from the server */
90
91    if (inSocket    <  0)    { err = EINVAL; }
92    if (outContext  == NULL) { err = EINVAL; }
93
94    /*
95     * The main authentication loop:
96     *
97     * GSS is a multimechanism API.  The number of packet exchanges required to authenticate
98     * varies between mechanisms.  As a result, we need to loop reading "input tokens" from
99     * the client, calling gss_accept_sec_context on the "input tokens" and send the resulting
100     * "output tokens" back to the client until we get GSS_S_COMPLETE or an error.
101     *
102     * When we are done, save the client principal so we can make authorization checks.
103     */
104
105    majorStatus = GSS_S_CONTINUE_NEEDED;
106    while (!err && (majorStatus != GSS_S_COMPLETE)) {
107        /* Clean up old input buffer */
108        if (inputTokenBuffer != NULL) {
109            free (inputTokenBuffer);
110            inputTokenBuffer = NULL;  /* don't double-free */
111        }
112
113        err = ReadToken (inSocket, &inputTokenBuffer, &inputTokenBufferLength);
114
115        if (!err) {
116            /* Set up input buffers for the next run through the loop */
117            inputToken.value = inputTokenBuffer;
118            inputToken.length = inputTokenBufferLength;
119        }
120
121        if (!err) {
122            gss_buffer_desc outputToken = { 0, NULL }; /* buffer to send to the server */
123
124            /*
125             * accept_sec_context does the actual work of taking the client's request and
126             * generating an appropriate reply.  Note that we pass GSS_C_NO_CREDENTIAL for
127             * the service principal.  This causes the server to accept any service principal
128             * in the server's keytab, which enables you to support multihomed hosts by having
129             * one key in the keytab for each host identity the server responds on.
130             *
131             * However, since we may have more keys in the keytab than we want the server
132             * to actually use, we will need to check which service principal the client used
133             * after authentication succeeds.  See ServicePrincipalIsValidForService() for
134             * where you would put these checks.  We don't check here since if we stopped
135             * responding in the middle of the authentication negotiation, the client
136             * would get an EOF, and the user wouldn't know what went wrong.
137             */
138
139            printf ("Calling gss_accept_sec_context...\n");
140            majorStatus = gss_accept_sec_context (&minorStatus, &context, GSS_C_NO_CREDENTIAL,
141                                                  &inputToken, GSS_C_NO_CHANNEL_BINDINGS, NULL /* client_name */,
142                                                  NULL /* mech_types */, &outputToken, NULL /* req_flags */,
143                                                  NULL /* time_rec */, NULL /* delegated_cred_handle */);
144
145            if ((outputToken.length > 0) && (outputToken.value != NULL)) {
146                /* Send the output token to the client (even on error) */
147                err = WriteToken (inSocket, outputToken.value, outputToken.length);
148
149                /* free the output token */
150                gss_release_buffer (&minorStatus, &outputToken);
151            }
152        }
153
154        if ((majorStatus != GSS_S_COMPLETE) && (majorStatus != GSS_S_CONTINUE_NEEDED)) {
155            printGSSErrors ("gss_accept_sec_context", majorStatus, minorStatus);
156            err = minorStatus ? minorStatus : majorStatus;
157        }
158    }
159
160    if (!err) {
161        *outContext = context;
162    } else {
163        printError (err, "Authenticate failed");
164    }
165
166    return err;
167}
168
169/* --------------------------------------------------------------------------- */
170
171static int ServicePrincipalIsValidForService (const char *inServicePrincipal)
172{
173    int err = 0;
174    krb5_context context = NULL;
175    krb5_principal principal = NULL;
176
177    if (inServicePrincipal == NULL) { err = EINVAL; }
178
179    if (!err) {
180        err = krb5_init_context (&context);
181    }
182
183    if (!err) {
184        err = krb5_parse_name (context, inServicePrincipal, &principal);
185    }
186
187    if (!err) {
188        /*
189         * Here is where we check to see if the service principal the client used is valid.
190         * Typically we would just check that the first component is the service name.
191         * Here we check only if the server was started with the service name option.
192         */
193        if ((gServiceName != NULL) && (strcmp (gServiceName, krb5_princ_name (context, principal)->data) != 0)) {
194            err = KRB5KRB_AP_WRONG_PRINC;
195        }
196    }
197
198    if (principal != NULL) { krb5_free_principal (context, principal); }
199    if (context   != NULL) { krb5_free_context (context); }
200
201    return err;
202}
203
204
205/* --------------------------------------------------------------------------- */
206
207static int ClientPrincipalIsAuthorizedForService (const char *inClientPrincipal)
208{
209    int err = 0;
210    krb5_context context = NULL;
211    krb5_principal principal = NULL;
212
213    if (inClientPrincipal == NULL) { err = EINVAL; }
214
215    if (!err) {
216        err = krb5_init_context (&context);
217    }
218
219    if (!err) {
220        err = krb5_parse_name (context, inClientPrincipal, &principal);
221    }
222
223    if (!err) {
224        /*
225         * Here is where the server checks to see if the client principal should be allowed
226         * to use your service. Typically it should check both the name and the realm,
227         * since with cross-realm shared keys, a user at another realm may be trying to
228         * contact your service.   Most sites don't want to let users from other realms
229         * use their services except for specific individuals.
230         */
231        err = 0;
232    }
233
234    if (principal != NULL) { krb5_free_principal (context, principal); }
235    if (context   != NULL) { krb5_free_context (context); }
236
237    return err;
238}
239
240/* --------------------------------------------------------------------------- */
241
242static int Authorize (gss_ctx_id_t *inContext, int *outAuthorized, int *outAuthorizationError)
243{
244    int err = 0;
245    OM_uint32 majorStatus;
246    OM_uint32 minorStatus = 0;
247    gss_name_t clientName = NULL;
248    gss_name_t serviceName = NULL;
249    char *clientPrincipal = NULL;
250    char *servicePrincipal = NULL;
251
252    if (outAuthorized         == NULL) { err = EINVAL; }
253    if (outAuthorizationError == NULL) { err = EINVAL; }
254
255    if (!err) {
256        /* Get the client and service principals used to authenticate */
257        majorStatus = gss_inquire_context (&minorStatus, *inContext, &clientName, &serviceName,
258                                           NULL, NULL, NULL, NULL, NULL);
259        if (majorStatus != GSS_S_COMPLETE) { err = minorStatus ? minorStatus : majorStatus; }
260    }
261
262    if (!err) {
263        /* Pull the client principal string out of the gss name */
264        gss_buffer_desc nameToken;
265
266        majorStatus = gss_display_name (&minorStatus, clientName, &nameToken, NULL);
267        if (majorStatus != GSS_S_COMPLETE) { err = minorStatus ? minorStatus : majorStatus; }
268
269        if (!err) {
270            clientPrincipal = malloc (nameToken.length + 1);
271            if (clientPrincipal == NULL) { err = ENOMEM; }
272        }
273
274        if (!err) {
275            memcpy (clientPrincipal, nameToken.value, nameToken.length);
276            clientPrincipal[nameToken.length] = '\0';
277        }
278
279        if (nameToken.value != NULL) { gss_release_buffer (&minorStatus, &nameToken); }
280    }
281
282    if (!err) {
283        /* Pull the service principal string out of the gss name */
284        gss_buffer_desc nameToken;
285
286        majorStatus = gss_display_name (&minorStatus, serviceName, &nameToken, NULL);
287        if (majorStatus != GSS_S_COMPLETE) { err = minorStatus ? minorStatus : majorStatus; }
288
289        if (!err) {
290            servicePrincipal = malloc (nameToken.length + 1);
291            if (servicePrincipal == NULL) { err = ENOMEM; }
292        }
293
294        if (!err) {
295            memcpy (servicePrincipal, nameToken.value, nameToken.length);
296            servicePrincipal[nameToken.length] = '\0';
297        }
298
299        if (nameToken.value != NULL) { gss_release_buffer (&minorStatus, &nameToken); }
300    }
301
302    if (!err) {
303        int authorizationErr = ServicePrincipalIsValidForService (servicePrincipal);
304
305        if (!authorizationErr) {
306            authorizationErr = ClientPrincipalIsAuthorizedForService (clientPrincipal);
307        }
308
309        printf ("'%s' is%s authorized for service '%s'\n",
310                    clientPrincipal, authorizationErr ? " NOT" : "", servicePrincipal);
311
312        *outAuthorized = !authorizationErr;
313        *outAuthorizationError = authorizationErr;
314    }
315
316    if (clientPrincipal  == NULL) { free (clientPrincipal); }
317    if (servicePrincipal == NULL) { free (servicePrincipal); }
318
319    return err;
320}
321
322/* --------------------------------------------------------------------------- */
323
324static void Usage (const char *argv[])
325{
326    fprintf (stderr, "Usage: %s [--port portNumber] [--sname serviceName]\n", argv[0]);
327    exit (1);
328}
329
330/* --------------------------------------------------------------------------- */
331
332int main (int argc, const char *argv[])
333{
334    int err = 0;
335    OM_uint32 minorStatus;
336    int port = kDefaultPort;
337    int listenFD = -1;
338    gss_ctx_id_t gssContext = GSS_C_NO_CONTEXT;
339    gss_buffer_desc outputToken = { 0, NULL };
340    int i = 0;
341
342    for (i = 1; (i < argc) && !err; i++) {
343        if ((strcmp (argv[i], "--port") == 0) && (i < (argc - 1))) {
344            port = strtol (argv[++i], NULL, 0);
345            if (port == 0) { err = errno; }
346        } else if ((strcmp(argv[i], "--sname") == 0) && (i < (argc - 1))) {
347            gServiceName = argv[++i];
348        } else {
349            err = EINVAL;
350        }
351    }
352
353    if (!err) {
354        printf ("%s: Starting up...\n", argv[0]);
355
356        err = SetupListeningSocket (port, &listenFD);
357    }
358
359    if (!err) {
360        int connectionErr = 0;
361        int connectionFD = -1;
362        int authorized = 0;
363        int authorizationError = 0;
364
365        connectionFD = accept (listenFD, NULL, NULL);
366        if (connectionFD < 0) {
367            if (errno != EINTR) {
368                err = errno;
369            }
370            //continue;  /* Try again */
371        }
372
373        printf ("Accepting new connection...\n");
374        connectionErr = Authenticate (connectionFD, &gssContext);
375
376        if (!connectionErr) {
377            connectionErr = Authorize (&gssContext, &authorized, &authorizationError);
378        }
379
380        if (!connectionErr) {
381            char buffer[1024];
382            memset (buffer, 0, sizeof (buffer));
383
384            /*
385             * Here is where your protocol would go.  This sample server just
386             * writes a nul terminated string to the client telling whether
387             * it was authorized.
388             */
389            if (authorized) {
390                snprintf (buffer, sizeof (buffer), "SUCCESS!");
391            } else {
392                snprintf (buffer, sizeof(buffer),  "FAILURE! %s (err = %d)",
393                          error_message (authorizationError), authorizationError);
394            }
395            connectionErr = WriteEncryptedToken (connectionFD, gssContext, buffer, strlen (buffer) + 1);
396        }
397
398        if (connectionErr) {
399            printError (connectionErr, "Connection failed");
400        }
401
402        if (connectionFD >= 0) { printf ("Closing connection.\n"); close (connectionFD); }
403    }
404
405    if (err) {
406        if (err == EINVAL) {
407            Usage (argv);
408        } else {
409            printError (err, "Server failed");
410        }
411    }
412
413    if (listenFD          >= 0)    { close (listenFD); }
414    if (gssContext        != NULL) { gss_delete_sec_context (&minorStatus, &gssContext, &outputToken); }
415    if (outputToken.value != NULL) { gss_release_buffer (&minorStatus, &outputToken); }
416
417    return err ? -1 : 0;
418}
419
420