1 /* ========================================================================
2  * Copyright 2006 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * ========================================================================
11  */
12 
13 
14 /* #define PROTOTYPE(x) x */
15 
16 #include <system.h>
17 #include <general.h>
18 
19 #include "wp_uidmapper_lib.h"
20 
21 #include <gssapi/gssapi_generic.h>
22 #include <gssapi/gssapi_krb5.h>
23 
24 #define AUTH_GSS_PROXY_MESSAGE 1
25 #define AUTH_GSS_PROXY_READ 2
26 #define AUTH_GSS_PROXY_SUCCESS 3
27 #define AUTH_GSS_PROXY_WRITE 4
28 #define AUTH_GSS_PROXY_WRITE_NIL 5
29 
30 #define AUTH_GSSAPI_P_NONE 1
31 #define AUTH_GSSAPI_P_INTEGRITY 2
32 #define AUTH_GSSAPI_P_PRIVACY 4
33 
34 #ifdef NO_UIDMAPPER
get_calling_username(int uid,char * name,int namelen)35 int get_calling_username(int uid,char *name,int namelen) {
36   struct passwd *pw;
37   unsigned long len;
38 
39   pw = getpwuid(uid);
40   if(!pw) return -1;
41   len = strlen(pw->pw_name);
42   if(len >= namelen) len = namelen - 1;
43   memcpy(name,pw->pw_name,len);
44   name[len] = 0;
45   return len;
46 }
47 #else
48 #define get_calling_username wp_uidmapper_getname
49 #endif
50 
read_full(int fd,void * buf,unsigned long size)51 static unsigned long read_full(int fd,void *buf,unsigned long size) {
52   unsigned long total,s;
53   for(total = 0; total < size; total += s) {
54     s = read(fd,(char*)buf + total,size - total);
55     if(s == -1) {
56       if((errno == EAGAIN) || (errno == EINTR)) s = 0;
57       else return -1;
58     } else if(s == 0) break;
59   }
60   return total;
61 }
62 
write_full(int fd,void * buf,unsigned long size)63 static unsigned long write_full(int fd,void *buf,unsigned long size) {
64   unsigned long total,s;
65   for(total = 0; total < size; total += s) {
66     s = write(fd,(char*)buf + total,size - total);
67     if(s == -1) {
68       if((errno == EAGAIN) || (errno == EINTR)) s = 0;
69       else return -1;
70     }
71   }
72   return total;
73 }
74 
cmd_message(char * str1,...)75 int cmd_message(char *str1, ...) {
76   va_list list;
77   unsigned long cmd[2],size;
78   char *str;
79 
80   for(size = 0,str = str1,va_start(list,str1); str; str = va_arg(list,char*))
81     size += strlen(str);
82   va_end(list);
83 
84   cmd[0] = AUTH_GSS_PROXY_MESSAGE;
85   cmd[1] = size;
86   if(write_full(1,cmd,sizeof(cmd)) == -1) return -1;
87   for(str = str1, va_start(list,str1); str; str = va_arg(list,char*))
88     if(size = strlen(str)) if(write_full(1,str,size) == -1) {
89       va_end(list);
90       return -1;
91     }
92   va_end(list);
93   return 0;
94 }
95 
cmd_read(gss_buffer_desc * pbuf)96 int cmd_read(gss_buffer_desc *pbuf) {
97   unsigned long cmd[2],len,size;
98   void *buf;
99 
100   cmd[0] = AUTH_GSS_PROXY_READ;
101   cmd[1] = 0;
102   if(write_full(1,cmd,sizeof(cmd)) == -1) return -1;
103 
104   len = read_full(0,&size,sizeof(size));
105   if(len != sizeof(size)) return -1;
106   if(size == 0) {
107     pbuf->value = 0;
108     pbuf->length = 0;
109     return 0;
110   }
111 
112   buf = malloc(size);
113   len = read_full(0,buf,size);
114   if(len != size) {
115     free(buf);
116     return -1;
117   }
118   pbuf->value = buf;
119   pbuf->length = size;
120   return 0;
121 }
122 
cmd_success()123 int cmd_success() {
124   unsigned long cmd[2];
125   cmd[0] = AUTH_GSS_PROXY_SUCCESS;
126   cmd[1] = 0;
127   if(write_full(1,cmd,sizeof(cmd)) == -1) return -1;
128   return 0;
129 }
130 
cmd_write(gss_buffer_desc * buf)131 int cmd_write(gss_buffer_desc *buf) {
132   unsigned long cmd[2];
133   cmd[0] = AUTH_GSS_PROXY_WRITE;
134   cmd[1] = buf->length;
135   if(write_full(1,cmd,sizeof(cmd)) == -1) return -1;
136   if(buf->length) if(write_full(1,buf->value,buf->length) == -1) return -1;
137   return 0;
138 }
139 
cmd_write_nil()140 int cmd_write_nil() {
141   unsigned long cmd[2];
142   cmd[0] = AUTH_GSS_PROXY_WRITE_NIL;
143   cmd[1] = 0;
144   if(write_full(1,cmd,sizeof(cmd)) == -1) return -1;
145   return 0;
146 }
147 
148 /*
149  * service principal in argv[1]
150  * requested username in argv[2]
151  */
152 
main(int argc,char * argv[])153 int main(int argc,char *argv[])
154 {
155   char *user = 0;
156   char userbuf[WP_BUF_SIZE];
157   char *prog;
158   OM_uint32 smj,smn,dsmn,mctx;
159   gss_name_t crname = GSS_C_NO_NAME;
160   gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
161   gss_buffer_desc chal,resp,buf;
162   int conf,i;
163   gss_qop_t qop;
164   gss_OID oid;
165   int calling_uid,eff_uid;
166 
167   if((prog = strrchr(argv[0], '/')) == NULL)
168     prog = argv[0];
169   else
170     prog++;
171 
172   openlog(prog,LOG_PID,LOG_MAIL);
173 
174   calling_uid = getuid();
175   eff_uid = geteuid();
176 #ifdef DEBUG
177   syslog(LOG_INFO,"uid = %d, euid=%d\n",calling_uid,eff_uid);
178 #endif
179 
180 #ifdef WEBSERVER_UID
181   /* if euid != uid, change to web server user */
182   if(calling_uid != eff_uid) if(setuid(WEBSERVER_UID)) {
183     syslog(LOG_ERR,"setuid ((%d != %d) -> %d) failed: %s",
184 	   calling_uid,eff_uid,WEBSERVER_UID,strerror(errno));
185     cmd_write_nil();
186     goto cleanup;
187   }
188 #endif
189 
190   if(argc < 2) {
191     syslog(LOG_WARNING,"not enough arguments");
192     cmd_write_nil();
193     goto cleanup;
194   }
195   if(get_calling_username(calling_uid,userbuf,WP_BUF_SIZE) == -1) {
196     syslog(LOG_WARNING,"cannot determine calling username");
197     cmd_write_nil();
198     goto cleanup;
199   }
200   if(argc == 2) {
201     user = userbuf;
202 #ifdef DEBUG
203     syslog(LOG_INFO,"calling=%s\n",user);
204 #endif
205   } else if(argc > 2) {
206     user = argv[2];
207 #ifdef DEBUG
208     syslog(LOG_INFO,"requested=%s calling=%s\n",user,userbuf);
209 #endif
210 #ifndef NO_NAME_CHECK
211     if(strcmp(user,userbuf)) {
212       syslog(LOG_WARNING,"cannot act on behalf of user %s (%s)",user,userbuf);
213       cmd_write_nil();
214       goto cleanup;
215     }
216 #endif
217   }
218 
219   /* expect empty challenge from server */
220   if(cmd_read(&chal)) {
221     syslog(LOG_WARNING,"cmd_read[initial] failed");
222     goto cleanup;
223   } else if(chal.length) {
224     free(chal.value);
225     syslog(LOG_WARNING,"cmd_read[initial] not empty");
226     goto cleanup;
227   }
228 
229   /*
230    * obtain credentials for requested service
231    */
232 
233   buf.value = argv[1];
234   buf.length = strlen(argv[1]);
235   if(gss_import_name (&smn,&buf,gss_nt_service_name,&crname) !=
236      GSS_S_COMPLETE) {
237     syslog(LOG_WARNING,"gss_import_name(%s) failed",buf.value);
238     cmd_write_nil();
239     goto cleanup;
240   }
241 
242   /* initial init_sec_context call, and send data */
243   memcpy(&oid,&gss_mech_krb5,sizeof(oid));
244   smj = gss_init_sec_context
245     (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,oid,
246      GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0,GSS_C_NO_CHANNEL_BINDINGS,
247      GSS_C_NO_BUFFER,0,&resp,0,0);
248   if((smj == GSS_S_COMPLETE) || (smj == GSS_S_CONTINUE_NEEDED)) {
249     i = cmd_write(&resp);
250     gss_release_buffer (&smn,&resp);
251     if(i) {
252       syslog(LOG_WARNING,"cmd_write[init_sec_context] failed");
253       goto cleanup;
254     }
255   }
256 
257   /* loop until init_sec_context is done */
258   while(smj == GSS_S_CONTINUE_NEEDED) {
259     if(cmd_read(&chal)) {
260       syslog(LOG_WARNING,"cmd_read[init_sec_context] failed");
261       goto cleanup;
262     } else if(!chal.length) {
263       syslog(LOG_WARNING,"cmd_read[init_sec_context] empty");
264       goto cleanup;
265     } else {
266       smj = gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,
267 				  GSS_C_NO_OID,
268 				  GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG,0,
269 				  GSS_C_NO_CHANNEL_BINDINGS,&chal,0,
270 				  &resp,0,0);
271       if(chal.value) free(chal.value);
272       if((smj == GSS_S_COMPLETE) || (smj == GSS_S_CONTINUE_NEEDED)) {
273 	i = cmd_write(&resp);
274 	gss_release_buffer (&smn,&resp);
275 	if(i) {
276 	  syslog(LOG_WARNING,"cmd_write[init_sec_context] failed");
277 	  goto cleanup;
278 	}
279       }
280     }
281   }
282 
283   switch(smj) {
284   case GSS_S_COMPLETE:
285     /* get challenge and unwrap it */
286     if(cmd_read(&chal)) {
287       syslog(LOG_WARNING,"cmd_read[gss_unwrap] failed");
288       goto cleanup;
289     }
290     smj = gss_unwrap (&smn,ctx,&chal,&resp,&conf,&qop);
291     if(chal.value) free(chal.value);
292     if(smj != GSS_S_COMPLETE) {
293       syslog(LOG_WARNING,"gss_unwrap failed");
294       cmd_write_nil();
295       goto cleanup;
296     } else if(resp.length < 4) {
297       syslog(LOG_WARNING,"challenge too short");
298       gss_release_buffer (&smn,&resp);
299       cmd_write_nil();
300       goto cleanup;
301     } else if(!( ((char*)resp.value)[0] & AUTH_GSSAPI_P_NONE)) {
302       syslog(LOG_WARNING,"invalid challenge");
303       gss_release_buffer (&smn,&resp);
304       cmd_write_nil();
305       goto cleanup;
306     }
307 
308     /* prepare response to challenge */
309     buf.length = 4 + (user ? strlen(user) : 0);
310     buf.value = malloc(buf.length);
311     memcpy (buf.value,resp.value,4);
312     gss_release_buffer (&smn,&resp);
313     *(char*)buf.value = AUTH_GSSAPI_P_NONE;
314     if(user) memcpy((char*)buf.value + 4, user, buf.length - 4);
315 
316     /* wrap response and send */
317     smj = gss_wrap (&smn,ctx,0,qop,&buf,&conf,&resp);
318     free(buf.value);
319     if(smj != GSS_S_COMPLETE) {
320       syslog(LOG_WARNING,"gss_unwrap failed");
321       cmd_write_nil();
322       goto cleanup;
323     }
324     i = cmd_write(&resp);
325     gss_release_buffer (&smn,&resp);
326     if(i) {
327       syslog(LOG_WARNING,"cmd_write[gss_wrap] failed");
328       goto cleanup;
329     }
330 
331     /* success! */
332     if(cmd_success()) syslog(LOG_WARNING,"cmd_success failed");
333     goto cleanup;
334 
335   case GSS_S_CREDENTIALS_EXPIRED:
336 #ifdef DEBUG
337     syslog(LOG_INFO,"Kerberos credentials expired (try running kinit)");
338 #endif
339     if(cmd_message("Kerberos credentials expired (try running kinit)",0)) {
340       syslog(LOG_WARNING,"cmd_message[credentials expired] failed");
341       goto cleanup;
342     }
343     cmd_write_nil();
344     goto cleanup;
345 
346   case GSS_S_FAILURE:
347     if (smn == (OM_uint32) KRB5_FCC_NOFILE) {
348 #ifdef DEBUG
349       syslog(LOG_INFO,"No credentials cache found (try running kinit)");
350 #endif
351       if(cmd_message("No credentials cache found (try running kinit)",0)) {
352 	syslog(LOG_WARNING,"cmd_message[no cache file found] failed");
353 	goto cleanup;
354       }
355     } else {
356       mctx = 0;
357       do {
358 	gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
359 			    GSS_C_NO_OID,&mctx,&resp);
360 #ifdef DEBUG
361 	syslog(LOG_INFO,"GSSAPI failure: %s",resp.value);
362 #endif
363 	i = cmd_message("GSSAPI failure: ",resp.value,0);
364 	gss_release_buffer (&dsmn,&resp);
365 	if(i) {
366 	  syslog(LOG_WARNING,"cmd_message[failure] failed");
367 	  goto cleanup;
368 	}
369       } while(mctx);
370     }
371     cmd_write_nil();
372     goto cleanup;
373 
374   default:
375     mctx = 0;
376     do {
377       gss_display_status (&dsmn,smn,GSS_C_GSS_CODE,
378 			  GSS_C_NO_OID,&mctx,&resp);
379 #ifdef DEBUG
380       syslog(LOG_INFO,"GSSAPI failure: %s",resp.value);
381 #endif
382       i = cmd_message("Unknown GSSAPI failure: ",resp.value,0);
383       gss_release_buffer (&dsmn,&resp);
384       if(i) {
385 	syslog(LOG_WARNING,"cmd_message[unknown failure] failed");
386 	goto cleanup;
387       }
388     } while(mctx);
389     if(!mctx) do {
390       gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
391 			  GSS_C_NO_OID,&mctx,&resp);
392 #ifdef DEBUG
393       syslog(LOG_INFO,"GSSAPI mechanism status: %s",resp.value);
394 #endif
395       i = cmd_message("GSSAPI mechanism status: ",resp.value,0);
396       gss_release_buffer (&dsmn,&resp);
397       if(i) {
398 	syslog(LOG_WARNING,"cmd_message[unknown failure 2] failed");
399 	goto cleanup;
400       }
401     } while(mctx);
402     cmd_write_nil();
403     goto cleanup;
404   }
405 
406  cleanup:
407   if(ctx != GSS_C_NO_CONTEXT) gss_delete_sec_context (&smn,&ctx,0);
408   if(crname != GSS_C_NO_NAME) gss_release_name (&smn,&crname);
409   closelog();
410   exit(0);
411   return 0;
412 }
413