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