Demonstration release of the principles underpinning krsd.
[krsd] / src / handler / auth.c
1 /*
2  * rs-serve - (c) 2013 Niklas E. Cathor
3  *
4  * This program is distributed in the hope that it will be useful,
5  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7  * GNU Affero General Public License for more details.
8  *
9  * You should have received a copy of the GNU Affero General Public License
10  * along with this program. If not, see <http://www.gnu.org/licenses/>.
11  */
12
13 #include <stdint.h>
14 #include <alloca.h>
15
16 #include <sys/signalfd.h>
17
18 #include "rs-serve.h"
19
20 #define IS_READ(r) (r->method == htp_method_GET || r->method == htp_method_HEAD)
21
22 // SPNEGO mechanism is 1.3.6.1.5.5.2 -- tag 0x06, length 6, 1.3 packed funny:
23 // uint8_t OID_SPNEGO_bytes [] = { 1*40+3, 6, 1, 5, 5, 2 };
24 // const gss_OID_desc OID_SPNEGO = {
25 //      .length = 6,
26 //      .elements =  OID_SPNEGO_bytes
27 // };
28
29 #if 0
30 static int match_scope(struct rs_scope *scope, evhtp_request_t *req) {
31   const char *file_path = REQUEST_GET_PATH(req);
32   log_debug("checking scope, name: %s, write: %d", scope->name, scope->write);
33   int scope_len = strlen(scope->name);
34   // check path
35   if( (strcmp(scope->name, "") == 0) || // root scope
36       ((strncmp(file_path + 1, scope->name, scope_len) == 0) && // other scope
37        file_path[1 + scope_len] == '/') ) {
38     log_debug("path authorized");
39     // check mode
40     if(scope->write || IS_READ(req)) {
41       log_debug("mode authorized");
42       return 0;
43     }
44   }
45   return -1;
46 }
47 #endif
48
49
50 static uint8_t b64_decode_table [256] = {
51         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
52         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
53         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
54         52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
55         99,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
56         15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
57         99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
58         41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99,
59         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
60         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
61         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
62         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
63         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
64         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
65         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
66         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
67 };
68
69 int b64_decode (gss_buffer_t out, const char *in) {
70         uint32_t digit = 0;
71         uint32_t block = 0;
72         int shift = 3*6;
73         out->length = 0;
74         while ((shift >= 0) && (digit <= 63)) {
75                 uint32_t digit = (uint32_t) b64_decode_table [(uint8_t) *in];
76                 if (digit <= 63) {
77                         block |= digit << shift;
78                         in++;
79                 } else if (shift == 3*6) {
80                         break;
81                 }
82                 shift -= 6;
83                 if (shift < 0) {
84                         ((uint8_t *) out->value) [out->length++] = block >> 16;
85                         ((uint8_t *) out->value) [out->length++] = block >>  8;
86                         ((uint8_t *) out->value) [out->length++] = block      ;
87                         //DEBUG// log_debug("%02x %02x %02x", (block >> 16) & 0x00ff, (block >> 8) & 0x00ff, block & 0x00ff);
88                         block = 0;
89                         if (digit <= 63) {
90                                 shift = 3*6;
91                         }
92                 }
93         }
94         if ((*in) && (*in != '=')) {
95                 return -1;
96         }
97         while ((out->length > 0) && (*in++ == '=')) {
98                 out->length--;
99         }
100         return 0;
101 }
102
103 static uint8_t b64_encode_table [64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
104
105 int b64_encode (char *out, gss_buffer_t in) {
106         int len64 = 0;
107         int pos256 = 0;
108         int totalbits = in->length * 4;
109         uint32_t chunk;
110         while (3 * len64 < totalbits) {
111                 int maskpos = len64 & 0x03;
112                 if (maskpos == 0) {
113                         chunk = (((unsigned char *) in->value) [pos256+0] << 16) |
114                                 (((unsigned char *) in->value) [pos256+1] <<  8) |
115                                  ((unsigned char *) in->value) [pos256+2];
116                         pos256 += 3;
117                 }
118                 *out++ = b64_encode_table [0x3f & (chunk >> (18 - maskpos * 6))];
119                 len64++;
120         }
121         switch (in->length % 3) {
122         case 1:
123                 // Append "=="
124                 out [len64++] = '=';
125                 // ...continue into next case...
126         case 2:
127                 // Append "="
128                 out [len64++] = '=';
129                 // ...continue into next case...
130         case 0:
131                 // Append no '=' characters
132                 break;
133         }
134         out [len64] = '\0';
135         return len64;
136 }
137
138
139 int authorize_request(evhtp_request_t *req, gss_buffer_t username) {
140 #if 0
141   char *username = REQUEST_GET_USER(req);
142 #endif
143   const char *auth_header = evhtp_header_find(req->headers_in, "Authorization");
144   evhtp_header_t *wwwauth = NULL;
145   gss_ctx_id_t ctxh = GSS_C_NO_CONTEXT;
146   gss_cred_id_t deleg = GSS_C_NO_CREDENTIAL;
147   OM_uint32 flgs = 0;
148   int b64len;
149   gss_buffer_desc gssbuf = GSS_C_EMPTY_BUFFER;
150   gss_buffer_desc gssout = GSS_C_EMPTY_BUFFER;
151   gss_name_t intname = GSS_C_NO_NAME;
152   gss_OID mech_oid;
153   OM_uint32 major, minor;
154   if(auth_header == NULL) {
155     log_debug("Got no auth header to work on; requesting SPNEGO");
156     ADD_RESP_HEADER(req, "WWW-Authenticate", "Negotiate");
157     return -1;
158   }
159   //DEBUG// log_debug("Got auth header: %s", auth_header);
160   const char *token;
161   int retval = 0;
162   if(auth_header) {
163     if(strncmp(auth_header, "Negotiate ", 10) == 0) {
164       token = auth_header + 10;
165       b64len = strlen (token);
166       if (b64len > 2048) {
167         log_error("Rejecting long SPNEGO token: %d characters.", b64len);
168         return -2;
169       }
170       gssbuf.length = (b64len * 3 + 3) >> 2; /* Perhaps slightly too much */
171       gssbuf.value = alloca (gssbuf.length);    /* No NULL return */
172       log_debug("Got SPNEGO token of size %d: %s", strlen (token), token);
173       if (b64_decode (&gssbuf, token) < 0) {
174         log_error("Rejecting faulty base64 coding in SPNEGO token.");
175         return -2;
176       }
177       log_debug("SPNEGO decoded length: %d (should be around %f minus trailing '=' signs)", gssbuf.length, strlen (token) * 6.0 / 8.0);
178       //TODO// Sessions are _only_ needed for additional client authentication
179       //TODO// So skip: Was a usable GSSAPI context already created?
180       major = gss_accept_sec_context (
181                         &minor,
182                         &ctxh,
183                         GSS_C_NO_CREDENTIAL,    //MODKRB: server_creds (with that value)
184                         &gssbuf,
185                         GSS_C_NO_CHANNEL_BINDINGS,
186                         &intname,
187                         &mech_oid,
188                         &gssout,
189                         &flgs,
190                         NULL,
191                         &deleg);
192       if (major != GSS_S_COMPLETE) {
193         if (major == GSS_S_CONTINUE_NEEDED) {
194           log_error("GSSAPI requires continued negotiation, which is not supported.");
195           retval = -2;
196         } else {
197           log_error("GSSAPI initiation of security context returns error code %d and minor %d.", major, minor);
198           retval = -1;
199         }
200       }
201       if ((major == GSS_S_CONTINUE_NEEDED) && (gssout.length > 0)) {
202         if (retval == 0) {
203           // We know that major == GSS_S_COMPLETE (otherwise retval < 0)
204           char *hdrval = malloc (11 + gssout.length * 4 / 3 + 3 + 1);
205           if(hdrval) {
206             memcpy (hdrval,      "Negotiate ", 10);
207             b64_encode (hdrval + 10, &gssout);
208             evhtp_headers_add_header(req->headers_out,
209                         evhtp_header_new ("WWW-Authenticate", hdrval, 0, 1));
210             free (hdrval);
211           }
212           gss_release_buffer (NULL, &gssout);
213         }
214         /* Keep retval == 0 --> the client may fail, but we are satisfied */
215       }
216       if (ctxh != GSS_C_NO_CONTEXT) {
217         gss_delete_sec_context (NULL, &ctxh, GSS_C_NO_BUFFER);
218       }
219       /* Pickup opaque intname handle, map it to a gss_buffer_t string */
220       if ((major == GSS_S_COMPLETE) && (retval == 0)) {
221         major = gss_display_name (&minor, intname, username, NULL);
222         if (major != GSS_S_COMPLETE) {
223           log_error("GSSAPI display name returns error code %d and minor %d.", major, minor);
224           return -2;
225         }
226         log_debug("GSSAPI accepted credential named %.*s.", username->length, username->value);
227       }
228       //TODO// Accepted and no sessions?  Then report success right now
229       if (major == GSS_S_COMPLETE) {
230         return 0;
231       }
232       return retval;
233     }
234 #if 0
235     if(strncmp(auth_header, "Bearer ", 7) == 0) {
236       token = auth_header + 7;
237       log_debug("Got Bearer token: %s", token);
238       struct rs_authorization *auth = lookup_authorization(username, token);
239       if(auth == NULL) {
240         log_debug("Authorization not found");
241       } else {
242         log_debug("Got authorization (%p, scopes: %d)", auth, auth->scopes.count);
243         struct rs_scope *scope;
244         int i;
245         for(i=0;i<auth->scopes.count;i++) {
246           scope = auth->scopes.ptr[i];
247           log_debug("Compare scope %s", scope->name);
248           if(match_scope(scope, req) == 0) {
249             return 0;
250           }
251         }
252       }
253     }
254 #endif
255   }
256   // special case: public reads on files (not directories) are allowed.
257   // nothing else though.
258   if(strncmp(REQUEST_GET_PATH(req), "/public/", 8) == 0 && IS_READ(req) &&
259      req->uri->path->file != NULL) {
260     return 0;
261   }
262   wwwauth = evhtp_header_new ("WWW-Authenticate", "Negotiate", 0, 0);
263   if (wwwauth) {
264     evhtp_headers_add_header (req->headers_out, wwwauth);
265   } else {
266     log_error("Failed to allocate memory for WWW-Authenticate header.");
267   }
268   return -1;
269 }