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