1
2
3
4
5
6
7
8
9
10 package org.mortbay.jetty.security;
11
12 import java.io.BufferedReader;
13 import java.io.IOException;
14 import java.io.InputStreamReader;
15 import java.security.Principal;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.Map;
20 import java.util.StringTokenizer;
21
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25
26 import org.mortbay.jetty.Handler;
27 import org.mortbay.jetty.HttpConnection;
28 import org.mortbay.jetty.HttpHeaders;
29 import org.mortbay.jetty.Request;
30 import org.mortbay.jetty.Response;
31 import org.mortbay.jetty.handler.ContextHandler;
32 import org.mortbay.log.Log;
33 import org.mortbay.log.Logger;
34 import org.mortbay.resource.Resource;
35 import org.mortbay.util.StringUtil;
36 import org.mortbay.util.URIUtil;
37
38
39
40
41
42
43
44
45
46
47
48
49 public class HTAccessHandler extends SecurityHandler
50 {
51 private Handler protegee;
52 private static Logger log=Log.getLogger(HTAccessHandler.class.getName());
53
54 String _default=null;
55 String _accessFile=".htaccess";
56
57 transient HashMap _htCache=new HashMap();
58
59
60
61
62
63
64 class DummyPrincipal implements Principal
65 {
66 private String _userName;
67
68 public DummyPrincipal(String name)
69 {
70 _userName=name;
71 }
72
73 public String getName()
74 {
75 return _userName;
76 }
77
78 public String toString()
79 {
80 return getName();
81 }
82 }
83
84
85
86
87
88
89
90
91
92
93 public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
94 {
95 Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
96 Response base_response=(response instanceof Response)?(Response)response:HttpConnection.getCurrentConnection().getResponse();
97
98 String pathInContext=target;
99
100 String user=null;
101 String password=null;
102 boolean IPValid=true;
103
104 if (log.isDebugEnabled())
105 log.debug("HTAccessHandler pathInContext="+pathInContext,null,null);
106
107 String credentials=request.getHeader(HttpHeaders.AUTHORIZATION);
108
109 if (credentials!=null)
110 {
111 credentials=credentials.substring(credentials.indexOf(' ')+1);
112 credentials=B64Code.decode(credentials,StringUtil.__ISO_8859_1);
113 int i=credentials.indexOf(':');
114 user=credentials.substring(0,i);
115 password=credentials.substring(i+1);
116
117 if (log.isDebugEnabled())
118 log.debug("User="+user+", password="+"******************************".substring(0,password.length()),null,null);
119 }
120
121 HTAccess ht=null;
122
123 try
124 {
125 Resource resource=null;
126 String directory=pathInContext.endsWith("/")?pathInContext:URIUtil.parentPath(pathInContext);
127
128
129 while (directory!=null)
130 {
131 String htPath=directory+_accessFile;
132 resource=((ContextHandler)getProtegee()).getResource(htPath);
133 if (log.isDebugEnabled())
134 log.debug("directory="+directory+" resource="+resource,null,null);
135
136 if (resource!=null&&resource.exists()&&!resource.isDirectory())
137 break;
138 resource=null;
139 directory=URIUtil.parentPath(directory);
140 }
141
142 boolean haveHtAccess=true;
143
144
145 if (resource==null&&_default!=null)
146 {
147 resource=Resource.newResource(_default);
148 if (!resource.exists()||resource.isDirectory())
149 haveHtAccess=false;
150 }
151 if (resource==null)
152 haveHtAccess=false;
153
154
155 if (pathInContext.endsWith(_accessFile)
156
157 ||pathInContext.endsWith(_accessFile+"~")||pathInContext.endsWith(_accessFile+".bak"))
158 {
159 response.sendError(HttpServletResponse.SC_FORBIDDEN);
160 base_request.setHandled(true);
161 return;
162 }
163
164 if (haveHtAccess)
165 {
166 if (log.isDebugEnabled())
167 log.debug("HTACCESS="+resource,null,null);
168
169 ht=(HTAccess)_htCache.get(resource);
170 if (ht==null||ht.getLastModified()!=resource.lastModified())
171 {
172 ht=new HTAccess(resource);
173 _htCache.put(resource,ht);
174 if (log.isDebugEnabled())
175 log.debug("HTCache loaded "+ht,null,null);
176 }
177
178
179 if (ht.isForbidden())
180 {
181 log.warn("Mis-configured htaccess: "+ht,null,null);
182 response.sendError(HttpServletResponse.SC_FORBIDDEN);
183 base_request.setHandled(true);
184 return;
185 }
186
187
188 Map methods=ht.getMethods();
189 if (methods.size()>0&&!methods.containsKey(request.getMethod()))
190 return;
191
192
193 int satisfy=ht.getSatisfy();
194
195
196 IPValid=ht.checkAccess("",request.getRemoteAddr());
197 if (log.isDebugEnabled())
198 log.debug("IPValid = "+IPValid,null,null);
199
200
201 if (IPValid==true&&satisfy==HTAccess.ANY)
202 return;
203
204
205
206 if (IPValid==false&&satisfy==HTAccess.ALL)
207 {
208 response.sendError(HttpServletResponse.SC_FORBIDDEN);
209 base_request.setHandled(true);
210 return;
211 }
212
213
214 if (!ht.checkAuth(user,password,getUserRealm(),base_request))
215 {
216 log.debug("Auth Failed",null,null);
217 response.setHeader(HttpHeaders.WWW_AUTHENTICATE,"basic realm="+ht.getName());
218 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
219 base_response.complete();
220 base_request.setHandled(true);
221 return;
222 }
223
224
225 if (user!=null)
226 {
227 base_request.setAuthType(Constraint.__BASIC_AUTH);
228 base_request.setUserPrincipal(getPrincipal(user, getUserRealm()));
229 }
230 }
231
232 if (getHandler()!=null)
233 {
234 getHandler().handle(target,request,response,dispatch);
235 }
236
237 }
238 catch (Exception ex)
239 {
240 log.warn("Exception",ex);
241 if (ht!=null)
242 {
243 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
244 base_request.setHandled(true);
245 }
246 }
247 }
248
249
250
251
252
253
254
255
256
257 public Principal getPrincipal (String user, UserRealm realm)
258 {
259 if (realm==null)
260 return new DummyPrincipal(user);
261
262 return realm.getPrincipal(user);
263 }
264
265
266
267
268
269
270
271
272
273
274 public void setDefault(String dir)
275 {
276 _default=dir;
277 }
278
279
280 public void setAccessFile(String anArg)
281 {
282 if (anArg==null)
283 _accessFile=".htaccess";
284 else
285 _accessFile=anArg;
286 }
287
288
289
290
291 private static class HTAccess
292 {
293
294 static final int ANY=0;
295 static final int ALL=1;
296 static final String USER="user";
297 static final String GROUP="group";
298 static final String VALID_USER="valid-user";
299
300
301 String _userFile;
302 Resource _userResource;
303 HashMap _users=null;
304 long _userModified;
305
306
307 String _groupFile;
308 Resource _groupResource;
309 HashMap _groups=null;
310 long _groupModified;
311
312 int _satisfy=0;
313 String _type;
314 String _name;
315 HashMap _methods=new HashMap();
316 HashSet _requireEntities=new HashSet();
317 String _requireName;
318 int _order;
319 ArrayList _allowList=new ArrayList();
320 ArrayList _denyList=new ArrayList();
321 long _lastModified;
322 boolean _forbidden=false;
323
324
325 public HTAccess(Resource resource)
326 {
327 BufferedReader htin=null;
328 try
329 {
330 htin=new BufferedReader(new InputStreamReader(resource.getInputStream()));
331 parse(htin);
332 _lastModified=resource.lastModified();
333
334 if (_userFile!=null)
335 {
336 _userResource=Resource.newResource(_userFile);
337 if (!_userResource.exists())
338 {
339 _forbidden=true;
340 log.warn("Could not find ht user file: "+_userFile,null,null);
341 }
342 else if (log.isDebugEnabled())
343 log.debug("user file: "+_userResource,null,null);
344 }
345
346 if (_groupFile!=null)
347 {
348 _groupResource=Resource.newResource(_groupFile);
349 if (!_groupResource.exists())
350 {
351 _forbidden=true;
352 log.warn("Could not find ht group file: "+_groupResource,null,null);
353 }
354 else if (log.isDebugEnabled())
355 log.debug("group file: "+_groupResource,null,null);
356 }
357 }
358 catch (IOException e)
359 {
360 _forbidden=true;
361 log.warn("LogSupport.EXCEPTION",e);
362 }
363 }
364
365
366 public boolean isForbidden()
367 {
368 return _forbidden;
369 }
370
371
372 public HashMap getMethods()
373 {
374 return _methods;
375 }
376
377
378 public long getLastModified()
379 {
380 return _lastModified;
381 }
382
383
384 public Resource getUserResource()
385 {
386 return _userResource;
387 }
388
389
390 public Resource getGroupResource()
391 {
392 return _groupResource;
393 }
394
395
396 public int getSatisfy()
397 {
398 return (_satisfy);
399 }
400
401
402 public String getName()
403 {
404 return _name;
405 }
406
407
408 public String getType()
409 {
410 return _type;
411 }
412
413
414 public boolean checkAccess(String host, String ip)
415 {
416 String elm;
417 boolean alp=false;
418 boolean dep=false;
419
420
421 if (_allowList.size()==0&&_denyList.size()==0)
422 return (true);
423
424
425 for (int i=0; i<_allowList.size(); i++)
426 {
427 elm=(String)_allowList.get(i);
428 if (elm.equals("all"))
429 {
430 alp=true;
431 break;
432 }
433 else
434 {
435 char c=elm.charAt(0);
436 if (c>='0'&&c<='9')
437 {
438
439 if (ip.startsWith(elm))
440 {
441 alp=true;
442 break;
443 }
444 }
445 else
446 {
447
448 if (host.endsWith(elm))
449 {
450 alp=true;
451 break;
452 }
453 }
454 }
455 }
456
457
458 for (int i=0; i<_denyList.size(); i++)
459 {
460 elm=(String)_denyList.get(i);
461 if (elm.equals("all"))
462 {
463 dep=true;
464 break;
465 }
466 else
467 {
468 char c=elm.charAt(0);
469 if (c>='0'&&c<='9')
470 {
471 if (ip.startsWith(elm))
472 {
473 dep=true;
474 break;
475 }
476 }
477 else
478 {
479 if (host.endsWith(elm))
480 {
481 dep=true;
482 break;
483 }
484 }
485 }
486 }
487
488 if (_order<0)
489 return !dep||alp;
490
491 return alp&&!dep;
492 }
493
494
495 public boolean checkAuth(String user, String pass, UserRealm realm, Request request)
496 {
497 if (_requireName==null)
498 return true;
499
500
501
502 Principal principal=realm==null?null:realm.authenticate(user,pass,request);
503 if (principal==null)
504 {
505
506 String code=getUserCode(user);
507 String salt=code!=null?code.substring(0,2):user;
508 String cred=(user!=null&&pass!=null)?UnixCrypt.crypt(pass,salt):null;
509 if (code==null||(code.equals("")&&!pass.equals(""))||!code.equals(cred))
510 return false;
511 }
512
513 if (_requireName.equalsIgnoreCase(USER))
514 {
515 if (_requireEntities.contains(user))
516 return true;
517 }
518 else if (_requireName.equalsIgnoreCase(GROUP))
519 {
520 ArrayList gps=getUserGroups(user);
521 if (gps!=null)
522 for (int g=gps.size(); g-->0;)
523 if (_requireEntities.contains(gps.get(g)))
524 return true;
525 }
526 else if (_requireName.equalsIgnoreCase(VALID_USER))
527 {
528 return true;
529 }
530
531 return false;
532 }
533
534
535 public boolean isAccessLimited()
536 {
537 if (_allowList.size()>0||_denyList.size()>0)
538 return true;
539 else
540 return false;
541 }
542
543
544 public boolean isAuthLimited()
545 {
546 if (_requireName!=null)
547 return true;
548 else
549 return false;
550 }
551
552
553 private String getUserCode(String user)
554 {
555 if (_userResource==null)
556 return null;
557
558 if (_users==null||_userModified!=_userResource.lastModified())
559 {
560 if (log.isDebugEnabled())
561 log.debug("LOAD "+_userResource,null,null);
562 _users=new HashMap();
563 BufferedReader ufin=null;
564 try
565 {
566 ufin=new BufferedReader(new InputStreamReader(_userResource.getInputStream()));
567 _userModified=_userResource.lastModified();
568 String line;
569 while ((line=ufin.readLine())!=null)
570 {
571 line=line.trim();
572 if (line.startsWith("#"))
573 continue;
574 int spos=line.indexOf(':');
575 if (spos<0)
576 continue;
577 String u=line.substring(0,spos).trim();
578 String p=line.substring(spos+1).trim();
579 _users.put(u,p);
580 }
581 }
582 catch (IOException e)
583 {
584 log.warn("LogSupport.EXCEPTION",e);
585 }
586 finally
587 {
588 try
589 {
590 if (ufin!=null)
591 ufin.close();
592 }
593 catch (IOException e2)
594 {
595 log.warn("LogSupport.EXCEPTION",e2);
596 }
597 }
598 }
599
600 return (String)_users.get(user);
601 }
602
603
604 private ArrayList getUserGroups(String group)
605 {
606 if (_groupResource==null)
607 return null;
608
609 if (_groups==null||_groupModified!=_groupResource.lastModified())
610 {
611 if (log.isDebugEnabled())
612 log.debug("LOAD "+_groupResource,null,null);
613
614 _groups=new HashMap();
615 BufferedReader ufin=null;
616 try
617 {
618 ufin=new BufferedReader(new InputStreamReader(_groupResource.getInputStream()));
619 _groupModified=_groupResource.lastModified();
620 String line;
621 while ((line=ufin.readLine())!=null)
622 {
623 line=line.trim();
624 if (line.startsWith("#")||line.length()==0)
625 continue;
626
627 StringTokenizer tok=new StringTokenizer(line,": \t");
628
629 if (!tok.hasMoreTokens())
630 continue;
631 String g=tok.nextToken();
632 if (!tok.hasMoreTokens())
633 continue;
634 while (tok.hasMoreTokens())
635 {
636 String u=tok.nextToken();
637 ArrayList gl=(ArrayList)_groups.get(u);
638 if (gl==null)
639 {
640 gl=new ArrayList();
641 _groups.put(u,gl);
642 }
643 gl.add(g);
644 }
645 }
646 }
647 catch (IOException e)
648 {
649 log.warn("LogSupport.EXCEPTION",e);
650 }
651 finally
652 {
653 try
654 {
655 if (ufin!=null)
656 ufin.close();
657 }
658 catch (IOException e2)
659 {
660 log.warn("LogSupport.EXCEPTION",e2);
661 }
662 }
663 }
664
665 return (ArrayList)_groups.get(group);
666 }
667
668
669 public String toString()
670 {
671 StringBuffer buf=new StringBuffer();
672
673 buf.append("AuthUserFile=");
674 buf.append(_userFile);
675 buf.append(", AuthGroupFile=");
676 buf.append(_groupFile);
677 buf.append(", AuthName=");
678 buf.append(_name);
679 buf.append(", AuthType=");
680 buf.append(_type);
681 buf.append(", Methods=");
682 buf.append(_methods);
683 buf.append(", satisfy=");
684 buf.append(_satisfy);
685 if (_order<0)
686 buf.append(", order=deny,allow");
687 else if (_order>0)
688 buf.append(", order=allow,deny");
689 else
690 buf.append(", order=mutual-failure");
691
692 buf.append(", Allow from=");
693 buf.append(_allowList);
694 buf.append(", deny from=");
695 buf.append(_denyList);
696 buf.append(", requireName=");
697 buf.append(_requireName);
698 buf.append(" ");
699 buf.append(_requireEntities);
700
701 return buf.toString();
702 }
703
704
705 private void parse(BufferedReader htin) throws IOException
706 {
707 String line;
708 while ((line=htin.readLine())!=null)
709 {
710 line=line.trim();
711 if (line.startsWith("#"))
712 continue;
713 else if (line.startsWith("AuthUserFile"))
714 {
715 _userFile=line.substring(13).trim();
716 }
717 else if (line.startsWith("AuthGroupFile"))
718 {
719 _groupFile=line.substring(14).trim();
720 }
721 else if (line.startsWith("AuthName"))
722 {
723 _name=line.substring(8).trim();
724 }
725 else if (line.startsWith("AuthType"))
726 {
727 _type=line.substring(8).trim();
728 }
729
730 else if (line.startsWith("<Limit"))
731 {
732 int limit=line.length();
733 int endp=line.indexOf('>');
734 StringTokenizer tkns;
735
736 if (endp<0)
737 endp=limit;
738 tkns=new StringTokenizer(line.substring(6,endp));
739 while (tkns.hasMoreTokens())
740 {
741 _methods.put(tkns.nextToken(),Boolean.TRUE);
742 }
743
744 while ((line=htin.readLine())!=null)
745 {
746 line=line.trim();
747 if (line.startsWith("#"))
748 continue;
749 else if (line.startsWith("satisfy"))
750 {
751 int pos1=7;
752 limit=line.length();
753 while ((pos1<limit)&&(line.charAt(pos1)<=' '))
754 pos1++;
755 int pos2=pos1;
756 while ((pos2<limit)&&(line.charAt(pos2)>' '))
757 pos2++;
758 String l_string=line.substring(pos1,pos2);
759 if (l_string.equals("all"))
760 _satisfy=1;
761 else if (l_string.equals("any"))
762 _satisfy=0;
763 }
764 else if (line.startsWith("require"))
765 {
766 int pos1=7;
767 limit=line.length();
768 while ((pos1<limit)&&(line.charAt(pos1)<=' '))
769 pos1++;
770 int pos2=pos1;
771 while ((pos2<limit)&&(line.charAt(pos2)>' '))
772 pos2++;
773 _requireName=line.substring(pos1,pos2).toLowerCase();
774 if (USER.equals(_requireName))
775 _requireName=USER;
776 else if (GROUP.equals(_requireName))
777 _requireName=GROUP;
778 else if (VALID_USER.equals(_requireName))
779 _requireName=VALID_USER;
780
781 pos1=pos2+1;
782 if (pos1<limit)
783 {
784 while ((pos1<limit)&&(line.charAt(pos1)<=' '))
785 pos1++;
786
787 tkns=new StringTokenizer(line.substring(pos1));
788 while (tkns.hasMoreTokens())
789 {
790 _requireEntities.add(tkns.nextToken());
791 }
792 }
793
794 }
795 else if (line.startsWith("order"))
796 {
797 if (log.isDebugEnabled())
798 log.debug("orderline="+line+"order="+_order,null,null);
799 if (line.indexOf("allow,deny")>0)
800 {
801 log.debug("==>allow+deny",null,null);
802 _order=1;
803 }
804 else if (line.indexOf("deny,allow")>0)
805 {
806 log.debug("==>deny,allow",null,null);
807 _order=-1;
808 }
809 else if (line.indexOf("mutual-failure")>0)
810 {
811 log.debug("==>mutual",null,null);
812 _order=0;
813 }
814 else
815 {
816 }
817 }
818 else if (line.startsWith("allow from"))
819 {
820 int pos1=10;
821 limit=line.length();
822 while ((pos1<limit)&&(line.charAt(pos1)<=' '))
823 pos1++;
824 if (log.isDebugEnabled())
825 log.debug("allow process:"+line.substring(pos1),null,null);
826 tkns=new StringTokenizer(line.substring(pos1));
827 while (tkns.hasMoreTokens())
828 {
829 _allowList.add(tkns.nextToken());
830 }
831 }
832 else if (line.startsWith("deny from"))
833 {
834 int pos1=9;
835 limit=line.length();
836 while ((pos1<limit)&&(line.charAt(pos1)<=' '))
837 pos1++;
838 if (log.isDebugEnabled())
839 log.debug("deny process:"+line.substring(pos1),null,null);
840
841 tkns=new StringTokenizer(line.substring(pos1));
842 while (tkns.hasMoreTokens())
843 {
844 _denyList.add(tkns.nextToken());
845 }
846 }
847 else if (line.startsWith("</Limit>"))
848 break;
849 }
850 }
851 }
852 }
853 }
854
855
856
857
858
859
860 protected Handler getProtegee()
861 {
862 return this.protegee;
863 }
864
865
866
867
868
869
870
871 public void setProtegee(Handler protegee)
872 {
873 this.protegee=protegee;
874 }
875
876 }