
    iQJ                     B   d Z ddlZddlmZ ddlmZmZ ddlZddl	Z	 ddl
Zn# e$ r ddlZY nw xY wddlmZ  ej        e          Z G d de          Ze G d d	                      Zd
ededefdZdedeeedz  f         fdZdededz  fdZ	 d"dedededeeeeeef                  fdZd"dededededz  fdZ	 d#dedededededz  f
dZ	 	 	 	 	 d$dedededededededz  fdZ	 	 	 	 d%dededededededz  fd Z 	 	 	 	 d%dededededededz  fd!Z!dS )&a3  
RFC 6764 - Locating Services for Calendaring and Contacts (CalDAV/CardDAV)

This module implements DNS-based service discovery for CalDAV and CardDAV
servers as specified in RFC 6764. It allows clients to discover service
endpoints from just a domain name or email address.

Discovery methods (in order of preference):
1. DNS SRV records (_caldavs._tcp / _carddavs._tcp for TLS)
2. DNS TXT records (for path information)
3. Well-Known URIs (/.well-known/caldav or /.well-known/carddav)

SECURITY CONSIDERATIONS:
    DNS-based discovery is vulnerable to attacks if DNS is not secured with DNSSEC:

    - DNS Spoofing: Attackers can provide malicious SRV/TXT records pointing to
      attacker-controlled servers
    - Downgrade Attacks: Malicious DNS can specify non-TLS services, causing
      credentials to be sent in plaintext
    - Man-in-the-Middle: Even with HTTPS, attackers can redirect to their servers

    MITIGATIONS:
    - require_tls=True (DEFAULT): Only accept HTTPS connections, preventing
      downgrade attacks
    - ssl_verify_cert=True (DEFAULT): Verify TLS certificates per RFC 6125
    - Domain validation (RFC 6764 Section 8): Discovered hostnames must be
      in the same domain as the queried domain to prevent redirection attacks
    - Use DNSSEC when possible for DNS integrity
    - Manually verify discovered endpoints for sensitive applications
    - Consider certificate pinning for known domains

    For high-security environments, manual configuration may be preferable to
    automatic discovery.

See: https://datatracker.ietf.org/doc/html/rfc6764
    N)	dataclass)urljoinurlparse)DAVErrorc                       e Zd ZdZdS )DiscoveryErrorz#Raised when service discovery failsN)__name__
__module____qualname____doc__     K/root/projects/butler/venv/lib/python3.11/site-packages/caldav/discovery.pyr   r   8   s        --Dr   r   c                       e Zd ZU dZeed<   eed<   eed<   eed<   eed<   dZeed<   dZ	eed	<   d
Z
eed<   dZedz  ed<   defdZdS )ServiceInfoz5Information about a discovered CalDAV/CardDAV serviceurlhostnameportpathtlsr   priorityweightunknownsourceNusernamereturnc           	      H    d| j          d| j         d| j         d| j         d	S )NzServiceInfo(url=z	, source=z, priority=z, username=))r   r   r   r   )selfs    r   __str__zServiceInfo.__str__L   s5    w$(wwT[wwT]wwgkgtwwwwr   )r	   r
   r   r   str__annotations__intboolr   r   r   r   r    r   r   r   r   r   >   s         ??	HHHMMM
III
III	IIIHcFCOOOFCHcDjx x x x x x xr   r   discovered_domainoriginal_domainr   c                     |                                                      d          }|                                                     d          }||k    rdS |                    d|z             rdS dS )a  
    Check if discovered domain is the same as or a subdomain of the original domain.

    This prevents DNS hijacking attacks where malicious DNS records redirect
    to completely different domains (e.g., acme.com -> evil.hackers.are.us).

    Args:
        discovered_domain: The hostname discovered via DNS SRV/TXT or well-known URI
        original_domain: The domain from the user's identifier

    Returns:
        True if discovered_domain is safe (same domain or subdomain), False otherwise

    Examples:
        >>> _is_subdomain_or_same('calendar.example.com', 'example.com')
        True
        >>> _is_subdomain_or_same('example.com', 'example.com')
        True
        >>> _is_subdomain_or_same('evil.com', 'example.com')
        False
        >>> _is_subdomain_or_same('subdomain.calendar.example.com', 'example.com')
        True
        >>> _is_subdomain_or_same('exampleXcom.evil.com', 'example.com')
        False
    .TF)lowerstripendswith)r%   r&   
discoveredoriginals       r   _is_subdomain_or_samer.   P   sz    6 #((**0055J$$&&,,S11H Xt 3>** t5r   
identifierc                     d| v rt          |           }|j        p| dfS d| v rW|                     d          }|d         r|d                                         nd}|d                                         }||fS |                                 dfS )a
  
    Extract domain and optional username from an email address or URL.

    Args:
        identifier: Email address (user@example.com) or domain (example.com)

    Returns:
        A tuple of (domain, username) where username is None if not present

    Examples:
        >>> _extract_domain('user@example.com')
        ('example.com', 'user')
        >>> _extract_domain('example.com')
        ('example.com', None)
        >>> _extract_domain('https://caldav.example.com/path')
        ('caldav.example.com', None)
    ://N@r   )r   r   splitr*   )r/   parsedpartsr   domains        r   _extract_domainr8   z   s    & 
*%%-:t44 j  %%',Qx958>>###Tr""!! %%r   txt_datac                     |                                  D ]_}d|v rY|                     dd          \  }}|                                                                dk    r|                                c S `dS )a  
    Parse TXT record data to extract the path attribute.

    According to RFC 6764, TXT records contain attribute=value pairs.
    We're looking for the 'path' attribute.

    Args:
        txt_data: TXT record data (e.g., "path=/caldav/")

    Returns:
        The path value or None if not found

    Examples:
        >>> _parse_txt_record('path=/caldav/')
        '/caldav/'
        >>> _parse_txt_record('path=/caldav/ other=value')
        '/caldav/'
    =   r   N)r4   r*   r)   )r9   pairkeyvalues       r   _parse_txt_recordr@      sv    (    % %$;;C++JCyy{{  ""f,,{{}}$$$4r   Tr7   service_typeuse_tlsc                    |rdnd}d| | d|  }t                               d|            	 t          j                            |d          }g }|D ]}t          |j                                      d          }t          |j	                  }	t          |j
                  }
t          |j                  }t                               d| d	|	 d
|
 d| d	           |                    ||	|
|f           |                    d            |S # t          j        j        t          j        j        t          j        j        f$ r,}t                               d| d|            g cY d}~S d}~ww xY w)an  
    Perform DNS SRV record lookup.

    Args:
        domain: The domain to query
        service_type: Either 'caldav' or 'carddav'
        use_tls: If True, query for TLS service (_caldavs), else non-TLS (_caldav)

    Returns:
        List of tuples: (hostname, port, priority, weight)
        Sorted by priority (lower is better), then randomized by weight
    s _._tcp.zPerforming SRV lookup for SRVr(   zFound SRV record: :z (priority=z	, weight=r   c                 $    | d         | d          fS )N      r   )xs    r   <lambda>z_srv_lookup.<locals>.<lambda>   s    AaD1Q4%= r   )r>   zSRV lookup failed for : N)logdebugdnsresolverresolver!   targetrstripr#   r   r   r   appendsortNXDOMAINNoAnswer	exceptionDNSException)r7   rA   rB   service_suffixsrv_nameanswersresultsrdatar   r   r   r   es                r   _srv_lookuprc      s   " $+SSN?<???v??HII5855666,&&x77 	? 	?E5<((//44Huz??D5>**H&&FIIc8ccdccxccZ`cccdddNNHdHf=>>>> 	00111 	"   
 			:8::q::;;;						s   C"D 5E3!E.(E3.E3c                    |rdnd}d| | d|  }t                               d|            	 t          j                            |d          }|D ]X}d                    d |j        D                       }t                               d|            t          |          }|r|c S Ynb# t          j        j        t          j        j	        t          j
        j        f$ r*}	t                               d	| d
|	            Y d}	~	nd}	~	ww xY wdS )a?  
    Perform DNS TXT record lookup to find the service path.

    Args:
        domain: The domain to query
        service_type: Either 'caldav' or 'carddav'
        use_tls: If True, query for TLS service (_caldavs), else non-TLS (_caldav)

    Returns:
        The path from the TXT record, or None if not found
    rD   rE   rF   rG   zPerforming TXT lookup for TXTc                 f    g | ].}t          |t                    r|                    d           n|/S )zutf-8)
isinstancebytesdecode).0rD   s     r   
<listcomp>z_txt_lookup.<locals>.<listcomp>   s8    YYYajE&:&:A'"""YYYr   zFound TXT record: zTXT lookup failed for rO   N)rP   rQ   rR   rS   rT   joinstringsr@   rY   rZ   r[   r\   )
r7   rA   rB   r]   txt_namer_   ra   r9   r   rb   s
             r   _txt_lookupro      sS    $+SSN?<???v??HII5855666<,&&x77 		 		EwwYY5=YYY H II5855666$X..D 		 	" < < <
 			:8::q::;;;;;;;;< 4s   A9B+ )B+ +5D
  DD

   timeoutssl_verify_certc                 .   d| }d|  | }t                               d|            	 t          j        |||d          }|j        dv r|j                            d          }|rt                               d|            t          ||          }t          |          }	|	j        p| }
t          |
|           s#t           
                    d	|  d
|
 d           dS t          ||
|	j        p|	j        dk    rdnd|	j        pd|	j        dk    d          S |j        dk    r2t                               d|            t          || d|dd          S n># t          j        j        $ r'}t                               d|            Y d}~nd}~ww xY wdS )a8  
    Try to discover service via Well-Known URI (RFC 5785).

    According to RFC 6764, if SRV/TXT lookup fails, clients should try:
    - https://domain/.well-known/caldav
    - https://domain/.well-known/carddav

    Security: Redirects to different domains are validated per RFC 6764 Section 8.

    Args:
        domain: The domain to query
        service_type: Either 'caldav' or 'carddav'
        timeout: Request timeout in seconds
        ssl_verify_cert: Whether to verify SSL certificates

    Returns:
        ServiceInfo if successful, None otherwise
    z/.well-known/zhttps://zTrying well-known URI: F)rq   verifyallow_redirects)i-  i.  i/  i3  i4  LocationzWell-known URI redirected to: zVRFC 6764 Security: Rejecting well-known redirect to different domain. Queried domain: z, Redirect target: z&. Ignoring this redirect for security.Nhttps  P   /z
well-known)r   r   r   r   r   r      z(Well-known URI is the service endpoint: TzWell-known URI lookup failed: )rP   rQ   requestsgetstatus_codeheadersr   r   r   r.   warningr   r   schemer   
exceptionsRequestException)r7   rA   rq   rr   well_known_pathr   responselocation	final_urlr5   redirect_hostnamerb   s               r   _well_known_lookupr     s7   * 5l44O
.V
._
.
.CII---...48 <"!	
 
 
 #<<<'++J77H 		E8EEFFF $C22	!),,$*O$=v! -->GG  KK@+1@ @FW@ @ @  
  4"!.Q0H0Hb+0'    3&&IIFFFGGG$#    ' / 8 8 8		6166777777778 4s$   B3E  8E <E F+FFcaldav
prefer_tlsrequire_tlsc                 (   |dvrt          d| d          t          |           \  }}t                              d| d|            |rt                              d|            |rdg}t                              d	           n$|rdd
gnd
dg}t                              d           |D ]}	t          |||	          }
|
r|
d         \  }}}}t          ||          s"t                              d| d| d           Tt          |||	          }|st                              d           d}|	rdnd}|	rdnd}||k    r| d| d| | }n	| d| | }t                              d| d|            t          |||||	||d|	  	        c S t                              d           t          ||||          }|r.||_        t                              d| d|j                    |S t                              d| d|            d S )!a  
    Discover CalDAV or CardDAV service for a domain or email address.

    This is the main entry point for RFC 6764 service discovery.
    It tries multiple methods in order:
    1. DNS SRV records (with TLS preferred)
    2. DNS TXT records for path information
    3. Well-Known URIs as fallback

    SECURITY WARNING:
        RFC 6764 discovery relies on DNS, which can be spoofed if not using DNSSEC.
        An attacker controlling DNS could:
        - Redirect connections to a malicious server
        - Downgrade from HTTPS to HTTP to capture credentials
        - Perform man-in-the-middle attacks

        By default, require_tls=True prevents HTTP downgrade attacks.
        For production use, consider:
        - Using DNSSEC-validated domains
        - Manual verification of discovered endpoints
        - Pinning certificates for known domains

    Args:
        identifier: Domain name (example.com) or email address (user@example.com)
        service_type: Either 'caldav' or 'carddav'
        timeout: Timeout for HTTP requests in seconds
        ssl_verify_cert: Whether to verify SSL certificates
        prefer_tls: If True, try TLS services first (only used if require_tls=False)
        require_tls: If True (default), ONLY accept TLS connections. This prevents
                     DNS-based downgrade attacks to plaintext HTTP. Set to False
                     only if you explicitly need to support non-TLS servers and
                     trust your DNS infrastructure.

    Returns:
        ServiceInfo object with discovered service details, or None if discovery fails

    Raises:
        DiscoveryError: If service_type is invalid

    Examples:
        >>> info = discover_service('user@example.com', 'caldav')
        >>> if info:
        ...     print(f"Service URL: {info.url}")

        >>> # Allow non-TLS (INSECURE - only for testing)
        >>> info = discover_service('user@example.com', 'caldav', require_tls=False)
    )r   carddavzInvalid service_type: z. Must be 'caldav' or 'carddav')reasonzDiscovering z service for domain: z$Username extracted from identifier: Tz/require_tls=True: Only attempting TLS discoveryFz:require_tls=False: Allowing non-TLS connections (INSECURE)r   zVRFC 6764 Security: Rejecting SRV record pointing to different domain. Queried domain: z, Target FQDN: z6. This may indicate DNS hijacking or misconfiguration.z$No TXT record found, using root pathrz   rw   httprx   ry   r1   rI   zDiscovered z service via SRV: srv)	r   r   r   r   r   r   r   r   r   z(SRV lookup failed, trying well-known URIz service via well-known URI: zFailed to discover z service for N)r   r8   rP   inforQ   r   rc   r.   ro   r   r   r   r   )r/   rA   rq   rr   r   r   r7   r   tls_optionsrB   srv_recordsr   r   r   r   r   r   default_portr   well_known_infos                       r   discover_servicer   d  s   n 000YLYYY
 
 
 	
 'z22FHHHGLGGvGGHHH E		CCCDDD  Rf		CDDDD (2DtUmmt}PQQQ . .!&,@@ +	/:1~,HdHf
 )6:: L'-L L>FL L L  
  v|W==D 		@AAA !(3WWVF")133rL|##;;H;;t;T;;44H4d44HHH<HH3HHIII!!!
 
 
 
 
 
C+	\ II8999(wXXO #+ _|__/J]__``` KKIlIIIIJJJ4r   c                 ,    t          | d||||          S )a  
    Convenience function to discover CalDAV service.

    Args:
        identifier: Domain name or email address
        timeout: Timeout for HTTP requests in seconds
        ssl_verify_cert: Whether to verify SSL certificates
        prefer_tls: If True, try TLS services first
        require_tls: If True (default), only accept TLS connections

    Returns:
        ServiceInfo object or None
    r   r/   rA   rq   rr   r   r   r   r/   rq   rr   r   r   s        r   discover_caldavr     s-    ( '   r   c                 ,    t          | d||||          S )a  
    Convenience function to discover CardDAV service.

    Args:
        identifier: Domain name or email address
        timeout: Timeout for HTTP requests in seconds
        ssl_verify_cert: Whether to verify SSL certificates
        prefer_tls: If True, try TLS services first
        require_tls: If True (default), only accept TLS connections

    Returns:
        ServiceInfo object or None
    r   r   r   r   s        r   discover_carddavr     s-    ( '   r   )T)rp   T)r   rp   TTT)rp   TTT)"r   loggingdataclassesr   urllib.parser   r   dns.exceptionrR   dns.resolverniquestsr|   ImportErrorcaldav.lib.errorr   	getLoggerr	   rP   r   r   r!   r$   r.   tupler8   r@   listr#   rc   ro   r   r   r   r   r   r   r   <module>r      s  # #J  ! ! ! ! ! ! * * * * * * * *           OOOOO & % % % % %g!!	 	 	 	 	X 	 	 	 x x x x x x x x"'S '3 '4 ' ' ' 'T& &c3:o(> & & & &D d
    : 59- --"--1-	%S#s"
#$- - - -`& & &3 & &t & & & &T PTP PP"P-0PHLP4P P P Pj ! G GGG G 	G
 G G 4G G G GX     	
  4   @     	
  4     s   # 	//