├── CONTRIBUTING.md ├── LICENSE.txt ├── PULL_REQUEST_TEMPLATE.md ├── duplicateguids.py ├── pysrc ├── dsattributes.py ├── dsquery.py └── opendirectory.py ├── setup.py ├── src ├── CDirectoryService.cpp ├── CDirectoryService.h ├── CDirectoryServiceAuth.cpp ├── CDirectoryServiceAuth.h ├── CDirectoryServiceException.cpp ├── CDirectoryServiceException.h ├── CDirectoryServiceManager.cpp ├── CDirectoryServiceManager.h ├── CFStringUtil.cpp ├── CFStringUtil.h ├── PythonWrapper.cpp ├── base64.cpp └── base64.h ├── support ├── PyOpenDirectory.xcodeproj │ ├── cyrusdaboo.mode1v3 │ ├── cyrusdaboo.pbxuser │ └── project.pbxproj └── test.cpp ├── test.py └── test_auth.py /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | By submitting a request, you represent that you have the right to license 2 | your contribution to Apple and the community, and agree that your 3 | contributions are licensed under the [Apache License Version 2.0](LICENSE.txt). 4 | 5 | For existing files modified by your request, you represent that you have 6 | retained any existing copyright notices and licensing terms. For each new 7 | file in your request, you represent that you have added to the file a 8 | copyright notice (including the year and the copyright owner's name) and the 9 | Calendar and Contacts Server's licensing terms. 10 | 11 | Before submitting the request, please make sure that your request follows 12 | the [Calendar and Contacts Server's guidelines for contributing 13 | code](../../../ccs-calendarserver/blob/master/HACKING.rst). 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | By submitting a request, you represent that you have the right to license 2 | your contribution to Apple and the community, and agree that your 3 | contributions are licensed under the [Apache License Version 2.0](LICENSE.txt). 4 | 5 | For existing files modified by your request, you represent that you have 6 | retained any existing copyright notices and licensing terms. For each new 7 | file in your request, you represent that you have added to the file a 8 | copyright notice (including the year and the copyright owner's name) and the 9 | Calendar and Contacts Server's licensing terms. 10 | 11 | Before submitting the request, please make sure that your request follows 12 | the [Calendar and Contacts Server's guidelines for contributing 13 | code](../../../ccs-calendarserver/blob/master/HACKING.rst). 14 | -------------------------------------------------------------------------------- /duplicateguids.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2006-2009 Apple Inc. All rights reserved. 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 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ## 16 | 17 | import opendirectory 18 | import dsattributes 19 | from dsquery import expression, match 20 | 21 | try: 22 | ref = opendirectory.odInit("/Search") 23 | if ref is None: 24 | print "Failed odInit" 25 | else: 26 | print "OK odInit" 27 | 28 | guids = {} 29 | for recordType in ( 30 | dsattributes.kDSStdRecordTypeUsers, 31 | dsattributes.kDSStdRecordTypeGroups, 32 | dsattributes.kDSStdRecordTypePlaces, 33 | dsattributes.kDSStdRecordTypeResources): 34 | 35 | d = opendirectory.listAllRecordsWithAttributes(ref, recordType, 36 | ( 37 | dsattributes.kDS1AttrGeneratedUID, 38 | )) 39 | 40 | for name, attrs in d.iteritems(): 41 | name = "%s/%s" % (recordType, name,) 42 | try: 43 | guid = attrs[dsattributes.kDS1AttrGeneratedUID] 44 | if guid in guids: 45 | print "Duplicate GUIDs for %s and %s: %s" % (guids[guid], name, guid,) 46 | else: 47 | guids[guid] = name 48 | except KeyError: 49 | print "No GUID for %s" % (name,) 50 | 51 | ref = None 52 | except opendirectory.ODError, ex: 53 | print ex 54 | except Exception, e: 55 | print e 56 | 57 | print "Done." 58 | -------------------------------------------------------------------------------- /pysrc/dsquery.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2006-2008 Apple Inc. All rights reserved. 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 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | ## 17 | 18 | """ 19 | Compound query builder. We do this in Python to avoid having to mess 20 | with pass a complex Python object hierarchy into C. These classes allow us to 21 | build the query in Python and generate the compound query string that the directory 22 | service C api requires. 23 | """ 24 | 25 | import dsattributes 26 | 27 | class match(object): 28 | """ 29 | Represents and attribute/value match operation. 30 | """ 31 | 32 | def __init__(self, attribute, value, matchType): 33 | self.attribute = attribute 34 | self.value = value 35 | self.matchType = matchType 36 | 37 | def generate(self): 38 | return { 39 | dsattributes.eDSExact : "(%s=%s)", 40 | dsattributes.eDSStartsWith : "(%s=%s*)", 41 | dsattributes.eDSEndsWith : "(%s=*%s)", 42 | dsattributes.eDSContains : "(%s=*%s*)", 43 | dsattributes.eDSLessThan : "(%s<%s)", 44 | dsattributes.eDSGreaterThan : "(%s>%s)", 45 | }.get(self.matchType, "(%s=*%s*)") % (self.attribute, self.value,) 46 | 47 | class expression(object): 48 | """ 49 | Represents a query expression that includes a boolean operator, and a list 50 | of sub-expressions operated on. The sub-expressions can either be another expression 51 | object or a match object. 52 | """ 53 | 54 | AND = "&" 55 | OR = "|" 56 | NOT = "!" 57 | 58 | def __init__(self, operator, subexpressions): 59 | assert(operator == expression.AND or operator == expression.OR or operator == expression.NOT) 60 | self.operator = operator 61 | self.subexpressions = subexpressions 62 | 63 | def generate(self): 64 | result = "" 65 | if self.operator == expression.NOT: 66 | result += "(" 67 | result += self.operator 68 | result += self.subexpressions.generate() 69 | result += ")" 70 | else: 71 | if len(self.subexpressions) > 1: 72 | result += "(" 73 | result += self.operator 74 | for sub in self.subexpressions: 75 | result += sub.generate() 76 | if len(self.subexpressions) > 1: 77 | result += ")" 78 | return result 79 | 80 | 81 | # Do some tests 82 | if __name__=='__main__': 83 | exprs = ( 84 | (expression( 85 | expression.AND, ( 86 | expression(expression.OR, (match("ResourceType", "xyz", dsattributes.eDSExact), match("ResourceType", "abc", dsattributes.eDSExact))), 87 | match("ServicesLocator", "GUID:VGUID:calendar", dsattributes.eDSStartsWith), 88 | ) 89 | ), "(&(|(ResourceType=xyz)(ResourceType=abc))(ServicesLocator=GUID:VGUID:calendar*))"), 90 | (expression( 91 | expression.AND, ( 92 | expression(expression.OR, (match("ResourceType", "xyz", dsattributes.eDSStartsWith), match("ResourceType", "abc", dsattributes.eDSEndsWith))), 93 | match("ServicesLocator", "GUID:VGUID:calendar", dsattributes.eDSContains), 94 | ) 95 | ), "(&(|(ResourceType=xyz*)(ResourceType=*abc))(ServicesLocator=*GUID:VGUID:calendar*))"), 96 | (expression( 97 | expression.AND, ( 98 | expression(expression.AND, (match("ResourceType", "xyz", dsattributes.eDSLessThan), match("ResourceType", "abc", dsattributes.eDSGreaterThan))), 99 | match("ServicesLocator", "GUID:VGUID:calendar", 0xBAD), 100 | ) 101 | ), "(&(&(ResourceTypeabc))(ServicesLocator=*GUID:VGUID:calendar*))"), 102 | (expression( 103 | expression.AND, ( 104 | match("ServicesLocator", "GUID:VGUID:calendar", 0xBAD), 105 | ) 106 | ), "(ServicesLocator=*GUID:VGUID:calendar*)"), 107 | (expression( 108 | expression.NOT, match(dsattributes.kDSNAttrNickName, "", dsattributes.eDSStartsWith ) 109 | ), "(!(" + dsattributes.kDSNAttrNickName + "=*))"), 110 | (expression( 111 | expression.AND, ( 112 | expression( 113 | expression.NOT, match(dsattributes.kDSNAttrNickName, "Billy", dsattributes.eDSContains ) 114 | ), 115 | expression( 116 | expression.NOT, match(dsattributes.kDSNAttrEMailAddress, "Billy", dsattributes.eDSContains ) 117 | ), 118 | ), 119 | ), "(&(!(" + dsattributes.kDSNAttrNickName + "=*Billy*))(!(" + dsattributes.kDSNAttrEMailAddress + "=*Billy*)))"), 120 | (expression( 121 | expression.NOT, expression( 122 | expression.OR, ( 123 | match(dsattributes.kDSNAttrNickName, "", dsattributes.eDSStartsWith ), 124 | match(dsattributes.kDSNAttrEMailAddress, "", dsattributes.eDSStartsWith ), 125 | ), 126 | ), 127 | ), "(!(|("+dsattributes.kDSNAttrNickName+"=*)("+dsattributes.kDSNAttrEMailAddress+"=*)))"), 128 | ) 129 | 130 | for expr, result in exprs: 131 | gen = expr.generate() 132 | if gen != result: 133 | print "Generate expression %s != %s" % (gen, result,) 134 | print "Done." 135 | -------------------------------------------------------------------------------- /pysrc/opendirectory.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2006-2009 Apple Inc. All rights reserved. 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 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ## 16 | 17 | """ 18 | PyOpenDirectory Function Description. 19 | """ 20 | 21 | def odInit(nodename): 22 | """ 23 | Create an Open Directory object to operate on the specified directory service node name. 24 | 25 | @param nodename: C{str} containing the node name. 26 | @return: C{object} an object to be passed to all subsequent functions on success, 27 | C{None} on failure. 28 | """ 29 | 30 | def listNodes(obj): 31 | """ 32 | List all the nodes current configured in Open Directory. 33 | 34 | @param obj: C{object} the object obtained from an odInit call. 35 | @return: C{list} containing a C{str} for each configured node. 36 | """ 37 | 38 | def getNodeAttributes(obj, nodename, attributes): 39 | """ 40 | Return key attributes for the specified directory node. The attributes 41 | can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 42 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 43 | 44 | @param obj: C{object} the object obtained from an odInit call. 45 | @param nodename: C{str} containing the OD nodename to query. 46 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 47 | @return: C{dict} of attributes found. 48 | """ 49 | 50 | def listAllRecordsWithAttributes(obj, recordType, attributes, count=0): 51 | """ 52 | List records in Open Directory, and return key attributes for each one. 53 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 54 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 55 | 56 | @param obj: C{object} the object obtained from an odInit call. 57 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 58 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 59 | @param count: C{int} maximum number of records to return (zero returns all). 60 | @return: C{dict} containing a C{dict} of attributes for each record found, 61 | or C{None} otherwise. 62 | """ 63 | 64 | def queryRecordsWithAttribute(obj, attr, value, matchType, casei, recordType, attributes, count=0): 65 | """ 66 | List records in Open Directory matching specified attribute/value, and return key attributes for each one. 67 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 68 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 69 | 70 | @param obj: C{object} the object obtained from an odInit call. 71 | @param attr: C{str} containing the attribute to search. 72 | @param value: C{str} containing the value to search for. 73 | @param matchType: C{int} DS match type to use when searching. 74 | @param casei: C{True} to do case-insensitive match, C{False} otherwise. 75 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 76 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 77 | @param count: C{int} maximum number of records to return (zero returns all). 78 | @return: C{dict} containing a C{dict} of attributes for each record found, 79 | or C{None} otherwise. 80 | """ 81 | 82 | def queryRecordsWithAttributes(obj, compound, casei, recordType, attributes, count=0): 83 | """ 84 | List records in Open Directory matching specified criteria, and return key attributes for each one. 85 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 86 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 87 | 88 | @param obj: C{object} the object obtained from an odInit call. 89 | @param compound: C{str} containing the compound search query to use. 90 | @param casei: C{True} to do case-insensitive match, C{False} otherwise. 91 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 92 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 93 | @param count: C{int} maximum number of records to return (zero returns all). 94 | @return: C{dict} containing a C{dict} of attributes for each record found, 95 | or C{None} otherwise. 96 | """ 97 | 98 | def listAllRecordsWithAttributes_list(obj, recordType, attributes, count=0): 99 | """ 100 | List records in Open Directory, and return key attributes for each one. 101 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 102 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 103 | 104 | @param obj: C{object} the object obtained from an odInit call. 105 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 106 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 107 | @param count: C{int} maximum number of records to return (zero returns all). 108 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 109 | for each record found, or C{None} otherwise. 110 | """ 111 | 112 | def queryRecordsWithAttribute_list(obj, attr, value, matchType, casei, recordType, attributes, count=0): 113 | """ 114 | List records in Open Directory matching specified attribute/value, and return key attributes for each one. 115 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 116 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 117 | 118 | @param obj: C{object} the object obtained from an odInit call. 119 | @param attr: C{str} containing the attribute to search. 120 | @param value: C{str} containing the value to search for. 121 | @param matchType: C{int} DS match type to use when searching. 122 | @param casei: C{True} to do case-insensitive match, C{False} otherwise. 123 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 124 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 125 | @param count: C{int} maximum number of records to return (zero returns all). 126 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 127 | for each record found, or C{None} otherwise. 128 | """ 129 | 130 | def queryRecordsWithAttributes_list(obj, compound, casei, recordType, attributes, count=0): 131 | """ 132 | List records in Open Directory matching specified criteria, and return key attributes for each one. 133 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 134 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 135 | 136 | @param obj: C{object} the object obtained from an odInit call. 137 | @param compound: C{str} containing the compound search query to use. 138 | @param casei: C{True} to do case-insensitive match, C{False} otherwise. 139 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 140 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 141 | @param count: C{int} maximum number of records to return (zero returns all). 142 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 143 | for each record found, or C{None} otherwise. 144 | """ 145 | 146 | def authenticateUserBasic(obj, nodename, user, pswd): 147 | """ 148 | Authenticate a user with a password to Open Directory. 149 | 150 | @param obj: C{object} the object obtained from an odInit call. 151 | @param nodename: C{str} the directory nodename for the record to check. 152 | @param user: C{str} the user identifier/directory record name to check. 153 | @param pswd: C{str} containing the password to check. 154 | @return: C{True} if the user was found, C{False} otherwise. 155 | """ 156 | 157 | def authenticateUserDigest(obj, nodename, user, challenge, response, method): 158 | """ 159 | Authenticate using HTTP Digest credentials to Open Directory. 160 | 161 | @param obj: C{object} the object obtained from an odInit call. 162 | @param nodename: C{str} the directory nodename for the record to check. 163 | @param user: C{str} the user identifier/directory record name to check. 164 | @param challenge: C{str} the HTTP challenge sent to the client. 165 | @param response: C{str} the HTTP response sent from the client. 166 | @param method: C{str} the HTTP method being used. 167 | @return: C{True} if the user was found, C{False} otherwise. 168 | """ 169 | 170 | class ODError(Exception): 171 | """ 172 | Exceptions from DirectoryServices errors. 173 | """ 174 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2006-2008 Apple Inc. All rights reserved. 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 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ## 16 | 17 | from distutils.core import setup, Extension 18 | import sys 19 | 20 | if sys.platform in ["darwin", "macosx"]: 21 | 22 | """ 23 | On Mac OS X we build the actual Python module linking to the 24 | DirectoryService.framework. 25 | """ 26 | 27 | module1 = Extension( 28 | 'opendirectory', 29 | extra_link_args = ['-framework', 'DirectoryService', "-framework", "CoreFoundation"], 30 | sources = [ 31 | 'src/PythonWrapper.cpp', 32 | 'src/CDirectoryServiceManager.cpp', 33 | 'src/CDirectoryService.cpp', 34 | 'src/CDirectoryServiceAuth.cpp', 35 | 'src/CDirectoryServiceException.cpp', 36 | 'src/CFStringUtil.cpp', 37 | 'src/base64.cpp', 38 | ], 39 | ) 40 | 41 | setup ( 42 | name = 'opendirectory', 43 | version = '1.0', 44 | description = 'This is a high-level interface to Open Directory for operations specific to a CalDAV server.', 45 | ext_modules = [module1], 46 | package_dir={'': 'pysrc'}, 47 | py_modules = ['dsattributes', 'dsquery',] 48 | ) 49 | 50 | else: 51 | """ 52 | On other OS's we simply include a stub file of prototypes. 53 | Eventually we should build the proper module and link 54 | with appropriate local ldap etc libraries. 55 | """ 56 | 57 | setup ( 58 | name = 'opendirectory', 59 | version = '1.0', 60 | description = 'This is a high-level interface to the Kerberos.framework', 61 | package_dir={'': 'pysrc'}, 62 | packages=[''] 63 | ) 64 | -------------------------------------------------------------------------------- /src/CDirectoryService.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2009 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | class CFStringUtil; 27 | 28 | class CDirectoryService 29 | { 30 | public: 31 | CDirectoryService(const char* nodename); 32 | virtual ~CDirectoryService(); 33 | 34 | CFMutableArrayRef ListNodes(bool using_python=true); 35 | CFMutableDictionaryRef GetNodeAttributes(const char* nodename, CFDictionaryRef attributes, bool using_python=true); 36 | 37 | CFMutableArrayRef ListAllRecordsWithAttributes(CFArrayRef recordTypes, CFDictionaryRef attributes, UInt32 maxRecordCount=0, bool using_python=true); 38 | CFMutableArrayRef QueryRecordsWithAttribute(const char* attr, const char* value, int matchType, bool casei, CFArrayRef recordTypes, CFDictionaryRef attributes, UInt32 maxRecordCount=0, bool using_python=true); 39 | CFMutableArrayRef QueryRecordsWithAttributes(const char* query, bool casei, CFArrayRef recordTypes, CFDictionaryRef attributes, UInt32 maxRecordCount=0, bool using_python=true); 40 | 41 | protected: 42 | 43 | class StPythonThreadState 44 | { 45 | public: 46 | StPythonThreadState(bool using_python=true) 47 | { 48 | if (using_python) 49 | mSavedState = PyEval_SaveThread(); 50 | else 51 | mSavedState = NULL; 52 | } 53 | 54 | ~StPythonThreadState() 55 | { 56 | if (mSavedState != NULL) 57 | PyEval_RestoreThread(mSavedState); 58 | } 59 | 60 | private: 61 | PyThreadState* mSavedState; 62 | }; 63 | 64 | const char* mNodeName; 65 | tDirReference mDir; 66 | tDirNodeReference mNode; 67 | tDataBufferPtr mData; 68 | UInt32 mDataSize; 69 | 70 | CFMutableArrayRef _ListNodes(); 71 | CFMutableDictionaryRef _GetNodeAttributes(const char* nodename, CFDictionaryRef attributes); 72 | 73 | CFMutableArrayRef _ListAllRecordsWithAttributes(CFArrayRef recordTypes, CFArrayRef names, CFDictionaryRef attrs, UInt32 maxRecordCount); 74 | CFMutableArrayRef _QueryRecordsWithAttributes(const char* attr, const char* value, int matchType, const char* compound, bool casei, CFArrayRef recordTypes, CFDictionaryRef attrs, UInt32 maxRecordCount); 75 | 76 | virtual void OpenService(); 77 | virtual void CloseService(); 78 | 79 | void OpenNode(); 80 | virtual tDirNodeReference OpenNamedNode(const char* nodename); 81 | void CloseNode(); 82 | 83 | void CreateBuffer(); 84 | void RemoveBuffer(); 85 | void ReallocBuffer(); 86 | 87 | void BuildStringDataList(CFArrayRef strs, tDataListPtr data); 88 | void BuildStringDataListFromKeys(CFDictionaryRef strs, tDataListPtr data); 89 | 90 | char* CStringFromBuffer(tDataBufferPtr data); 91 | char* CStringBase64FromBuffer(tDataBufferPtr data); 92 | char* CStringFromData(const char* data, size_t len); 93 | }; 94 | -------------------------------------------------------------------------------- /src/CDirectoryServiceAuth.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #include "CDirectoryServiceAuth.h" 21 | 22 | #include "CDirectoryServiceException.h" 23 | 24 | #ifndef kDSStdAuthSASLProxy 25 | #define kDSStdAuthSASLProxy "dsAuthMethodStandard:dsAuthSASLProxy" 26 | #endif 27 | #define kSASLDIGESTMD5 "DIGEST-MD5" 28 | 29 | #pragma mark -----Public API 30 | 31 | CDirectoryServiceAuth::CDirectoryServiceAuth() : 32 | CDirectoryService("") 33 | { 34 | } 35 | 36 | CDirectoryServiceAuth::~CDirectoryServiceAuth() 37 | { 38 | // Clean-up 39 | CloseService(); 40 | } 41 | 42 | // AuthenticateUserBasic 43 | // 44 | // Authenticate a user to the directory using plain text credentials. 45 | // 46 | // @param nodename: the directory nodename for the user record. 47 | // @param user: the identifier/directory record name of the user. 48 | // @param pswd: the plain text password to authenticate with. 49 | // @return: true if authentication succeeds, false otherwise. 50 | // 51 | bool CDirectoryServiceAuth::AuthenticateUserBasic(const char* nodename, const char* user, const char* pswd, bool& result, bool using_python) 52 | { 53 | try 54 | { 55 | StPythonThreadState threading(using_python); 56 | 57 | result = NativeAuthenticationBasicToNode(nodename, user, pswd); 58 | return true; 59 | } 60 | catch(CDirectoryServiceException& dserror) 61 | { 62 | if (using_python) 63 | dserror.SetPythonException(); 64 | return false; 65 | } 66 | catch(...) 67 | { 68 | CDirectoryServiceException dserror; 69 | if (using_python) 70 | dserror.SetPythonException(); 71 | return false; 72 | } 73 | } 74 | 75 | // AuthenticateUserDigest 76 | // 77 | // Authenticate a user to the directory using HTTP DIGEST credentials. 78 | // 79 | // @param nodename: the directory nodename for the user record. 80 | // @param user: the identifier/directory record name of the user. 81 | // @param challenge: HTTP challenge sent by server. 82 | // @param response: HTTP response sent by client. 83 | // @return: true if authentication succeeds, false otherwise. 84 | // 85 | bool CDirectoryServiceAuth::AuthenticateUserDigest(const char* nodename, const char* user, const char* challenge, const char* response, const char* method, bool& result, bool using_python) 86 | { 87 | try 88 | { 89 | StPythonThreadState threading(using_python); 90 | 91 | result = NativeAuthenticationDigestToNode(nodename, user, challenge, response, method); 92 | return true; 93 | } 94 | catch(CDirectoryServiceException& dserror) 95 | { 96 | if (using_python) 97 | dserror.SetPythonException(); 98 | return false; 99 | } 100 | catch(...) 101 | { 102 | CDirectoryServiceException dserror; 103 | if (using_python) 104 | dserror.SetPythonException(); 105 | return false; 106 | } 107 | } 108 | 109 | // AuthenticateUserDigestToActiveDirectory 110 | // 111 | // Authenticate a user to the directory using SASL Digest credentials. 112 | // 113 | // @param nodename: the directory nodename for the user record. 114 | // @param user: the identifier/directory record name of the user. 115 | // @param response: HTTP response sent by client. 116 | // @return: true if authentication succeeds, false otherwise. 117 | // 118 | bool CDirectoryServiceAuth::AuthenticateUserDigestToActiveDirectory(const char* nodename, const char* user, const char* response, bool& result, bool using_python) 119 | { 120 | try 121 | { 122 | StPythonThreadState threading(using_python); 123 | 124 | result = NativeAuthenticationSASLDigestToNode(nodename, user, response); 125 | return true; 126 | } 127 | catch(CDirectoryServiceException& dserror) 128 | { 129 | if (using_python) 130 | dserror.SetPythonException(); 131 | return false; 132 | } 133 | catch(...) 134 | { 135 | CDirectoryServiceException dserror; 136 | if (using_python) 137 | dserror.SetPythonException(); 138 | return false; 139 | } 140 | } 141 | 142 | 143 | // GetDigestMD5ChallengeFromActiveDirectory 144 | // 145 | // Authenticate a user to the directory using SASL Digest credentials. 146 | // 147 | // @param nodename: the directory nodename for the user record. 148 | // @return: challange as CFStringRef 149 | // 150 | CFStringRef CDirectoryServiceAuth::GetDigestMD5ChallengeFromActiveDirectory(const char* nodename, bool using_python) 151 | { 152 | try 153 | { 154 | StPythonThreadState threading(using_python); 155 | CFStringRef challenge = NULL; 156 | (void) NativeAuthenticationSASLDigestToNode(nodename, "anonymous", "", &challenge); 157 | return challenge; 158 | } 159 | catch(CDirectoryServiceException& dserror) 160 | { 161 | if (using_python) 162 | dserror.SetPythonException(); 163 | return NULL; 164 | } 165 | catch(...) 166 | { 167 | CDirectoryServiceException dserror; 168 | if (using_python) 169 | dserror.SetPythonException(); 170 | return NULL; 171 | } 172 | } 173 | 174 | 175 | #pragma mark -----Private API 176 | 177 | // NativeAuthenticationBasicToNode 178 | // 179 | // Authenticate a user to the directory. 180 | // 181 | // @param nodename: the node to authenticate to. 182 | // @param user: the identifier/directory record name of the user. 183 | // @param pswd: the plain text password to authenticate with. 184 | // @return: true if authentication succeeds, false otherwise. 185 | // 186 | bool CDirectoryServiceAuth::NativeAuthenticationBasicToNode(const char* nodename, const char* user, const char* pswd) 187 | { 188 | bool result = false; 189 | tDirNodeReference node = 0L; 190 | tDataNodePtr authType = NULL; 191 | tDataBufferPtr authData = NULL; 192 | tContextData context = NULL; 193 | 194 | try 195 | { 196 | // Make sure we have a valid directory service 197 | OpenService(); 198 | 199 | // Open the node we want to query 200 | node = OpenNamedNode(nodename); 201 | 202 | CreateBuffer(); 203 | 204 | // First, specify the type of authentication. 205 | authType = ::dsDataNodeAllocateString(mDir, kDSStdAuthClearText); 206 | 207 | // Build input data 208 | // Native authentication is a one step authentication scheme. 209 | // Step 1 210 | // Send: 211 | // 212 | // Receive: success or failure. 213 | UInt32 aDataBufSize = sizeof(UInt32) + ::strlen(user) + sizeof(UInt32) + ::strlen(pswd); 214 | authData = ::dsDataBufferAllocate(mDir, aDataBufSize); 215 | if (authData == NULL) 216 | ThrowIfDSErr(eDSNullDataBuff); 217 | 218 | // Fill the buffer 219 | ::dsFillAuthBuffer(authData, 2, 220 | ::strlen(user), user, 221 | ::strlen(pswd), pswd); 222 | 223 | // Do authentication 224 | tDirStatus dirStatus = ::dsDoDirNodeAuth(node, authType, true, authData, mData, &context); 225 | result = (dirStatus == eDSNoErr); 226 | 227 | // Cleanup 228 | ::dsDataBufferDeAllocate(mDir, authData); 229 | authData = NULL; 230 | ::dsDataNodeDeAllocate(mDir, authType); 231 | authType = NULL; 232 | RemoveBuffer(); 233 | 234 | // If fatal error, force full reset 235 | if (not result and (dirStatus != eDSAuthFailed)) 236 | { 237 | CloseService(); 238 | } 239 | } 240 | catch(...) 241 | { 242 | // Cleanup 243 | if (authData != NULL) 244 | ::dsDataBufferDeAllocate(mDir, authData); 245 | if (authType != NULL) 246 | ::dsDataNodeDeAllocate(mDir, authType); 247 | RemoveBuffer(); 248 | CloseService(); 249 | 250 | throw; 251 | } 252 | 253 | return result; 254 | } 255 | 256 | // NativeAuthenticationDigestToNode 257 | // 258 | // Authenticate a user to the directory. 259 | // 260 | // @param nodename: the node to authenticate to. 261 | // @param user: the identifier/directory record name of the user. 262 | // @param challenge: the server challenge. 263 | // @param response: the client response. 264 | // @param method: the HTTP method. 265 | // @return: true if authentication succeeds, false otherwise. 266 | // 267 | bool CDirectoryServiceAuth::NativeAuthenticationDigestToNode(const char* nodename, const char* user, 268 | const char* challenge, const char* response, const char* method) 269 | { 270 | bool result = false; 271 | tDirNodeReference node = 0L; 272 | tDataNodePtr authType = NULL; 273 | tDataBufferPtr authData = NULL; 274 | tContextData context = NULL; 275 | 276 | try 277 | { 278 | // Make sure we have a valid directory service 279 | OpenService(); 280 | 281 | // Open the node we want to query 282 | node = OpenNamedNode(nodename); 283 | 284 | CreateBuffer(); 285 | 286 | // First, specify the type of authentication. 287 | authType = ::dsDataNodeAllocateString(mDir, kDSStdAuthDIGEST_MD5); 288 | 289 | // Build input data 290 | // Native authentication is a one step authentication scheme. 291 | // Step 1 292 | // Send: 293 | // 294 | // 295 | // 296 | // Receive: success or failure. 297 | UInt32 aDataBufSize = sizeof(UInt32) + ::strlen(user) + 298 | sizeof(UInt32) + ::strlen(challenge) + 299 | sizeof(UInt32) + ::strlen(response) + 300 | sizeof(UInt32) + ::strlen(method); 301 | authData = ::dsDataBufferAllocate(mDir, aDataBufSize); 302 | if (authData == NULL) 303 | ThrowIfDSErr(eDSNullDataBuff); 304 | 305 | // Fill the buffer 306 | ::dsFillAuthBuffer(authData, ::strlen(method)?4:3, 307 | ::strlen(user), user, 308 | ::strlen(challenge), challenge, 309 | ::strlen(response), response, 310 | ::strlen(method), method); 311 | 312 | // Do authentication 313 | tDirStatus dirStatus = ::dsDoDirNodeAuth(node, authType, true, authData, mData, &context); 314 | result = (dirStatus == eDSNoErr); 315 | 316 | // Cleanup 317 | ::dsDataBufferDeAllocate(mDir, authData); 318 | authData = NULL; 319 | ::dsDataNodeDeAllocate(mDir, authType); 320 | authType = NULL; 321 | RemoveBuffer(); 322 | 323 | // If fatal error, force full reset 324 | if (not result and (dirStatus != eDSAuthFailed)) 325 | { 326 | CloseService(); 327 | } 328 | } 329 | catch(...) 330 | { 331 | // Cleanup 332 | if (authData != NULL) 333 | ::dsDataBufferDeAllocate(mDir, authData); 334 | if (authType != NULL) 335 | ::dsDataNodeDeAllocate(mDir, authType); 336 | RemoveBuffer(); 337 | CloseService(); 338 | 339 | throw; 340 | } 341 | 342 | return result; 343 | } 344 | 345 | // NativeAuthenticationSASLDigestToNode 346 | // 347 | // Authenticate a user to the directory. 348 | // 349 | // @param nodename: the node to authenticate to. 350 | // @param user: the identifier/directory record name of the user. 351 | // @param sasldata: the client response. 352 | // @param saslResult: returned step data. 353 | // @return: true if authentication succeeds, false otherwise. 354 | // 355 | bool CDirectoryServiceAuth::NativeAuthenticationSASLDigestToNode(const char* nodename, const char* user, const char* sasldata, CFStringRef* saslResult) 356 | { 357 | bool result = false; 358 | tDirNodeReference node = 0L; 359 | tDataNodePtr authType = NULL; 360 | tDataBufferPtr authData = NULL; 361 | tContextData context = NULL; 362 | 363 | try 364 | { 365 | // Make sure we have a valid directory service 366 | OpenService(); 367 | 368 | // Open the node we want to query 369 | node = OpenNamedNode(nodename); 370 | 371 | CreateBuffer(); 372 | 373 | // First, specify the type of authentication. 374 | authType = ::dsDataNodeAllocateString(mDir, kDSStdAuthSASLProxy); 375 | 376 | // Build input data 377 | // Native authentication is a one step authentication scheme. 378 | // Step 1 379 | // Send: 380 | // 381 | // 382 | // Receive: success or failure and 383 | // step data: 384 | // 385 | UInt32 aDataBufSize = sizeof(UInt32) + ::strlen(user) + 386 | sizeof(UInt32) + ::strlen(kSASLDIGESTMD5) + 387 | sizeof(UInt32) + ::strlen(sasldata); 388 | authData = ::dsDataBufferAllocate(mDir, aDataBufSize); 389 | if (authData == NULL) 390 | ThrowIfDSErr(eDSNullDataBuff); 391 | 392 | // Fill the buffer 393 | ::dsFillAuthBuffer(authData, 3, 394 | ::strlen(user), user, 395 | ::strlen(kSASLDIGESTMD5), kSASLDIGESTMD5, 396 | ::strlen(sasldata), sasldata); 397 | 398 | // Do authentication 399 | tDirStatus dirStatus = ::dsDoDirNodeAuth(node, authType, true, authData, mData, &context); 400 | result = (dirStatus == eDSNoErr); 401 | 402 | if (result && (NULL != saslResult)) 403 | { 404 | // get first step data in string (always a string for kSASLDIGESTMD5) 405 | *saslResult = CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)&mData->fBufferData[4], *(UInt32*)&mData->fBufferData[0], 406 | kCFStringEncodingUTF8, false); 407 | } 408 | 409 | // Cleanup 410 | ::dsDataBufferDeAllocate(mDir, authData); 411 | authData = NULL; 412 | ::dsDataNodeDeAllocate(mDir, authType); 413 | authType = NULL; 414 | RemoveBuffer(); 415 | 416 | // If fatal error, force full reset 417 | if (not result and (dirStatus != eDSAuthFailed)) 418 | { 419 | CloseService(); 420 | } 421 | } 422 | catch(...) 423 | { 424 | // Cleanup 425 | if (authData != NULL) 426 | ::dsDataBufferDeAllocate(mDir, authData); 427 | if (authType != NULL) 428 | ::dsDataNodeDeAllocate(mDir, authType); 429 | RemoveBuffer(); 430 | CloseService(); 431 | 432 | throw; 433 | } 434 | 435 | return result; 436 | } 437 | 438 | /* 439 | // NativeGenerateClientResponse 440 | // 441 | // Authenticate a user to the directory. 442 | // 443 | // @param nodename: the node to authenticate to. 444 | // @param user: the identifier/directory record name of the user. 445 | // @param sasldata: the client response. 446 | // @param saslResult: returned step data. 447 | // @return: true if authentication succeeds, false otherwise. 448 | // 449 | bool CDirectoryServiceAuth::NativeGenerateClientResponse(const char* serverChallenge, CFStringRef* response) 450 | { 451 | bool result = false; 452 | tDirNodeReference node = 0L; 453 | tDataNodePtr authType = NULL; 454 | tDataBufferPtr authData = NULL; 455 | tContextData context = NULL; 456 | 457 | try 458 | { 459 | sasl_conn = ::SASLClientNewContext( callbacks, &sasl_context ); 460 | // Client hashes the digest and responds 461 | result = ::sasl_client_step(sasl_conn, (char *)[serverChal bytes], [serverChal length], NULL, &data, &len); 462 | if (result != SASL_CONTINUE) { 463 | printf("sasl_client_step = %d\n", result); 464 | goto done; 465 | } 466 | 467 | } 468 | catch(...) 469 | { 470 | // Cleanup 471 | if (authData != NULL) 472 | ::dsDataBufferDeAllocate(mDir, authData); 473 | if (authType != NULL) 474 | ::dsDataNodeDeAllocate(mDir, authType); 475 | RemoveBuffer(); 476 | CloseService(); 477 | 478 | throw; 479 | } 480 | 481 | return result; 482 | } 483 | */ 484 | 485 | 486 | // CloseService 487 | // 488 | // Close the directory service if previously open. 489 | // Also close any open/cached nodes. 490 | // 491 | void CDirectoryServiceAuth::CloseService() 492 | { 493 | if (mDir != 0L) 494 | { 495 | // Close all open nodes 496 | for(TNodeMap::const_iterator iter = mNodeMap.begin(); iter != mNodeMap.end(); iter++) 497 | { 498 | ::dsCloseDirNode((*iter).second); 499 | } 500 | mNodeMap.clear(); 501 | } 502 | 503 | CDirectoryService::CloseService(); 504 | } 505 | 506 | // OpenNamedNode 507 | // 508 | // Open a named node in the directory. 509 | // 510 | // @param nodename: the name of the node to open. 511 | // @return: node reference if success, NULL otherwise. 512 | // @throw: yes 513 | // 514 | tDirNodeReference CDirectoryServiceAuth::OpenNamedNode(const char* nodename) 515 | { 516 | // Check cache first 517 | tDirNodeReference result = NULL; 518 | TNodeMap::const_iterator found = mNodeMap.find(nodename); 519 | if (found != mNodeMap.end()) 520 | { 521 | return (*found).second; 522 | } 523 | 524 | // Create a new one and cache 525 | result = CDirectoryService::OpenNamedNode(nodename); 526 | mNodeMap[nodename] = result; 527 | return result; 528 | } 529 | -------------------------------------------------------------------------------- /src/CDirectoryServiceAuth.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #pragma once 21 | 22 | #include "CDirectoryService.h" 23 | 24 | #include 25 | #include 26 | 27 | class CDirectoryServiceAuth : public CDirectoryService 28 | { 29 | public: 30 | CDirectoryServiceAuth(); 31 | virtual ~CDirectoryServiceAuth(); 32 | 33 | bool AuthenticateUserBasic(const char* nodename, const char* user, const char* pswd, bool& result, bool using_python=true); 34 | bool AuthenticateUserDigest(const char* nodename, const char* user, const char* challenge, const char* response, const char* method, bool& result, bool using_python=true); 35 | bool AuthenticateUserDigestToActiveDirectory(const char* nodename, const char* user, const char* response, bool& result, bool using_python=true); 36 | 37 | CFStringRef GetDigestMD5ChallengeFromActiveDirectory(const char* nodename, bool using_python=true); 38 | 39 | protected: 40 | 41 | typedef std::map TNodeMap; 42 | TNodeMap mNodeMap; 43 | 44 | bool NativeAuthenticationBasicToNode(const char* nodename, const char* user, const char* pswd); 45 | bool NativeAuthenticationDigestToNode(const char* nodename, const char* user, const char* challenge, const char* response, const char* method); 46 | bool NativeAuthenticationSASLDigestToNode(const char* nodename, const char* user, const char* sasldata, CFStringRef* saslResult = NULL); 47 | 48 | virtual void CloseService(); 49 | virtual tDirNodeReference OpenNamedNode(const char* nodename); 50 | }; 51 | -------------------------------------------------------------------------------- /src/CDirectoryServiceException.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #include "CDirectoryServiceException.h" 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | extern PyObject* ODException_class; 28 | 29 | #pragma mark -----Public API 30 | 31 | CDirectoryServiceException::CDirectoryServiceException() 32 | { 33 | mDSError = eUndefinedError; 34 | ::snprintf(mDescription, 1024, "Unknown Error"); 35 | } 36 | 37 | CDirectoryServiceException::CDirectoryServiceException(tDirStatus error, const char* file, long line) 38 | { 39 | mDSError = error; 40 | ::snprintf(mDescription, 1024, "Exception raised in file %s at line %ld", file, line); 41 | } 42 | 43 | CDirectoryServiceException::~CDirectoryServiceException() 44 | { 45 | } 46 | 47 | void CDirectoryServiceException::ThrowDSError(tDirStatus error, const char* file, long line) 48 | { 49 | CDirectoryServiceException dirStatus(error, file, line); 50 | throw dirStatus; 51 | } 52 | 53 | void CDirectoryServiceException::SetPythonException() 54 | { 55 | char error[1024]; 56 | ::snprintf(error, 1024, "%s %s", "DirectoryServices Error:", mDescription); 57 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", error, mDSError)); 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/CDirectoryServiceException.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | class CDirectoryServiceException 25 | { 26 | public: 27 | CDirectoryServiceException(); 28 | CDirectoryServiceException(tDirStatus error, const char* file, long line); 29 | ~CDirectoryServiceException(); 30 | 31 | static void ThrowDSError(tDirStatus error, const char* file, long line); 32 | 33 | void SetPythonException(); 34 | 35 | private: 36 | tDirStatus mDSError; 37 | char mDescription[1024]; 38 | }; 39 | 40 | # define ThrowIfDSErr(x) { if (x != eDSNoErr) CDirectoryServiceException::ThrowDSError(x, __FILE__, __LINE__); } 41 | # define ThrowIfNULL(x) { if (x == NULL) CDirectoryServiceException::ThrowDSError(eUndefinedError, __FILE__, __LINE__); } 42 | -------------------------------------------------------------------------------- /src/CDirectoryServiceManager.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #include "CDirectoryServiceManager.h" 21 | 22 | #include "CDirectoryService.h" 23 | #include "CDirectoryServiceAuth.h" 24 | #include "CDirectoryServiceException.h" 25 | 26 | #pragma mark -----Public API 27 | 28 | CDirectoryServiceManager::CDirectoryServiceManager(const char* nodename) 29 | { 30 | mNodeName = ::strdup(nodename); 31 | mAuthService = NULL; 32 | } 33 | 34 | CDirectoryServiceManager::~CDirectoryServiceManager() 35 | { 36 | if (mAuthService != NULL) 37 | { 38 | delete mAuthService; 39 | mAuthService = NULL; 40 | } 41 | ::free(mNodeName); 42 | } 43 | 44 | CDirectoryService* CDirectoryServiceManager::GetService() 45 | { 46 | return new CDirectoryService(mNodeName); 47 | } 48 | 49 | CDirectoryServiceAuth* CDirectoryServiceManager::GetAuthService() 50 | { 51 | if (mAuthService == NULL) 52 | mAuthService = new CDirectoryServiceAuth(); 53 | return mAuthService; 54 | } 55 | -------------------------------------------------------------------------------- /src/CDirectoryServiceManager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps high-level Directory Service calls needed by the 3 | * CalDAV server. 4 | ** 5 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | #pragma once 21 | 22 | class CDirectoryService; 23 | class CDirectoryServiceAuth; 24 | 25 | class CDirectoryServiceManager 26 | { 27 | public: 28 | CDirectoryServiceManager(const char* nodename); 29 | ~CDirectoryServiceManager(); 30 | 31 | CDirectoryService* GetService(); 32 | CDirectoryServiceAuth* GetAuthService(); 33 | 34 | private: 35 | char* mNodeName; 36 | CDirectoryServiceAuth* mAuthService; 37 | }; 38 | -------------------------------------------------------------------------------- /src/CFStringUtil.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps CFString. 3 | ** 4 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | **/ 18 | 19 | #include "CFStringUtil.h" 20 | 21 | // Construct with c-string. 22 | // 23 | // @param cstr: c-string to create CFString from (mustr be UTF-8 encoded). 24 | // 25 | CFStringUtil::CFStringUtil(const char* cstr) 26 | { 27 | mRef = ::CFStringCreateWithCString(kCFAllocatorDefault, cstr, kCFStringEncodingUTF8); 28 | mTemp = NULL; 29 | } 30 | 31 | // Construct with existing CFStringRef. 32 | // 33 | // @param ref: CFStringRef to use - this is retained. 34 | // 35 | CFStringUtil::CFStringUtil(CFStringRef ref) 36 | { 37 | mRef = ref; 38 | if (mRef != NULL) 39 | ::CFRetain(mRef); 40 | mTemp = NULL; 41 | } 42 | 43 | CFStringUtil::~CFStringUtil() 44 | { 45 | if (mRef != NULL) 46 | { 47 | ::CFRelease(mRef); 48 | mRef = NULL; 49 | } 50 | if (mTemp != NULL) 51 | { 52 | ::free((void*)mTemp); 53 | mTemp = NULL; 54 | } 55 | } 56 | 57 | CFStringUtil& CFStringUtil::operator=(const CFStringUtil& copy) 58 | { 59 | mRef = copy.get(); 60 | if (mRef != NULL) 61 | ::CFRetain(mRef); 62 | mTemp = NULL; 63 | 64 | return *this; 65 | } 66 | 67 | 68 | // Return a new c-string from the CFString data. 69 | // 70 | // @return: c-string created from CFString - this must be deallocated (free) by caller. 71 | // 72 | char* CFStringUtil::c_str() const 73 | { 74 | const char* bytes = (mRef != NULL) ? CFStringGetCStringPtr(mRef, kCFStringEncodingUTF8) : ""; 75 | 76 | if (bytes == NULL) 77 | { 78 | // Need to convert the CFString to UTF-8. Since we don't know the exact length of the UTF-8 data 79 | // we have to iterate over the conversion process until we succeed or the length we are allocating 80 | // is greater than the value it could maximally be. We start with half the UTF-16 encoded value, 81 | // which will give an accurate count for an ascii only string (plus add one for \0). 82 | CFIndex len = ::CFStringGetLength(mRef)/2 + 1; 83 | CFIndex maxSize = ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(mRef), kCFStringEncodingUTF8) + 1; 84 | char* buffer = NULL; 85 | while(true) 86 | { 87 | buffer = (char*)::malloc(len); 88 | if (buffer == NULL) 89 | break; 90 | buffer[0] = 0; 91 | Boolean success = ::CFStringGetCString(mRef, buffer, len, kCFStringEncodingUTF8); 92 | if (!success) 93 | { 94 | ::free(buffer); 95 | buffer = NULL; 96 | if (len == maxSize) 97 | { 98 | buffer = (char*)::malloc(1); 99 | buffer[0] = 0; 100 | break; 101 | } 102 | len *= 2; 103 | if (len > maxSize) 104 | len = maxSize; 105 | } 106 | else 107 | break; 108 | } 109 | 110 | return buffer; 111 | } 112 | else 113 | { 114 | return ::strdup(bytes); 115 | } 116 | } 117 | 118 | // Return a temporary c-string from the CFString data. 119 | // 120 | // @return: c-string created from CFString - this must NOT be deallocated by caller as this object owns it. 121 | // 122 | const char* CFStringUtil::temp_str() const 123 | { 124 | if (mTemp != NULL) 125 | { 126 | ::free((void*)mTemp); 127 | mTemp = NULL; 128 | } 129 | mTemp = c_str(); 130 | return mTemp; 131 | } 132 | 133 | // Reset with existing CFStringRef. Any existing CFStringRef is released before the new one is used. 134 | // 135 | // @param ref: CFStringRef to use - this is retained. 136 | // 137 | void CFStringUtil::reset(CFStringRef ref) 138 | { 139 | if (mRef != NULL) 140 | { 141 | ::CFRelease(mRef); 142 | mRef = NULL; 143 | } 144 | mRef = ref; 145 | if (mRef != NULL) 146 | ::CFRetain(mRef); 147 | } 148 | -------------------------------------------------------------------------------- /src/CFStringUtil.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A class that wraps CFString. 3 | ** 4 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | **/ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | class CFStringUtil 24 | { 25 | public: 26 | CFStringUtil(const char* cstr); 27 | CFStringUtil(CFStringRef ref); 28 | ~CFStringUtil(); 29 | 30 | CFStringUtil& operator=(const CFStringUtil& copy); 31 | 32 | CFStringRef get() const 33 | { 34 | return mRef; 35 | } 36 | 37 | char* c_str() const; 38 | const char* temp_str() const; 39 | 40 | void reset(CFStringRef ref); 41 | 42 | private: 43 | CFStringRef mRef; 44 | mutable const char* mTemp; 45 | }; 46 | -------------------------------------------------------------------------------- /src/PythonWrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2006-2009 Apple Inc. All rights reserved. 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 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | #include 18 | #include 19 | 20 | #include "CDirectoryServiceManager.h" 21 | #include "CDirectoryService.h" 22 | #include "CDirectoryServiceAuth.h" 23 | #include "CFStringUtil.h" 24 | 25 | #include 26 | #include 27 | 28 | #ifndef Py_RETURN_TRUE 29 | #define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True 30 | #endif 31 | #ifndef Py_RETURN_FALSE 32 | #define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False 33 | #endif 34 | #ifndef Py_RETURN_NONE 35 | #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None 36 | #endif 37 | 38 | class PyObjectException : public std::exception 39 | { 40 | public: 41 | PyObjectException(const char* msg) 42 | { 43 | message = msg; 44 | } 45 | 46 | virtual const char* what() 47 | { 48 | return message; 49 | } 50 | 51 | private: 52 | const char* message; 53 | }; 54 | 55 | class PyTupleOrList 56 | { 57 | public: 58 | PyTupleOrList(PyObject* item) 59 | { 60 | if (PyTuple_Check(item)) 61 | { 62 | tuple = item; 63 | list = NULL; 64 | } 65 | else if (PyList_Check(item)) 66 | { 67 | list = item; 68 | tuple = NULL; 69 | } 70 | else 71 | throw PyObjectException("Expecting a list or tuple in 'PyTupleOrList'."); 72 | } 73 | 74 | Py_ssize_t getSize() const 75 | { 76 | if (tuple) 77 | return PyTuple_Size(tuple); 78 | else if (list) 79 | return PyList_Size(list); 80 | else 81 | return 0; 82 | } 83 | 84 | PyObject* get(Py_ssize_t index) const 85 | { 86 | if (tuple) 87 | return PyTuple_GetItem(tuple, index); 88 | else if (list) 89 | return PyList_GetItem(list, index); 90 | else 91 | return NULL; 92 | } 93 | 94 | static bool typeOK(PyObject* item) 95 | { 96 | return PyTuple_Check(item) || PyList_Check(item); 97 | } 98 | 99 | private: 100 | PyObject* tuple; 101 | PyObject* list; 102 | }; 103 | 104 | // Utility function - not exposed to Python 105 | static PyObject* CFStringToPyStr(CFStringRef str) 106 | { 107 | CFStringUtil s(str); 108 | return PyString_FromString(s.temp_str()); 109 | } 110 | 111 | // Utility function - not exposed to Python 112 | static PyObject* CFArrayToPyList(CFArrayRef list, bool sorted = false) 113 | { 114 | CFIndex lsize = (list != NULL) ? CFArrayGetCount(list) : 0; 115 | if (sorted and (list != NULL)) 116 | CFArraySortValues((CFMutableArrayRef)list, CFRangeMake(0, lsize), (CFComparatorFunction)CFStringCompare, NULL); 117 | 118 | PyObject* result = PyList_New(lsize); 119 | for(CFIndex i = 0; i < lsize; i++) 120 | { 121 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(list, i); 122 | PyObject* pystr = CFStringToPyStr(str); 123 | 124 | PyList_SetItem(result, i, pystr); 125 | } 126 | 127 | return result; 128 | } 129 | 130 | // Utility function - not exposed to Python 131 | static CFArrayRef PyStringTupleOrListToCFArray(PyObject* item) 132 | { 133 | if (PyTuple_Check(item) || PyList_Check(item)) 134 | { 135 | PyTupleOrList pyitem(item); 136 | CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, pyitem.getSize(), &kCFTypeArrayCallBacks); 137 | for(int i = 0; i < pyitem.getSize(); i++) 138 | { 139 | PyObject* str = pyitem.get(i); 140 | if ((str == NULL) || !PyString_Check(str)) 141 | { 142 | CFRelease(result); 143 | throw PyObjectException("Expecting a str type in 'PyStringTupleOrListToCFArray'."); 144 | } 145 | const char* cstr = PyString_AsString(str); 146 | if (cstr == NULL) 147 | { 148 | CFRelease(result); 149 | throw PyObjectException("Could not extract string in 'PyStringTupleOrListToCFArray'."); 150 | } 151 | CFStringUtil cfstr(cstr); 152 | CFArrayAppendValue(result, cfstr.get()); 153 | } 154 | 155 | return result; 156 | } 157 | else if (PyString_Check(item)) 158 | { 159 | CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks); 160 | const char* cstr = PyString_AsString(item); 161 | if (cstr == NULL) 162 | { 163 | CFRelease(result); 164 | throw PyObjectException("Could not extract string in 'PyStringTupleOrListToCFArray'."); 165 | } 166 | CFStringUtil cfstr(cstr); 167 | CFArrayAppendValue(result, cfstr.get()); 168 | 169 | return result; 170 | } 171 | else 172 | { 173 | throw PyObjectException("Could not extract string, tuple or list in 'PyStringTupleOrListToCFArray'."); 174 | } 175 | } 176 | 177 | // Utility function - not exposed to Python 178 | static CFArrayRef PyTupleOrListToCFArray(PyObject* item) 179 | { 180 | PyTupleOrList pyitem(item); 181 | CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, pyitem.getSize(), &kCFTypeArrayCallBacks); 182 | for(int i = 0; i < pyitem.getSize(); i++) 183 | { 184 | PyObject* str = pyitem.get(i); 185 | if ((str == NULL) || !PyString_Check(str)) 186 | { 187 | CFRelease(result); 188 | throw PyObjectException("Expecting a str type in 'PyTupleOrListToCFArray'."); 189 | } 190 | const char* cstr = PyString_AsString(str); 191 | if (cstr == NULL) 192 | { 193 | CFRelease(result); 194 | throw PyObjectException("Could not extract string in 'PyTupleOrListToCFArray'."); 195 | } 196 | CFStringUtil cfstr(cstr); 197 | CFArrayAppendValue(result, cfstr.get()); 198 | } 199 | 200 | return result; 201 | } 202 | 203 | // Utility function - not exposed to Python 204 | static CFDictionaryRef AttributesToCFDictionary(PyObject* item) 205 | { 206 | PyTupleOrList pyitem(item); 207 | CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault, pyitem.getSize(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 208 | for(int i = 0; i < pyitem.getSize(); i++) 209 | { 210 | PyObject* str = pyitem.get(i); 211 | if ((str == NULL) || !PyString_Check(str) && !PyList_Check(str) && !PyTuple_Check(str)) 212 | { 213 | CFRelease(result); 214 | throw PyObjectException("Expecting a str, list or tuple type in 'AttributesToCFDictionary'."); 215 | } 216 | if (PyString_Check(str)) 217 | { 218 | const char* cstr = PyString_AsString(str); 219 | if (cstr == NULL) 220 | { 221 | CFRelease(result); 222 | throw PyObjectException("Could not extract string in 'PyTupleOrListToCFArray'."); 223 | } 224 | CFStringUtil cfstr(cstr); 225 | CFDictionarySetValue(result, cfstr.get(), CFSTR("str")); 226 | } 227 | else if (PyList_Check(str) || PyTuple_Check(str)) 228 | { 229 | CFArrayRef strs = PyTupleOrListToCFArray(str); 230 | CFIndex strsize = CFArrayGetCount(strs); 231 | if ((strsize != 1) && (strsize != 2)) 232 | { 233 | CFRelease(strs); 234 | CFRelease(result); 235 | throw PyObjectException("Expecting one or two items in tuple or list in 'PyTupleOrListToCFArray'."); 236 | } 237 | 238 | if (strsize == 2) 239 | CFDictionarySetValue(result, CFArrayGetValueAtIndex(strs, 0), CFArrayGetValueAtIndex(strs, 1)); 240 | else 241 | CFDictionarySetValue(result, CFArrayGetValueAtIndex(strs, 0), CFSTR("str")); 242 | CFRelease(strs); 243 | } 244 | } 245 | 246 | return result; 247 | } 248 | 249 | // Utility function - not exposed to Python 250 | static void CFDictionaryIterator(const void* key, const void* value, void* ref) 251 | { 252 | CFStringRef strkey = (CFStringRef)key; 253 | PyObject* dict = (PyObject*)ref; 254 | 255 | PyObject* pystrkey = CFStringToPyStr(strkey); 256 | 257 | // The dictionary value may be a string or a list 258 | if (CFGetTypeID((CFTypeRef)value) == CFStringGetTypeID()) 259 | { 260 | CFStringRef strvalue = (CFStringRef)value; 261 | PyObject* pystrvalue = CFStringToPyStr(strvalue); 262 | PyDict_SetItem(dict, pystrkey, pystrvalue); 263 | Py_DECREF(pystrvalue); 264 | } 265 | else if(CFGetTypeID((CFTypeRef)value) == CFArrayGetTypeID()) 266 | { 267 | CFArrayRef arrayvalue = (CFArrayRef)value; 268 | PyObject* pylistvalue = CFArrayToPyList(arrayvalue); 269 | PyDict_SetItem(dict, pystrkey, pylistvalue); 270 | Py_DECREF(pylistvalue); 271 | } 272 | Py_DECREF(pystrkey); 273 | } 274 | 275 | // Utility function - not exposed to Python 276 | static PyObject* CFDictionaryToPyDict(CFDictionaryRef dict) 277 | { 278 | PyObject* result = PyDict_New(); 279 | if (dict != NULL) 280 | CFDictionaryApplyFunction(dict, CFDictionaryIterator, result); 281 | 282 | return result; 283 | } 284 | 285 | // Utility function - not exposed to Python 286 | static void CFDictionaryDictionaryIterator(const void* key, const void* value, void* ref) 287 | { 288 | CFStringRef strkey = (CFStringRef)key; 289 | CFDictionaryRef dictvalue = (CFDictionaryRef)value; 290 | PyObject* dict = (PyObject*)ref; 291 | 292 | PyObject* pystrkey = CFStringToPyStr(strkey); 293 | PyObject* pydictvalue = CFDictionaryToPyDict(dictvalue); 294 | 295 | PyDict_SetItem(dict, pystrkey, pydictvalue); 296 | Py_DECREF(pystrkey); 297 | Py_DECREF(pydictvalue); 298 | } 299 | 300 | // Utility function - not exposed to Python 301 | static PyObject* CFDictionaryDictionaryToPyDict(CFDictionaryRef dict) 302 | { 303 | PyObject* result = PyDict_New(); 304 | if (dict != NULL) 305 | CFDictionaryApplyFunction(dict, CFDictionaryDictionaryIterator, result); 306 | 307 | return result; 308 | } 309 | 310 | // Utility function - not exposed to Python 311 | static PyObject* CFArrayStringDictionaryToPyList(CFArrayRef list) 312 | { 313 | CFIndex lsize = (list != NULL) ? CFArrayGetCount(list) : 0; 314 | if (lsize != 2) 315 | return NULL; 316 | 317 | PyObject* result = PyList_New(lsize); 318 | 319 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(list, 0); 320 | PyObject* pystr = CFStringToPyStr(str); 321 | PyList_SetItem(result, 0, pystr); 322 | 323 | CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(list, 1); 324 | PyObject* pydict = CFDictionaryToPyDict(dict); 325 | PyList_SetItem(result, 1, pydict); 326 | 327 | return result; 328 | } 329 | 330 | // Utility function - not exposed to Python 331 | static PyObject* CFArrayArrayDictionaryToPyList(CFArrayRef list) 332 | { 333 | CFIndex lsize = (list != NULL) ? CFArrayGetCount(list) : 0; 334 | 335 | PyObject* result = PyList_New(lsize); 336 | for(CFIndex i = 0, j = 0; i < lsize; i++) 337 | { 338 | CFArrayRef nested = (CFArrayRef)CFArrayGetValueAtIndex(list, i); 339 | PyObject* pylist = CFArrayStringDictionaryToPyList(nested); 340 | if (pylist != NULL) 341 | { 342 | PyList_SetItem(result, j++, pylist); 343 | } 344 | } 345 | 346 | return result; 347 | } 348 | 349 | // Utility function - not exposed to Python 350 | static void CFArrayStringDictionaryToPyDict(CFArrayRef list, PyObject* result) 351 | { 352 | CFIndex lsize = (list != NULL) ? CFArrayGetCount(list) : 0; 353 | if (lsize != 2) 354 | return; 355 | 356 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(list, 0); 357 | PyObject* pystrkey = CFStringToPyStr(str); 358 | 359 | CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(list, 1); 360 | PyObject* pydictvalue = CFDictionaryToPyDict(dict); 361 | 362 | PyDict_SetItem(result, pystrkey, pydictvalue); 363 | Py_DECREF(pystrkey); 364 | Py_DECREF(pydictvalue); 365 | } 366 | 367 | // Utility function - not exposed to Python 368 | static PyObject* CFArrayArrayDictionaryToPyDict(CFArrayRef list) 369 | { 370 | CFIndex lsize = (list != NULL) ? CFArrayGetCount(list) : 0; 371 | 372 | PyObject* result = PyDict_New(); 373 | for(CFIndex i = 0; i < lsize; i++) 374 | { 375 | CFArrayRef nested = (CFArrayRef)CFArrayGetValueAtIndex(list, i); 376 | CFArrayStringDictionaryToPyDict(nested, result); 377 | } 378 | 379 | return result; 380 | } 381 | 382 | PyObject* ODException_class = NULL; 383 | 384 | /* 385 | Internal method. 386 | */ 387 | static PyObject *_listAllRecordsWithAttributes(PyObject *self, PyObject *args, bool list) 388 | { 389 | PyObject* pyds; 390 | PyObject* recordType; 391 | PyObject* attributes; 392 | int maxRecordCount = 0; 393 | if (!PyArg_ParseTuple(args, "OOO|i", &pyds, &recordType, &attributes, &maxRecordCount) || !PyCObject_Check(pyds) || !PyTupleOrList::typeOK(attributes)) 394 | { 395 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices listAllRecordsWithAttributes: could not parse arguments", 0)); 396 | return NULL; 397 | } 398 | 399 | // Convert string/tuple/list to CFArray 400 | CFArrayRef cfrecordtypes = NULL; 401 | try 402 | { 403 | cfrecordtypes = PyStringTupleOrListToCFArray(recordType); 404 | } 405 | catch(PyObjectException& ex) 406 | { 407 | std::string msg("DirectoryServices listAllRecordsWithAttributes: could not parse recordTypes: "); 408 | msg += ex.what(); 409 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 410 | return NULL; 411 | } 412 | 413 | // Convert list to CFArray of CFString 414 | CFDictionaryRef cfattributes = NULL; 415 | try 416 | { 417 | cfattributes = AttributesToCFDictionary(attributes); 418 | } 419 | catch(PyObjectException& ex) 420 | { 421 | std::string msg("DirectoryServices listAllRecordsWithAttributes: could not parse attributes list: "); 422 | msg += ex.what(); 423 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 424 | CFRelease(cfrecordtypes); 425 | return NULL; 426 | } 427 | 428 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 429 | if (dsmgr != NULL) 430 | { 431 | std::auto_ptr ds(dsmgr->GetService()); 432 | CFMutableArrayRef results = NULL; 433 | results = ds->ListAllRecordsWithAttributes(cfrecordtypes, cfattributes, maxRecordCount); 434 | if (results != NULL) 435 | { 436 | PyObject* result = list ? CFArrayArrayDictionaryToPyList(results) : CFArrayArrayDictionaryToPyDict(results); 437 | CFRelease(results); 438 | CFRelease(cfattributes); 439 | CFRelease(cfrecordtypes); 440 | 441 | return result; 442 | } 443 | } 444 | else 445 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices listAllRecordsWithAttributes: invalid directory service argument", 0)); 446 | 447 | CFRelease(cfattributes); 448 | CFRelease(cfrecordtypes); 449 | return NULL; 450 | } 451 | 452 | static PyObject *_queryRecordsWithAttribute(PyObject *self, PyObject *args, bool list) 453 | { 454 | PyObject* pyds; 455 | const char* attr; 456 | const char* value; 457 | int matchType; 458 | PyObject* caseio; 459 | bool casei; 460 | PyObject* recordType; 461 | PyObject* attributes; 462 | int maxRecordCount = 0; 463 | if (!PyArg_ParseTuple(args, "OssiOOO|i", &pyds, &attr, &value, &matchType, &caseio, &recordType, &attributes, &maxRecordCount) || 464 | !PyCObject_Check(pyds) || !PyBool_Check(caseio) || !PyTupleOrList::typeOK(attributes)) 465 | { 466 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices queryRecordsWithAttribute: could not parse arguments", 0)); 467 | return NULL; 468 | } 469 | 470 | casei = (caseio == Py_True); 471 | 472 | // Convert string/tuple/list to CFArray 473 | CFArrayRef cfrecordtypes = NULL; 474 | try 475 | { 476 | cfrecordtypes = PyStringTupleOrListToCFArray(recordType); 477 | } 478 | catch(PyObjectException& ex) 479 | { 480 | std::string msg("DirectoryServices listAllRecordsWithAttributes: could not parse recordTypes: "); 481 | msg += ex.what(); 482 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 483 | return NULL; 484 | } 485 | 486 | // Convert list to CFArray of CFString 487 | CFDictionaryRef cfattributes = NULL; 488 | try 489 | { 490 | cfattributes = AttributesToCFDictionary(attributes); 491 | } 492 | catch(PyObjectException& ex) 493 | { 494 | std::string msg("DirectoryServices queryRecordsWithAttribute: could not parse attributes list: "); 495 | msg += ex.what(); 496 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 497 | CFRelease(cfrecordtypes); 498 | return NULL; 499 | } 500 | 501 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 502 | if (dsmgr != NULL) 503 | { 504 | std::auto_ptr ds(dsmgr->GetService()); 505 | CFMutableArrayRef results = NULL; 506 | results = ds->QueryRecordsWithAttribute(attr, value, matchType, casei, cfrecordtypes, cfattributes, maxRecordCount); 507 | if (results != NULL) 508 | { 509 | PyObject* result = list ? CFArrayArrayDictionaryToPyList(results) : CFArrayArrayDictionaryToPyDict(results); 510 | CFRelease(results); 511 | CFRelease(cfattributes); 512 | CFRelease(cfrecordtypes); 513 | 514 | return result; 515 | } 516 | } 517 | else 518 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices queryRecordsWithAttribute: invalid directory service argument", 0)); 519 | 520 | CFRelease(cfattributes); 521 | CFRelease(cfrecordtypes); 522 | return NULL; 523 | } 524 | 525 | static PyObject *_queryRecordsWithAttributes(PyObject *self, PyObject *args, bool list) 526 | { 527 | PyObject* pyds; 528 | const char* query; 529 | PyObject* caseio; 530 | bool casei; 531 | PyObject* recordType; 532 | PyObject* attributes; 533 | int maxRecordCount = 0; 534 | if (!PyArg_ParseTuple(args, "OsOOO|i", &pyds, &query, &caseio, &recordType, &attributes, &maxRecordCount) || 535 | !PyCObject_Check(pyds) || !PyBool_Check(caseio) || !PyTupleOrList::typeOK(attributes)) 536 | { 537 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices queryRecordsWithAttributes: could not parse arguments", 0)); 538 | return NULL; 539 | } 540 | 541 | casei = (caseio == Py_True); 542 | 543 | // Convert string/tuple/list to CFArray 544 | CFArrayRef cfrecordtypes = NULL; 545 | try 546 | { 547 | cfrecordtypes = PyStringTupleOrListToCFArray(recordType); 548 | } 549 | catch(PyObjectException& ex) 550 | { 551 | std::string msg("DirectoryServices listAllRecordsWithAttributes: could not parse recordTypes: "); 552 | msg += ex.what(); 553 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 554 | return NULL; 555 | } 556 | 557 | // Convert list to CFArray of CFString 558 | CFDictionaryRef cfattributes = NULL; 559 | try 560 | { 561 | cfattributes = AttributesToCFDictionary(attributes); 562 | } 563 | catch(PyObjectException& ex) 564 | { 565 | std::string msg("DirectoryServices queryRecordsWithAttributes: could not parse attributes list: "); 566 | msg += ex.what(); 567 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 568 | CFRelease(cfrecordtypes); 569 | return NULL; 570 | } 571 | 572 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 573 | if (dsmgr != NULL) 574 | { 575 | std::auto_ptr ds(dsmgr->GetService()); 576 | CFMutableArrayRef results = NULL; 577 | results = ds->QueryRecordsWithAttributes(query, casei, cfrecordtypes, cfattributes, maxRecordCount); 578 | if (results != NULL) 579 | { 580 | PyObject* result = list ? CFArrayArrayDictionaryToPyList(results) : CFArrayArrayDictionaryToPyDict(results); 581 | CFRelease(results); 582 | CFRelease(cfattributes); 583 | CFRelease(cfrecordtypes); 584 | 585 | return result; 586 | } 587 | } 588 | else 589 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices queryRecordsWithAttributes: invalid directory service argument", 0)); 590 | 591 | CFRelease(cfattributes); 592 | CFRelease(cfrecordtypes); 593 | return NULL; 594 | } 595 | 596 | /* 597 | This is an automatic destructor for the object obtained by odInit. It is not directly 598 | exposed to Python, instead Python calls it automatically when reclaiming the object. 599 | */ 600 | extern "C" void odDestroy(void* obj) 601 | { 602 | CDirectoryServiceManager* dsmgr = static_cast(obj); 603 | delete dsmgr; 604 | } 605 | 606 | /* 607 | def odInit(nodename): 608 | """ 609 | Create an Open Directory object to operate on the specified directory service node name. 610 | 611 | @param nodename: C{str} containing the node name. 612 | @return: C{object} an object to be passed to all subsequent functions on success, 613 | C{None} on failure. 614 | """ 615 | */ 616 | extern "C" PyObject* odInit(PyObject* self, PyObject* args) 617 | { 618 | const char* nodename; 619 | if (!PyArg_ParseTuple(args, "s", &nodename)) 620 | { 621 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices odInit: could not parse arguments", 0)); 622 | return NULL; 623 | } 624 | 625 | CDirectoryServiceManager* dsmgr = new CDirectoryServiceManager(nodename); 626 | if (dsmgr != NULL) 627 | { 628 | return PyCObject_FromVoidPtr(dsmgr, odDestroy); 629 | } 630 | 631 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices odInit: could not initialize directory service", 0)); 632 | return NULL; 633 | } 634 | 635 | /* 636 | def listNodes(obj): 637 | """ 638 | List all the nodes currently configured in Open Directory. 639 | 640 | @param obj: C{object} the object obtained from an odInit call. 641 | @return: C{list} containing a C{str} for each configured node. 642 | """ 643 | */ 644 | extern "C" PyObject *listNodes(PyObject *self, PyObject *args) 645 | { 646 | PyObject* pyds; 647 | if (!PyArg_ParseTuple(args, "O", &pyds) || !PyCObject_Check(pyds)) 648 | { 649 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices listNodes: could not parse arguments", 0)); 650 | return NULL; 651 | } 652 | 653 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 654 | if (dsmgr != NULL) 655 | { 656 | std::auto_ptr ds(dsmgr->GetService()); 657 | CFMutableArrayRef results = NULL; 658 | results = ds->ListNodes(); 659 | if (results != NULL) 660 | { 661 | PyObject* result = CFArrayToPyList(results); 662 | CFRelease(results); 663 | 664 | return result; 665 | } 666 | } 667 | else 668 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices listNodes: invalid directory service argument", 0)); 669 | 670 | return NULL; 671 | } 672 | 673 | /* 674 | def getNodeAttributes(obj, nodename, attributes): 675 | """ 676 | Return key attributes for the specified directory node. The attributes 677 | can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 678 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 679 | 680 | @param obj: C{object} the object obtained from an odInit call. 681 | @param nodename: C{str} containing the OD nodename to query. 682 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 683 | @return: C{dict} of attributes found. 684 | """ 685 | */ 686 | extern "C" PyObject *getNodeAttributes(PyObject *self, PyObject *args) 687 | { 688 | PyObject* pyds; 689 | const char* nodename; 690 | PyObject* attributes; 691 | if (!PyArg_ParseTuple(args, "OsO", &pyds, &nodename, &attributes) || !PyCObject_Check(pyds) || !PyTupleOrList::typeOK(attributes)) 692 | { 693 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices getNodeAttributes: could not parse arguments", 0)); 694 | return NULL; 695 | } 696 | 697 | // Convert list to CFArray of CFString 698 | CFDictionaryRef cfattributes = NULL; 699 | try 700 | { 701 | cfattributes = AttributesToCFDictionary(attributes); 702 | } 703 | catch(PyObjectException& ex) 704 | { 705 | std::string msg("DirectoryServices getNodeAttributes: could not parse attributes list: "); 706 | msg += ex.what(); 707 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", msg.c_str(), 0)); 708 | CFRelease(cfattributes); 709 | return NULL; 710 | } 711 | 712 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 713 | if (dsmgr != NULL) 714 | { 715 | std::auto_ptr ds(dsmgr->GetService()); 716 | CFMutableDictionaryRef results = NULL; 717 | results = ds->GetNodeAttributes(nodename, cfattributes); 718 | if (results != NULL) 719 | { 720 | PyObject* result = CFDictionaryToPyDict(results); 721 | CFRelease(results); 722 | CFRelease(cfattributes); 723 | 724 | return result; 725 | } 726 | } 727 | else 728 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices getNodeAttributes: invalid directory service argument", 0)); 729 | 730 | CFRelease(cfattributes); 731 | return NULL; 732 | } 733 | 734 | /* 735 | def listAllRecordsWithAttributes(obj, recordType, attributes, count=0): 736 | """ 737 | List records in Open Directory, and return key attributes for each one. The attributes 738 | can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 739 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 740 | 741 | @param obj: C{object} the object obtained from an odInit call. 742 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 743 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 744 | @param count: C{int} maximum number of records to return (zero returns all). 745 | @return: C{dict} containing a C{dict} of attributes for each record found, 746 | or C{None} otherwise. 747 | """ 748 | */ 749 | extern "C" PyObject *listAllRecordsWithAttributes(PyObject *self, PyObject *args) 750 | { 751 | return _listAllRecordsWithAttributes(self, args, false); 752 | } 753 | 754 | /* 755 | def queryRecordsWithAttribute(obj, attr, value, matchType, casei, recordType, attributes, count=0): 756 | """ 757 | List records in Open Directory matching specified attribute and value, and return key attributes for each one. 758 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 759 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 760 | 761 | @param obj: C{object} the object obtained from an odInit call. 762 | @param attr: C{str} for the attribute to query. 763 | @param value: C{str} for the attribute value to query. 764 | @param matchType: C{int} DS match type to use when searching. 765 | @param casei: C{True} to do case-insenstive match, C{False} otherwise. 766 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 767 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 768 | @param count: C{int} maximum number of records to return (zero returns all). 769 | @return: C{dict} containing a C{dict} of attributes for each record found, 770 | or C{None} otherwise. 771 | """ 772 | */ 773 | extern "C" PyObject *queryRecordsWithAttribute(PyObject *self, PyObject *args) 774 | { 775 | return _queryRecordsWithAttribute(self, args, false); 776 | } 777 | 778 | /* 779 | def queryRecordsWithAttributes(obj, query, casei, recordType, attributes, count=0): 780 | """ 781 | List records in Open Directory matching specified compound query, and return key attributes for each one. 782 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 783 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 784 | 785 | @param obj: C{object} the object obtained from an odInit call. 786 | @param query: C{str} the compound query string. 787 | @param casei: C{True} to do case-insenstive match, C{False} otherwise. 788 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 789 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 790 | @param count: C{int} maximum number of records to return (zero returns all). 791 | @return: C{dict} containing a C{dict} of attributes for each record found, 792 | or C{None} otherwise. 793 | """ 794 | */ 795 | extern "C" PyObject *queryRecordsWithAttributes(PyObject *self, PyObject *args) 796 | { 797 | return _queryRecordsWithAttributes(self, args, false); 798 | } 799 | 800 | /* 801 | def listAllRecordsWithAttributes_list(obj, recordType, attributes, count=0): 802 | """ 803 | List records in Open Directory, and return key attributes for each one. 804 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 805 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 806 | 807 | @param obj: C{object} the object obtained from an odInit call. 808 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 809 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 810 | @param count: C{int} maximum number of records to return (zero returns all). 811 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 812 | for each record found, or C{None} otherwise. 813 | """ 814 | */ 815 | extern "C" PyObject *listAllRecordsWithAttributes_list(PyObject *self, PyObject *args) 816 | { 817 | return _listAllRecordsWithAttributes(self, args, true); 818 | } 819 | 820 | /* 821 | def queryRecordsWithAttribute_list(obj, attr, value, matchType, casei, recordType, attributes, count=0): 822 | """ 823 | List records in Open Directory matching specified attribute and value, and return key attributes for each one. 824 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 825 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 826 | 827 | @param obj: C{object} the object obtained from an odInit call. 828 | @param attr: C{str} for the attribute to query. 829 | @param value: C{str} for the attribute value to query. 830 | @param matchType: C{int} DS match type to use when searching. 831 | @param casei: C{True} to do case-insenstive match, C{False} otherwise. 832 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 833 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 834 | @param count: C{int} maximum number of records to return (zero returns all). 835 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 836 | for each record found, or C{None} otherwise. 837 | """ 838 | */ 839 | extern "C" PyObject *queryRecordsWithAttribute_list(PyObject *self, PyObject *args) 840 | { 841 | return _queryRecordsWithAttribute(self, args, true); 842 | } 843 | 844 | /* 845 | def queryRecordsWithAttributes_list(obj, query, casei, recordType, attributes, count=0): 846 | """ 847 | List records in Open Directory matching specified compound query, and return key attributes for each one. 848 | The attributes can be a C{str} for the attribute name, or a C{tuple} or C{list} where the first C{str} 849 | is the attribute name, and the second C{str} is an encoding type, either "str" or "base64". 850 | 851 | @param obj: C{object} the object obtained from an odInit call. 852 | @param query: C{str} the compound query string. 853 | @param casei: C{True} to do case-insenstive match, C{False} otherwise. 854 | @param recordType: C{str}, C{tuple} or C{list} containing the OD record types to lookup. 855 | @param attributes: C{list} or C{tuple} containing the attributes to return for each record. 856 | @param count: C{int} maximum number of records to return (zero returns all). 857 | @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes 858 | for each record found, or C{None} otherwise. 859 | """ 860 | */ 861 | extern "C" PyObject *queryRecordsWithAttributes_list(PyObject *self, PyObject *args) 862 | { 863 | return _queryRecordsWithAttributes(self, args, true); 864 | } 865 | 866 | /* 867 | def authenticateUserBasic(obj, nodename, user, pswd): 868 | """ 869 | Authenticate a user with a password to Open Directory. 870 | 871 | @param obj: C{object} the object obtained from an odInit call. 872 | @param nodename: C{str} the directory nodename for the record to check. 873 | @param user: C{str} the user identifier/directory record name to check. 874 | @param pswd: C{str} containing the password to check. 875 | @return: C{True} if the user was found, C{False} otherwise. 876 | """ 877 | */ 878 | extern "C" PyObject *authenticateUserBasic(PyObject *self, PyObject *args) 879 | { 880 | PyObject* pyds; 881 | const char* nodename; 882 | const char* user; 883 | const char* pswd; 884 | if (!PyArg_ParseTuple(args, "Osss", &pyds, &nodename, &user, &pswd) || !PyCObject_Check(pyds)) 885 | { 886 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices authenticateUserBasic: could not parse arguments", 0)); 887 | return NULL; 888 | } 889 | 890 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 891 | if (dsmgr != NULL) 892 | { 893 | CDirectoryServiceAuth* ds = dsmgr->GetAuthService(); 894 | bool result = false; 895 | bool authresult = false; 896 | result = ds->AuthenticateUserBasic(nodename, user, pswd, authresult); 897 | if (result) 898 | { 899 | if (authresult) 900 | Py_RETURN_TRUE; 901 | else 902 | Py_RETURN_FALSE; 903 | } 904 | } 905 | else 906 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices authenticateUserBasic: invalid directory service argument", 0)); 907 | 908 | return NULL; 909 | } 910 | 911 | /* 912 | def authenticateUserDigest(obj, nodename, user, challenge, response, method): 913 | """ 914 | Authenticate using HTTP Digest credentials to Open Directory. 915 | 916 | @param obj: C{object} the object obtained from an odInit call. 917 | @param nodename: C{str} the directory nodename for the record to check. 918 | @param user: C{str} the user identifier/directory record name to check. 919 | @param challenge: C{str} the HTTP challenge sent to the client. 920 | @param response: C{str} the HTTP response sent from the client. 921 | @param method: C{str} the HTTP method being used. 922 | @return: C{True} if the user was found, C{False} otherwise. 923 | """ 924 | */ 925 | extern "C" PyObject *authenticateUserDigest(PyObject *self, PyObject *args) 926 | { 927 | PyObject* pyds; 928 | const char* nodename; 929 | const char* user; 930 | const char* challenge; 931 | const char* response; 932 | const char* method; 933 | if (!PyArg_ParseTuple(args, "Osssss", &pyds, &nodename, &user, &challenge, &response, &method) || !PyCObject_Check(pyds)) 934 | { 935 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices authenticateUserDigest: could not parse arguments", 0)); 936 | return NULL; 937 | } 938 | 939 | CDirectoryServiceManager* dsmgr = static_cast(PyCObject_AsVoidPtr(pyds)); 940 | if (dsmgr != NULL) 941 | { 942 | CDirectoryServiceAuth* ds = dsmgr->GetAuthService(); 943 | bool result = false; 944 | bool authresult = false; 945 | result = ds->AuthenticateUserDigest(nodename, user, challenge, response, method, authresult); 946 | if (result) 947 | { 948 | if (authresult) 949 | Py_RETURN_TRUE; 950 | else 951 | Py_RETURN_FALSE; 952 | } 953 | } 954 | else 955 | PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices authenticateUserDigest: invalid directory service argument", 0)); 956 | 957 | return NULL; 958 | } 959 | 960 | static PyMethodDef ODMethods[] = { 961 | {"odInit", odInit, METH_VARARGS, 962 | "Initialize the Open Directory system."}, 963 | {"listNodes", listNodes, METH_VARARGS, 964 | "List all the nodes currently configured in Open Directory."}, 965 | {"getNodeAttributes", getNodeAttributes, METH_VARARGS, 966 | "Return key attributes for the specified directory node."}, 967 | {"listAllRecordsWithAttributes", listAllRecordsWithAttributes, METH_VARARGS, 968 | "List all records of the specified type in Open Directory, returning requested attributes."}, 969 | {"queryRecordsWithAttribute", queryRecordsWithAttribute, METH_VARARGS, 970 | "List records in Open Directory matching specified attribute/value, and return key attributes for each one."}, 971 | {"queryRecordsWithAttributes", queryRecordsWithAttributes, METH_VARARGS, 972 | "List records in Open Directory matching specified criteria, and return key attributes for each one."}, 973 | {"listAllRecordsWithAttributes_list", listAllRecordsWithAttributes_list, METH_VARARGS, 974 | "List all records of the specified type in Open Directory, returning requested attributes."}, 975 | {"queryRecordsWithAttribute_list", queryRecordsWithAttribute_list, METH_VARARGS, 976 | "List records in Open Directory matching specified attribute/value, and return key attributes for each one."}, 977 | {"queryRecordsWithAttributes_list", queryRecordsWithAttributes_list, METH_VARARGS, 978 | "List records in Open Directory matching specified criteria, and return key attributes for each one."}, 979 | {"authenticateUserBasic", authenticateUserBasic, METH_VARARGS, 980 | "Authenticate a user with a password to Open Directory using plain text authentication."}, 981 | {"authenticateUserDigest", authenticateUserDigest, METH_VARARGS, 982 | "Authenticate a user with a password to Open Directory using HTTP DIGEST authentication."}, 983 | {NULL, NULL, 0, NULL} /* Sentinel */ 984 | }; 985 | 986 | PyMODINIT_FUNC initopendirectory(void) 987 | { 988 | PyObject* m = Py_InitModule("opendirectory", ODMethods); 989 | 990 | PyObject* d = PyModule_GetDict(m); 991 | 992 | if (!(ODException_class = PyErr_NewException("opendirectory.ODError", NULL, NULL))) 993 | goto error; 994 | PyDict_SetItemString(d, "ODError", ODException_class); 995 | Py_INCREF(ODException_class); 996 | 997 | 998 | error: 999 | if (PyErr_Occurred()) 1000 | PyErr_SetString(PyExc_ImportError, "opendirectory: init failed"); 1001 | } 1002 | -------------------------------------------------------------------------------- /src/base64.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 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 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | #include "base64.h" 18 | 19 | #include 20 | #include 21 | 22 | // base64 tables 23 | static char basis_64[] = 24 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 25 | static signed char index_64[128] = 26 | { 27 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 28 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 29 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 30 | 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, 31 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 32 | 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, 33 | -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 34 | 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 35 | }; 36 | #define CHAR64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)]) 37 | 38 | // base64_encode : base64 encode 39 | // 40 | // value : data to encode 41 | // vlen : length of data 42 | // (result) : new char[] - c-str of result 43 | char *base64_encode(const unsigned char *value, int vlen) 44 | { 45 | char *result = (char *)malloc((vlen * 4) / 3 + 5); 46 | char *out = result; 47 | while (vlen >= 3) 48 | { 49 | *out++ = basis_64[value[0] >> 2]; 50 | *out++ = basis_64[((value[0] << 4) & 0x30) | (value[1] >> 4)]; 51 | *out++ = basis_64[((value[1] << 2) & 0x3C) | (value[2] >> 6)]; 52 | *out++ = basis_64[value[2] & 0x3F]; 53 | value += 3; 54 | vlen -= 3; 55 | } 56 | if (vlen > 0) 57 | { 58 | *out++ = basis_64[value[0] >> 2]; 59 | unsigned char oval = (value[0] << 4) & 0x30; 60 | if (vlen > 1) oval |= value[1] >> 4; 61 | *out++ = basis_64[oval]; 62 | *out++ = (vlen < 2) ? '=' : basis_64[(value[1] << 2) & 0x3C]; 63 | *out++ = '='; 64 | } 65 | *out = '\0'; 66 | 67 | return result; 68 | } 69 | 70 | // base64_decode : base64 decode 71 | // 72 | // value : c-str to decode 73 | // rlen : length of decoded result 74 | // (result) : new unsigned char[] - decoded result 75 | unsigned char *base64_decode(const char *value, int *rlen) 76 | { 77 | *rlen = 0; 78 | int c1, c2, c3, c4; 79 | 80 | int vlen = strlen(value); 81 | unsigned char *result =(unsigned char *)malloc((vlen * 3) / 4 + 1); 82 | unsigned char *out = result; 83 | 84 | while (1) 85 | { 86 | if (value[0]==0) 87 | return result; 88 | c1 = value[0]; 89 | if (CHAR64(c1) == -1) 90 | goto base64_decode_error;; 91 | c2 = value[1]; 92 | if (CHAR64(c2) == -1) 93 | goto base64_decode_error;; 94 | c3 = value[2]; 95 | if ((c3 != '=') && (CHAR64(c3) == -1)) 96 | goto base64_decode_error;; 97 | c4 = value[3]; 98 | if ((c4 != '=') && (CHAR64(c4) == -1)) 99 | goto base64_decode_error;; 100 | 101 | value += 4; 102 | *out++ = (CHAR64(c1) << 2) | (CHAR64(c2) >> 4); 103 | *rlen += 1; 104 | if (c3 != '=') 105 | { 106 | *out++ = ((CHAR64(c2) << 4) & 0xf0) | (CHAR64(c3) >> 2); 107 | *rlen += 1; 108 | if (c4 != '=') 109 | { 110 | *out++ = ((CHAR64(c3) << 6) & 0xc0) | CHAR64(c4); 111 | *rlen += 1; 112 | } 113 | } 114 | } 115 | 116 | base64_decode_error: 117 | *result = 0; 118 | *rlen = 0; 119 | return result; 120 | } 121 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 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 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | char *base64_encode(const unsigned char *value, int vlen); 18 | unsigned char *base64_decode(const char *value, int *rlen); 19 | -------------------------------------------------------------------------------- /support/PyOpenDirectory.xcodeproj/cyrusdaboo.pbxuser: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 4 | activeBuildConfigurationName = Debug; 5 | activeExecutable = AF155A290A501F5C007E1E6E /* PyOpenDirectory */; 6 | activeTarget = 8DD76F620486A84900D96B5E /* PyOpenDirectory */; 7 | addToTargets = ( 8 | 8DD76F620486A84900D96B5E /* PyOpenDirectory */, 9 | ); 10 | breakpoints = ( 11 | AF444F3C0F79A3A5005606BB /* test.cpp:80 */, 12 | ); 13 | codeSenseManager = AF155A2E0A501F7B007E1E6E /* Code sense */; 14 | executables = ( 15 | AF155A290A501F5C007E1E6E /* PyOpenDirectory */, 16 | ); 17 | perUserDictionary = { 18 | "PBXConfiguration.PBXBreakpointsDataSource.v1:1CA1AED706398EBD00589147" = { 19 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 20 | PBXFileTableDataSourceColumnSortingKey = PBXBreakpointsDataSource_BreakpointID; 21 | PBXFileTableDataSourceColumnWidthsKey = ( 22 | 20, 23 | 20, 24 | 198, 25 | 20, 26 | 99, 27 | 99, 28 | 29, 29 | 20, 30 | ); 31 | PBXFileTableDataSourceColumnsKey = ( 32 | PBXBreakpointsDataSource_ActionID, 33 | PBXBreakpointsDataSource_TypeID, 34 | PBXBreakpointsDataSource_BreakpointID, 35 | PBXBreakpointsDataSource_UseID, 36 | PBXBreakpointsDataSource_LocationID, 37 | PBXBreakpointsDataSource_ConditionID, 38 | PBXBreakpointsDataSource_IgnoreCountID, 39 | PBXBreakpointsDataSource_ContinueID, 40 | ); 41 | }; 42 | PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { 43 | PBXFileTableDataSourceColumnSortingDirectionKey = 1; 44 | PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; 45 | PBXFileTableDataSourceColumnWidthsKey = ( 46 | 20, 47 | 892, 48 | 20, 49 | 48, 50 | 43, 51 | 43, 52 | 20, 53 | ); 54 | PBXFileTableDataSourceColumnsKey = ( 55 | PBXFileDataSource_FiletypeID, 56 | PBXFileDataSource_Filename_ColumnID, 57 | PBXFileDataSource_Built_ColumnID, 58 | PBXFileDataSource_ObjectSize_ColumnID, 59 | PBXFileDataSource_Errors_ColumnID, 60 | PBXFileDataSource_Warnings_ColumnID, 61 | PBXFileDataSource_Target_ColumnID, 62 | ); 63 | }; 64 | PBXPerProjectTemplateStateSaveDate = 259621147; 65 | PBXWorkspaceStateSaveDate = 259621147; 66 | }; 67 | sourceControlManager = AF155A2D0A501F7B007E1E6E /* Source Control */; 68 | userBuildSettings = { 69 | }; 70 | }; 71 | 08FB7796FE84155DC02AAC07 /* test.cpp */ = { 72 | uiCtxt = { 73 | sepNavIntBoundsRect = "{{0, 0}, {1379, 5404}}"; 74 | sepNavSelRange = "{2804, 0}"; 75 | sepNavVisRange = "{2302, 755}"; 76 | sepNavVisRect = "{{0, 1574}, {1521, 647}}"; 77 | sepNavWindowFrame = "{{168, 4}, {1220, 874}}"; 78 | }; 79 | }; 80 | 8DD76F620486A84900D96B5E /* PyOpenDirectory */ = { 81 | activeExec = 0; 82 | executables = ( 83 | AF155A290A501F5C007E1E6E /* PyOpenDirectory */, 84 | ); 85 | }; 86 | AF02AC560CBE690500F478B8 /* CDirectoryServiceException.cpp */ = { 87 | uiCtxt = { 88 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1041}}"; 89 | sepNavSelRange = "{608, 0}"; 90 | sepNavVisRange = "{0, 1744}"; 91 | sepNavWindowFrame = "{{684, 53}, {1145, 959}}"; 92 | }; 93 | }; 94 | AF02AC570CBE690500F478B8 /* CDirectoryServiceException.h */ = { 95 | uiCtxt = { 96 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1041}}"; 97 | sepNavSelRange = "{1019, 0}"; 98 | sepNavVisRange = "{0, 1391}"; 99 | sepNavWindowFrame = "{{73, 219}, {1145, 959}}"; 100 | }; 101 | }; 102 | AF155A290A501F5C007E1E6E /* PyOpenDirectory */ = { 103 | isa = PBXExecutable; 104 | activeArgIndices = ( 105 | ); 106 | argumentStrings = ( 107 | ); 108 | autoAttachOnCrash = 1; 109 | breakpointsEnabled = 1; 110 | configStateDict = { 111 | }; 112 | customDataFormattersEnabled = 1; 113 | debuggerPlugin = GDBDebugging; 114 | disassemblyDisplayState = 0; 115 | dylibVariantSuffix = ""; 116 | enableDebugStr = 1; 117 | environmentEntries = ( 118 | ); 119 | executableSystemSymbolLevel = 0; 120 | executableUserSymbolLevel = 0; 121 | libgmallocEnabled = 0; 122 | name = PyOpenDirectory; 123 | savedGlobals = { 124 | }; 125 | sourceDirectories = ( 126 | ); 127 | variableFormatDictionary = { 128 | $cs = 1; 129 | $ds = 1; 130 | $eax = 1; 131 | $ebp = 1; 132 | $ebx = 1; 133 | $ecx = 1; 134 | $edi = 1; 135 | $edx = 1; 136 | $eflags = 1; 137 | $eip = 1; 138 | $es = 1; 139 | $esi = 1; 140 | $esp = 1; 141 | $gs = 1; 142 | $ss = 1; 143 | }; 144 | }; 145 | AF155A2D0A501F7B007E1E6E /* Source Control */ = { 146 | isa = PBXSourceControlManager; 147 | fallbackIsa = XCSourceControlManager; 148 | isSCMEnabled = 0; 149 | scmConfiguration = { 150 | repositoryNamesForRoots = { 151 | }; 152 | }; 153 | scmType = ""; 154 | }; 155 | AF155A2E0A501F7B007E1E6E /* Code sense */ = { 156 | isa = PBXCodeSenseManager; 157 | indexTemplatePath = ""; 158 | }; 159 | AF155A2F0A501F84007E1E6E /* CDirectoryService.cpp */ = { 160 | uiCtxt = { 161 | sepNavIntBoundsRect = "{{0, 0}, {1140, 17822}}"; 162 | sepNavSelRange = "{2771, 0}"; 163 | sepNavVisRange = "{10094, 2039}"; 164 | sepNavVisRect = "{{0, 0}, {1309, 1017}}"; 165 | sepNavWindowFrame = "{{85, 4}, {1280, 1174}}"; 166 | }; 167 | }; 168 | AF155A300A501F84007E1E6E /* CDirectoryService.h */ = { 169 | uiCtxt = { 170 | sepNavIntBoundsRect = "{{0, 0}, {1290, 1330}}"; 171 | sepNavSelRange = "{1038, 120}"; 172 | sepNavVisRange = "{890, 2046}"; 173 | sepNavVisRect = "{{0, 123}, {752, 676}}"; 174 | sepNavWindowFrame = "{{50, 9}, {1390, 869}}"; 175 | }; 176 | }; 177 | AF155A310A501F84007E1E6E /* PythonWrapper.cpp */ = { 178 | uiCtxt = { 179 | sepNavIntBoundsRect = "{{0, 0}, {1163, 14070}}"; 180 | sepNavSelRange = "{21895, 0}"; 181 | sepNavVisRange = "{21578, 468}"; 182 | sepNavVisRect = "{{0, 0}, {1521, 647}}"; 183 | sepNavWindowFrame = "{{89, 4}, {1043, 828}}"; 184 | }; 185 | }; 186 | AF155AFB0A502C09007E1E6E /* CFStringUtil.h */ = { 187 | uiCtxt = { 188 | sepNavIntBoundsRect = "{{0, 0}, {1070, 741}}"; 189 | sepNavSelRange = "{790, 12}"; 190 | sepNavVisRange = "{0, 1120}"; 191 | sepNavVisRect = "{{0, 0}, {766, 699}}"; 192 | sepNavWindowFrame = "{{746, 4}, {811, 828}}"; 193 | }; 194 | }; 195 | AF155AFC0A502C09007E1E6E /* CFStringUtil.cpp */ = { 196 | uiCtxt = { 197 | sepNavIntBoundsRect = "{{0, 0}, {1113, 2128}}"; 198 | sepNavSelRange = "{0, 0}"; 199 | sepNavVisRange = "{0, 1337}"; 200 | sepNavVisRect = "{{0, 1572}, {1342, 371}}"; 201 | sepNavWindowFrame = "{{36, 4}, {811, 1024}}"; 202 | }; 203 | }; 204 | AF41D9AB0CBDBAE200AB863D /* CDirectoryServiceManager.cpp */ = { 205 | uiCtxt = { 206 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1041}}"; 207 | sepNavSelRange = "{713, 0}"; 208 | sepNavVisRange = "{0, 1469}"; 209 | sepNavWindowFrame = "{{558, 246}, {1280, 828}}"; 210 | }; 211 | }; 212 | AF41D9AC0CBDBAE200AB863D /* CDirectoryServiceManager.h */ = { 213 | uiCtxt = { 214 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1041}}"; 215 | sepNavSelRange = "{916, 0}"; 216 | sepNavVisRange = "{0, 1074}"; 217 | sepNavWindowFrame = "{{677, 13}, {1280, 828}}"; 218 | }; 219 | }; 220 | AF444F3C0F79A3A5005606BB /* test.cpp:80 */ = { 221 | isa = PBXFileBreakpoint; 222 | actions = ( 223 | ); 224 | breakpointStyle = 0; 225 | continueAfterActions = 0; 226 | countType = 0; 227 | delayBeforeContinue = 0; 228 | fileReference = 08FB7796FE84155DC02AAC07 /* test.cpp */; 229 | functionName = "main (int argc, const char * argv[])"; 230 | hitCount = 1; 231 | ignoreCount = 0; 232 | lineNumber = 80; 233 | location = PyOpenDirectory; 234 | modificationTime = 259630172.337334; 235 | state = 2; 236 | }; 237 | AFC1CA770E809C5200FAB3DB /* base64.h */ = { 238 | uiCtxt = { 239 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1041}}"; 240 | sepNavSelRange = "{0, 0}"; 241 | sepNavVisRange = "{0, 746}"; 242 | sepNavWindowFrame = "{{15, -1}, {1067, 874}}"; 243 | }; 244 | }; 245 | AFC1CA780E809C5200FAB3DB /* base64.cpp */ = { 246 | uiCtxt = { 247 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1610}}"; 248 | sepNavSelRange = "{1633, 0}"; 249 | sepNavVisRange = "{0, 2561}"; 250 | }; 251 | }; 252 | AFC9AC0A0EF8A3FC0050787E /* CDirectoryServiceAuth.h */ = { 253 | uiCtxt = { 254 | sepNavIntBoundsRect = "{{0, 0}, {1593, 1018}}"; 255 | sepNavSelRange = "{1662, 0}"; 256 | sepNavVisRange = "{0, 1665}"; 257 | }; 258 | }; 259 | AFC9AC0B0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp */ = { 260 | uiCtxt = { 261 | sepNavIntBoundsRect = "{{0, 0}, {1535, 4200}}"; 262 | sepNavSelRange = "{4382, 16}"; 263 | sepNavVisRange = "{2963, 2221}"; 264 | }; 265 | }; 266 | } 267 | -------------------------------------------------------------------------------- /support/PyOpenDirectory.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 42; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 661DFBCA1088E5C900846632 /* libsasl2.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 661DFBC91088E5C900846632 /* libsasl2.2.dylib */; }; 11 | 8DD76F650486A84900D96B5E /* test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* test.cpp */; settings = {ATTRIBUTES = (); }; }; 12 | AF00155A0B8A21340045DAEE /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF0015590B8A21340045DAEE /* Python.framework */; }; 13 | AF00155E0B8A21FD0045DAEE /* PythonWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF155A310A501F84007E1E6E /* PythonWrapper.cpp */; }; 14 | AF02AC580CBE690500F478B8 /* CDirectoryServiceException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF02AC560CBE690500F478B8 /* CDirectoryServiceException.cpp */; }; 15 | AF155A320A501F84007E1E6E /* CDirectoryService.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF155A2F0A501F84007E1E6E /* CDirectoryService.cpp */; }; 16 | AF155A330A501F84007E1E6E /* CDirectoryService.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AF155A300A501F84007E1E6E /* CDirectoryService.h */; }; 17 | AF155A370A501F9D007E1E6E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF155A350A501F9D007E1E6E /* CoreFoundation.framework */; }; 18 | AF155A380A501F9D007E1E6E /* DirectoryService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF155A360A501F9D007E1E6E /* DirectoryService.framework */; }; 19 | AF155AFD0A502C09007E1E6E /* CFStringUtil.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AF155AFB0A502C09007E1E6E /* CFStringUtil.h */; }; 20 | AF155AFE0A502C09007E1E6E /* CFStringUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF155AFC0A502C09007E1E6E /* CFStringUtil.cpp */; }; 21 | AF41D9AD0CBDBAE200AB863D /* CDirectoryServiceManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF41D9AB0CBDBAE200AB863D /* CDirectoryServiceManager.cpp */; }; 22 | AFC1CA790E809C5200FAB3DB /* base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFC1CA780E809C5200FAB3DB /* base64.cpp */; }; 23 | AFC9AC0C0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFC9AC0B0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 8DD76F690486A84900D96B5E /* CopyFiles */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 8; 30 | dstPath = /usr/share/man/man1/; 31 | dstSubfolderSpec = 0; 32 | files = ( 33 | AF155A330A501F84007E1E6E /* CDirectoryService.h in CopyFiles */, 34 | AF155AFD0A502C09007E1E6E /* CFStringUtil.h in CopyFiles */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 1; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 08FB7796FE84155DC02AAC07 /* test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test.cpp; sourceTree = ""; }; 42 | 661DFBC91088E5C900846632 /* libsasl2.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsasl2.2.dylib; path = /usr/lib/libsasl2.2.dylib; sourceTree = ""; }; 43 | 8DD76F6C0486A84900D96B5E /* PyOpenDirectory */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PyOpenDirectory; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | AF0015590B8A21340045DAEE /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = /System/Library/Frameworks/Python.framework; sourceTree = ""; }; 45 | AF02AC560CBE690500F478B8 /* CDirectoryServiceException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CDirectoryServiceException.cpp; path = ../src/CDirectoryServiceException.cpp; sourceTree = SOURCE_ROOT; }; 46 | AF02AC570CBE690500F478B8 /* CDirectoryServiceException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDirectoryServiceException.h; path = ../src/CDirectoryServiceException.h; sourceTree = SOURCE_ROOT; }; 47 | AF155A2F0A501F84007E1E6E /* CDirectoryService.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = CDirectoryService.cpp; path = ../src/CDirectoryService.cpp; sourceTree = SOURCE_ROOT; }; 48 | AF155A300A501F84007E1E6E /* CDirectoryService.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = CDirectoryService.h; path = ../src/CDirectoryService.h; sourceTree = SOURCE_ROOT; }; 49 | AF155A310A501F84007E1E6E /* PythonWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = PythonWrapper.cpp; path = ../src/PythonWrapper.cpp; sourceTree = SOURCE_ROOT; }; 50 | AF155A350A501F9D007E1E6E /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; 51 | AF155A360A501F9D007E1E6E /* DirectoryService.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DirectoryService.framework; path = /System/Library/Frameworks/DirectoryService.framework; sourceTree = ""; }; 52 | AF155AFB0A502C09007E1E6E /* CFStringUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CFStringUtil.h; path = ../src/CFStringUtil.h; sourceTree = SOURCE_ROOT; }; 53 | AF155AFC0A502C09007E1E6E /* CFStringUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CFStringUtil.cpp; path = ../src/CFStringUtil.cpp; sourceTree = SOURCE_ROOT; }; 54 | AF41D9AB0CBDBAE200AB863D /* CDirectoryServiceManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CDirectoryServiceManager.cpp; path = ../src/CDirectoryServiceManager.cpp; sourceTree = SOURCE_ROOT; }; 55 | AF41D9AC0CBDBAE200AB863D /* CDirectoryServiceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDirectoryServiceManager.h; path = ../src/CDirectoryServiceManager.h; sourceTree = SOURCE_ROOT; }; 56 | AFC1CA770E809C5200FAB3DB /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = base64.h; path = ../src/base64.h; sourceTree = SOURCE_ROOT; }; 57 | AFC1CA780E809C5200FAB3DB /* base64.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.c; fileEncoding = 4; name = base64.cpp; path = ../src/base64.cpp; sourceTree = SOURCE_ROOT; }; 58 | AFC9AC0A0EF8A3FC0050787E /* CDirectoryServiceAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDirectoryServiceAuth.h; path = ../src/CDirectoryServiceAuth.h; sourceTree = SOURCE_ROOT; }; 59 | AFC9AC0B0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CDirectoryServiceAuth.cpp; path = ../src/CDirectoryServiceAuth.cpp; sourceTree = SOURCE_ROOT; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 8DD76F660486A84900D96B5E /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | AF155A370A501F9D007E1E6E /* CoreFoundation.framework in Frameworks */, 68 | AF155A380A501F9D007E1E6E /* DirectoryService.framework in Frameworks */, 69 | AF00155A0B8A21340045DAEE /* Python.framework in Frameworks */, 70 | 661DFBCA1088E5C900846632 /* libsasl2.2.dylib in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 08FB7794FE84155DC02AAC07 /* PyOpenDirectory */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 08FB7795FE84155DC02AAC07 /* Source */, 81 | AF155A3D0A501FA0007E1E6E /* Frameworks */, 82 | 1AB674ADFE9D54B511CA2CBB /* Products */, 83 | ); 84 | name = PyOpenDirectory; 85 | sourceTree = ""; 86 | }; 87 | 08FB7795FE84155DC02AAC07 /* Source */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | AF02AC560CBE690500F478B8 /* CDirectoryServiceException.cpp */, 91 | AF02AC570CBE690500F478B8 /* CDirectoryServiceException.h */, 92 | AF41D9AB0CBDBAE200AB863D /* CDirectoryServiceManager.cpp */, 93 | AF41D9AC0CBDBAE200AB863D /* CDirectoryServiceManager.h */, 94 | AF155A2F0A501F84007E1E6E /* CDirectoryService.cpp */, 95 | AF155A300A501F84007E1E6E /* CDirectoryService.h */, 96 | AFC9AC0B0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp */, 97 | AFC9AC0A0EF8A3FC0050787E /* CDirectoryServiceAuth.h */, 98 | AF155A310A501F84007E1E6E /* PythonWrapper.cpp */, 99 | 08FB7796FE84155DC02AAC07 /* test.cpp */, 100 | AF155AFC0A502C09007E1E6E /* CFStringUtil.cpp */, 101 | AF155AFB0A502C09007E1E6E /* CFStringUtil.h */, 102 | AFC1CA780E809C5200FAB3DB /* base64.cpp */, 103 | AFC1CA770E809C5200FAB3DB /* base64.h */, 104 | ); 105 | name = Source; 106 | sourceTree = ""; 107 | }; 108 | 1AB674ADFE9D54B511CA2CBB /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 8DD76F6C0486A84900D96B5E /* PyOpenDirectory */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | AF155A3D0A501FA0007E1E6E /* Frameworks */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | AF0015590B8A21340045DAEE /* Python.framework */, 120 | AF155A350A501F9D007E1E6E /* CoreFoundation.framework */, 121 | AF155A360A501F9D007E1E6E /* DirectoryService.framework */, 122 | 661DFBC91088E5C900846632 /* libsasl2.2.dylib */, 123 | ); 124 | name = Frameworks; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 8DD76F620486A84900D96B5E /* PyOpenDirectory */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "PyOpenDirectory" */; 133 | buildPhases = ( 134 | 8DD76F640486A84900D96B5E /* Sources */, 135 | 8DD76F660486A84900D96B5E /* Frameworks */, 136 | 8DD76F690486A84900D96B5E /* CopyFiles */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = PyOpenDirectory; 143 | productInstallPath = "$(HOME)/bin"; 144 | productName = PyOpenDirectory; 145 | productReference = 8DD76F6C0486A84900D96B5E /* PyOpenDirectory */; 146 | productType = "com.apple.product-type.tool"; 147 | }; 148 | /* End PBXNativeTarget section */ 149 | 150 | /* Begin PBXProject section */ 151 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 152 | isa = PBXProject; 153 | buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "PyOpenDirectory" */; 154 | compatibilityVersion = "Xcode 2.4"; 155 | hasScannedForEncodings = 1; 156 | mainGroup = 08FB7794FE84155DC02AAC07 /* PyOpenDirectory */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 8DD76F620486A84900D96B5E /* PyOpenDirectory */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 8DD76F640486A84900D96B5E /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 8DD76F650486A84900D96B5E /* test.cpp in Sources */, 171 | AF155A320A501F84007E1E6E /* CDirectoryService.cpp in Sources */, 172 | AF155AFE0A502C09007E1E6E /* CFStringUtil.cpp in Sources */, 173 | AF00155E0B8A21FD0045DAEE /* PythonWrapper.cpp in Sources */, 174 | AF41D9AD0CBDBAE200AB863D /* CDirectoryServiceManager.cpp in Sources */, 175 | AF02AC580CBE690500F478B8 /* CDirectoryServiceException.cpp in Sources */, 176 | AFC1CA790E809C5200FAB3DB /* base64.cpp in Sources */, 177 | AFC9AC0C0EF8A3FC0050787E /* CDirectoryServiceAuth.cpp in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin XCBuildConfiguration section */ 184 | 1DEB923208733DC60010E9CD /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | COPY_PHASE_STRIP = NO; 188 | GCC_DYNAMIC_NO_PIC = NO; 189 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 190 | GCC_MODEL_TUNING = G5; 191 | GCC_OPTIMIZATION_LEVEL = 0; 192 | INSTALL_PATH = "$(HOME)/bin"; 193 | PRODUCT_NAME = PyOpenDirectory; 194 | ZERO_LINK = YES; 195 | }; 196 | name = Debug; 197 | }; 198 | 1DEB923308733DC60010E9CD /* Release */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ARCHS = ( 202 | ppc, 203 | i386, 204 | ); 205 | GCC_GENERATE_DEBUGGING_SYMBOLS = NO; 206 | GCC_MODEL_TUNING = G5; 207 | INSTALL_PATH = "$(HOME)/bin"; 208 | PRODUCT_NAME = PyOpenDirectory; 209 | }; 210 | name = Release; 211 | }; 212 | 1DEB923608733DC60010E9CD /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; 216 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 217 | GCC_WARN_UNUSED_VARIABLE = YES; 218 | HEADER_SEARCH_PATHS = /System/Library/Frameworks/Python.framework/Headers; 219 | PREBINDING = NO; 220 | SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; 221 | USER_HEADER_SEARCH_PATHS = ../src; 222 | }; 223 | name = Debug; 224 | }; 225 | 1DEB923708733DC60010E9CD /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; 229 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 230 | GCC_WARN_UNUSED_VARIABLE = YES; 231 | HEADER_SEARCH_PATHS = ""; 232 | PREBINDING = NO; 233 | SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; 234 | USER_HEADER_SEARCH_PATHS = ../src; 235 | }; 236 | name = Release; 237 | }; 238 | /* End XCBuildConfiguration section */ 239 | 240 | /* Begin XCConfigurationList section */ 241 | 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "PyOpenDirectory" */ = { 242 | isa = XCConfigurationList; 243 | buildConfigurations = ( 244 | 1DEB923208733DC60010E9CD /* Debug */, 245 | 1DEB923308733DC60010E9CD /* Release */, 246 | ); 247 | defaultConfigurationIsVisible = 0; 248 | defaultConfigurationName = Release; 249 | }; 250 | 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "PyOpenDirectory" */ = { 251 | isa = XCConfigurationList; 252 | buildConfigurations = ( 253 | 1DEB923608733DC60010E9CD /* Debug */, 254 | 1DEB923708733DC60010E9CD /* Release */, 255 | ); 256 | defaultConfigurationIsVisible = 0; 257 | defaultConfigurationName = Release; 258 | }; 259 | /* End XCConfigurationList section */ 260 | }; 261 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 262 | } 263 | -------------------------------------------------------------------------------- /support/test.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2006-2009 Apple Inc. All rights reserved. 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 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "CDirectoryService.h" 25 | #include "CDirectoryServiceAuth.h" 26 | #include "CFStringUtil.h" 27 | 28 | tDirReference gDirRef = NULL; 29 | 30 | char* CStringFromCFString(CFStringRef str); 31 | void PrintDictionaryDictionary(const void* key, const void* value, void* ref); 32 | void PrintDictionary(const void* key, const void* value, void* ref); 33 | void PrintArrayArray(CFMutableArrayRef list); 34 | void PrintArray(CFArrayRef list); 35 | void AuthenticateUser(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* pswd); 36 | void AuthenticateUserDigest(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* challenge, const char* response, const char* method); 37 | void AuthenticateUserDigestToActiveDirectory(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* response); 38 | void GetDigestMD5ChallengeFromActiveDirectory(CDirectoryServiceAuth* dir, const char* nodename); 39 | 40 | void AuthenticateUserDigestODAD(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* pswd, bool verbose = false); 41 | 42 | CFStringRef GetClientResponseFromSASL( const char* username, const char* pswd, const char* serverchallenge ); 43 | CFStringRef GetDigestMD5ChallengeFromSASL( void ); 44 | 45 | #define kDSStdRecordTypeResources "dsRecTypeStandard:Resources" 46 | #define kDSNAttrServicesLocator "dsAttrTypeStandard:ServicesLocator" 47 | #define kDSNAttrJPEGPhoto "dsAttrTypeStandard:JPEGPhoto" 48 | 49 | void ListNodes(CDirectoryService* dir) 50 | { 51 | CFMutableArrayRef data = dir->ListNodes(false); 52 | if (data != NULL) 53 | { 54 | printf("\n*** Nodes: %ld ***\n", CFArrayGetCount(data)); 55 | for(CFIndex i = 0; i < CFArrayGetCount(data); i++) 56 | { 57 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(data, i); 58 | const char* bytes = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 59 | 60 | if (bytes == NULL) 61 | { 62 | char localBuffer[256]; 63 | Boolean success; 64 | success = CFStringGetCString(str, localBuffer, 256, kCFStringEncodingUTF8); 65 | printf("%ld: %s\n", i, localBuffer); 66 | } 67 | else 68 | { 69 | printf("%ld: %s\n", i, (const char*)bytes); 70 | } 71 | } 72 | CFRelease(data); 73 | } 74 | } 75 | 76 | void GetNodeAttributes(CDirectoryService* dir) 77 | { 78 | CFStringRef attrs[2]; 79 | attrs[0] = CFSTR(kDS1AttrSearchPath); 80 | attrs[1] = CFSTR(kDS1AttrReadOnlyNode); 81 | 82 | CFStringRef types[2]; 83 | types[0] = CFSTR("str"); 84 | types[1] = CFSTR("str"); 85 | CFDictionaryRef attrsdict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)attrs, (const void **)types, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 86 | 87 | CFMutableDictionaryRef nodeData = dir->GetNodeAttributes("/Search", attrsdict, false); 88 | if (nodeData != NULL) 89 | { 90 | printf("\n*** Node Attributes: %ld ***\n", CFDictionaryGetCount(nodeData)); 91 | CFDictionaryApplyFunction(nodeData, PrintDictionary, NULL); 92 | CFRelease(nodeData); 93 | } 94 | CFRelease(attrsdict); 95 | } 96 | 97 | void GetUserRecordDetails(CDirectoryService* dir) 98 | { 99 | CFStringRef attrs[3]; 100 | attrs[0] = CFSTR(kDS1AttrDistinguishedName); 101 | attrs[1] = CFSTR(kDS1AttrGeneratedUID); 102 | attrs[2] = CFSTR(kDSNAttrJPEGPhoto); 103 | 104 | CFStringRef types[3]; 105 | types[0] = CFSTR("str"); 106 | types[1] = CFSTR("str"); 107 | types[2] = CFSTR("base64"); 108 | CFDictionaryRef attrsdict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)attrs, (const void **)types, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 109 | 110 | CFStringRef rtypes[1]; 111 | rtypes[0] = CFSTR(kDSStdRecordTypeUsers); 112 | CFArrayRef recordTypes = CFArrayCreate(kCFAllocatorDefault, (const void**)rtypes, 1, &kCFTypeArrayCallBacks); 113 | 114 | CFMutableArrayRef data = dir->ListAllRecordsWithAttributes(recordTypes, attrsdict, 0, false); 115 | if (data != NULL) 116 | { 117 | printf("\n*** Users: %ld ***\n", CFArrayGetCount(data)); 118 | for(CFIndex i = 0; i < CFArrayGetCount(data); i++) 119 | { 120 | CFArrayRef tuple = (CFArrayRef)CFArrayGetValueAtIndex(data, i); 121 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(tuple, 0); 122 | const char* bytes = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 123 | 124 | if (bytes == NULL) 125 | { 126 | char localBuffer[256]; 127 | Boolean success; 128 | success = CFStringGetCString(str, localBuffer, 256, kCFStringEncodingUTF8); 129 | printf("%ld: %s\n", i, localBuffer); 130 | } 131 | else 132 | { 133 | printf("%ld: %s\n", i, (const char*)bytes); 134 | } 135 | } 136 | CFRelease(data); 137 | } 138 | else 139 | { 140 | printf("\nNo Users returned\n"); 141 | } 142 | CFRelease(attrsdict); 143 | } 144 | 145 | void GetGroupRecordDetails(CDirectoryService* dir) 146 | { 147 | #if 0 148 | CFStringRef strings[2]; 149 | strings[0] = CFSTR(kDSNAttrGroupMembers); 150 | strings[1] = CFSTR(kDS1AttrGeneratedUID); 151 | CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, (const void **)strings, 2, &kCFTypeArrayCallBacks); 152 | 153 | CFMutableArrayRef data = dir->ListAllRecordsWithAttributes(kDSStdRecordTypeGroups, array); 154 | if (data != NULL) 155 | { 156 | printf("\n*** Groups: %d ***\n", CFArrayGetCount(data)); 157 | for(CFIndex i = 0; i < CFArrayGetCount(data); i++) 158 | { 159 | CFArrayRef tuple = (CFArrayRef)CFArrayGetValueAtIndex(data, i); 160 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(tuple, 0); 161 | const char* bytes = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 162 | 163 | if (bytes == NULL) 164 | { 165 | char localBuffer[256]; 166 | Boolean success; 167 | success = CFStringGetCString(str, localBuffer, 256, kCFStringEncodingUTF8); 168 | printf("%ld: %s\n", i, localBuffer); 169 | } 170 | else 171 | { 172 | printf("%ld: %s\n", i, (const char*)bytes); 173 | } 174 | } 175 | CFRelease(data); 176 | } 177 | else 178 | { 179 | printf("\nNo Groups returned\n"); 180 | } 181 | CFRelease(array); 182 | #endif 183 | } 184 | 185 | void GetSpecificUserRecordDetails(CDirectoryService* dir) 186 | { 187 | #if 0 188 | CFStringRef keys[2]; 189 | keys[0] = CFSTR(kDS1AttrFirstName); 190 | keys[1] = CFSTR(kDS1AttrLastName); 191 | CFStringRef values[2]; 192 | values[0] = CFSTR("cyrus"); 193 | values[1] = CFSTR("daboo"); 194 | CFDictionaryRef kvdict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void**)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 195 | 196 | CFStringRef strings[2]; 197 | strings[0] = CFSTR(kDS1AttrDistinguishedName); 198 | strings[1] = CFSTR(kDS1AttrGeneratedUID); 199 | CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, (const void **)strings, 2, &kCFTypeArrayCallBacks); 200 | 201 | CFMutableDictionaryRef dict = dir->QueryRecordsWithAttributes(kvdict, eDSContains, false, false, kDSStdRecordTypeUsers, array); 202 | if (dict != NULL) 203 | { 204 | printf("\n*** Users: %d ***\n", CFDictionaryGetCount(dict)); 205 | CFDictionaryApplyFunction(dict, PrintDictionaryDictionary, NULL); 206 | CFRelease(dict); 207 | } 208 | else 209 | { 210 | printf("\nNo Users returned\n"); 211 | } 212 | CFRelease(array); 213 | #endif 214 | } 215 | 216 | void GetResourcesRecordDetails(CDirectoryService* dir) 217 | { 218 | #if 0 219 | CFStringRef strings[2]; 220 | strings[0] = CFSTR(kDS1AttrDistinguishedName); 221 | strings[1] = CFSTR(kDS1AttrXMLPlist); 222 | CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, (const void **)strings, 2, &kCFTypeArrayCallBacks); 223 | 224 | CFMutableDictionaryRef dict = dir->QueryRecordsWithAttribute(kDSNAttrServicesLocator, "D9A8E41B", eDSStartsWith, false, kDSStdRecordTypeResources, array); 225 | if (dict != NULL) 226 | { 227 | printf("\n*** Computers: %d ***\n", CFDictionaryGetCount(dict)); 228 | CFDictionaryApplyFunction(dict, PrintDictionaryDictionary, NULL); 229 | CFRelease(dict); 230 | } 231 | else 232 | { 233 | printf("\nNo Users returned\n"); 234 | } 235 | CFRelease(array); 236 | #endif 237 | } 238 | 239 | void GetCompoundResourcesRecordDetails(CDirectoryService* dir) 240 | { 241 | #if 0 242 | const char* compoundtest = "(&(|(dsAttrTypeStandard:RealName=U2*)(dsAttrTypeStandard:RealName=X S*))(dsAttrTypeStandard:ServicesLocator=D9A8E41B-C591-4D6B-A1CA-B57FFB8EF2F5:F967C034-54B8-4E65-9B38-7A6CD2600268:calendar))"; 243 | 244 | CFStringRef strings[2]; 245 | strings[0] = CFSTR(kDS1AttrDistinguishedName); 246 | strings[1] = CFSTR(kDS1AttrXMLPlist); 247 | CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, (const void **)strings, 2, &kCFTypeArrayCallBacks); 248 | 249 | CFMutableDictionaryRef dict = dir->QueryRecordsWithAttributes(compoundtest, true, kDSStdRecordTypeResources, array); 250 | if (dict != NULL) 251 | { 252 | printf("\n*** Computers: %d ***\n", CFDictionaryGetCount(dict)); 253 | CFDictionaryApplyFunction(dict, PrintDictionaryDictionary, NULL); 254 | CFRelease(dict); 255 | } 256 | else 257 | { 258 | printf("\nNo Users returned\n"); 259 | } 260 | CFRelease(array); 261 | #endif 262 | } 263 | 264 | int main (int argc, const char * argv[]) { 265 | 266 | //CDirectoryService* dir = new CDirectoryService("/Search"); 267 | CDirectoryServiceAuth* authdir = new CDirectoryServiceAuth(); 268 | 269 | //AuthenticateUser(authdir, "/LDAPv3/127.0.0.1", "oliverdaboo", "oliver"); 270 | AuthenticateUser(authdir, "/LDAPv3/127.0.0.1", "eleanordaboo", "eleanor"); 271 | 272 | { 273 | const char* n = "/LDAPv3/127.0.0.1"; 274 | //const char* u = "test"; 275 | //const char* c = "nonce=\"1\", qop=\"auth\", realm=\"Test\", algorithm=\"md5\", opaque=\"1\""; 276 | //const char* r = "username=\"test\", nonce=\"1\", cnonce=\"1\", nc=\"1\", realm=\"Test\", algorithm=\"md5\", opaque=\"1\", qop=\"auth\", uri=\"/\", response=\"4241f31ffe6f9c99b891f88e9c41caa9\""; 277 | //const char* c = "WWW-Authenticate: digest nonce=\"1621696297327727918745238639\", opaque=\"121994e78694cbdff74f12cb32ee6f00-MTYyMTY5NjI5NzMyNzcyNzkxODc0NTIzODYzOSwxMjcuMC4wLjEsMTE2ODU2ODg5NQ==\", realm=\"Test Realm\", algorithm=\"md5\", qop=\"auth\""; 278 | //const char* r = "Authorization: Digest username=\"test\", realm=\"Test Realm\", nonce=\"1621696297327727918745238639\", uri=\"/principals/users/test/\", response=\"e260f13cffcc15572ddeec9c31de437b\", opaque=\"121994e78694cbdff74f12cb32ee6f00-MTYyMTY5NjI5NzMyNzcyNzkxODc0NTIzODYzOSwxMjcuMC4wLjEsMTE2ODU2ODg5NQ==\", algorithm=\"md5\", cnonce=\"70cbd8f04227d8d46c0193b290beaf0d\", nc=00000001, qop=\"auth\""; 279 | const char* u = ""; 280 | const char* c = "DIGEST-MD5"; 281 | const char* r = ""; 282 | AuthenticateUserDigest(authdir, n, u, c, r, "GET"); 283 | } 284 | 285 | GetDigestMD5ChallengeFromActiveDirectory(authdir, "/Active Directory/All Domains"); 286 | 287 | { 288 | const char* n = "/Active Directory/All Domains"; 289 | const char* u = ""; 290 | const char* r = ""; 291 | AuthenticateUserDigestToActiveDirectory(authdir, n, u, r); 292 | } 293 | 294 | 295 | AuthenticateUser(authdir, "/Active Directory/All Domains", "servicetest", "pass"); 296 | AuthenticateUserDigestODAD(authdir, "/Active Directory/All Domains", "servicetest", "pass"); 297 | 298 | //AuthenticateUser(authdir, "/LDAPv3/127.0.0.1", "eleanordaboo", "eleanor"); 299 | //AuthenticateUserDigestODAD(authdir, "/LDAPv3/127.0.0.1", "eleanordaboo", "eleanor"); 300 | 301 | return 0; 302 | } 303 | 304 | void AuthenticateUser(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* pswd) 305 | { 306 | bool result = false; 307 | if (dir->AuthenticateUserBasic(nodename, user, pswd, result, false)) 308 | { 309 | if (result) 310 | printf("AuthenticateUserBasic() success; nodename:\"%s\", user:\"%s\", pswd:\"%s\"\n", nodename, user, pswd ); 311 | else 312 | printf("AuthenticateUserBasic() success, but auth failed; nodename:\"%s\", user:\"%s\", pswd:\"%s\"\n", nodename, user, pswd ); 313 | } 314 | else 315 | printf("AuthenticateUserBasic() failed; nodename:\"%s\", user:\"%s\", pswd:\"%s\"\n", nodename, user, pswd ); 316 | } 317 | 318 | void AuthenticateUserDigest(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* challenge, const char* response, const char* method) 319 | { 320 | bool result = false; 321 | if (dir->AuthenticateUserDigest(nodename, user, challenge, response, method, result, false)) 322 | { 323 | if (result) 324 | printf("AuthenticateUserDigest() success; nodename:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 325 | else 326 | printf("AuthenticateUserDigest() success, but auth failed; user:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 327 | } 328 | else 329 | printf("AuthenticateUserDigest() failed, nodename:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 330 | } 331 | 332 | 333 | void AuthenticateUserDigestToActiveDirectory(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* response) 334 | { 335 | 336 | bool result = false; 337 | if (dir->AuthenticateUserDigestToActiveDirectory(nodename, user, response, result, false)) 338 | { 339 | if (result) 340 | printf("AuthenticateUserDigestToActiveDirectory() success; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 341 | else 342 | printf("AuthenticateUserDigestToActiveDirectory() success, but auth failed; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 343 | } 344 | else 345 | printf("AuthenticateUserDigestToActiveDirectory() failed; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 346 | } 347 | 348 | void GetDigestMD5ChallengeFromActiveDirectory(CDirectoryServiceAuth* dir, const char* nodename) 349 | { 350 | 351 | CFStringRef result = dir->GetDigestMD5ChallengeFromActiveDirectory(nodename, false); 352 | if (NULL != result) 353 | { 354 | CFStringUtil s(result); 355 | printf("GetDigestMD5ChallengeFromActiveDirectory() success; nodename:\"%s\", challenge:\"%s\"\n", nodename, s.temp_str()); 356 | 357 | CFRelease( result ); 358 | } 359 | else 360 | printf("GetDigestMD5ChallengeFromActiveDirectory() failed; nodename:\"%s\"\n", nodename); 361 | } 362 | 363 | 364 | void AuthenticateUserDigestODAD(CDirectoryServiceAuth* dir, const char* nodename, const char* user, const char* pswd, bool verbose) 365 | { 366 | CFStringRef challengeRef = NULL; 367 | CFStringRef responseRef = NULL; 368 | bool activeDirectory = (0 == strncmp(nodename, "/Active Directory/", strlen("/Active Directory/"))); 369 | 370 | // first get server challange 371 | if (activeDirectory) 372 | { 373 | // get server challenge from AD 374 | challengeRef = dir->GetDigestMD5ChallengeFromActiveDirectory(nodename, false); 375 | if (NULL != challengeRef) 376 | { 377 | if (verbose) { 378 | CFStringUtil s(challengeRef); 379 | printf("GetDigestMD5ChallengeFromActiveDirectory() success; nodename:\"%s\", challenge:\"%s\"\n", nodename, s.temp_str()); 380 | } 381 | } 382 | else 383 | printf("GetDigestMD5ChallengeFromActiveDirectory() failed; nodename:\"%s\"\n", nodename); 384 | } 385 | else 386 | { 387 | // get server challenge from AD 388 | challengeRef = GetDigestMD5ChallengeFromSASL(); 389 | if (NULL != challengeRef) 390 | { 391 | if (verbose) { 392 | CFStringUtil s(challengeRef); 393 | printf("GetDigestMD5ChallengeFromSASL() success; nodename:\"%s\", challenge:\"%s\"\n", nodename, s.temp_str()); 394 | } 395 | } 396 | else 397 | printf("GetDigestMD5ChallengeFromSASL() failed; nodename:\"%s\"\n", nodename); 398 | } 399 | 400 | if (NULL != challengeRef) 401 | { 402 | 403 | CFStringUtil challengeStrUtil(challengeRef); 404 | const char* challenge = challengeStrUtil.temp_str(); 405 | 406 | // now create response 407 | responseRef = GetClientResponseFromSASL( user, pswd, challenge ); 408 | CFStringUtil responseStrUtil(responseRef); 409 | const char* response = responseStrUtil.temp_str(); 410 | 411 | if (NULL != responseRef) 412 | { 413 | if (verbose) { 414 | printf("GetClientResponseFromSASL() success; user:\"%s\", pswd:\"%s\", challenge:\"%s\", response:\"%s\"\n", user, pswd, challenge, response); 415 | } 416 | } 417 | else 418 | { 419 | printf("GetClientResponseFromSASL() failed; user:\"%s\", pswd:\"%s\", challenge:\"%s\"\n", user, pswd, challenge); 420 | goto exit; 421 | } 422 | 423 | if (activeDirectory) 424 | { 425 | // now auth 426 | bool result = false; 427 | if (dir->AuthenticateUserDigestToActiveDirectory(nodename, user, response, result, false)) 428 | { 429 | if (result) 430 | printf("AuthenticateUserDigestToActiveDirectory() success; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 431 | else 432 | printf("AuthenticateUserDigestToActiveDirectory() success, but auth failed; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 433 | } 434 | else 435 | printf("AuthenticateUserDigestToActiveDirectory() failed; nodename:\"%s\", user:\"%s\", response:\"%s\"\n", nodename, user, response ); 436 | } 437 | else 438 | { 439 | bool result = false; 440 | const char* method = ""; 441 | if (dir->AuthenticateUserDigest(nodename, user, challenge, response, method, result, false)) 442 | { 443 | if (result) 444 | printf("AuthenticateUserDigest() success; nodename:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 445 | else 446 | printf("AuthenticateUserDigest() success, but auth failed; nodename:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 447 | } 448 | else 449 | printf("AuthenticateUserDigest() failed, nodename:\"%s\", user:\"%s\", challenge:\"%s\", response:\"%s\", method:\"%s\"\n", nodename, user, challenge, response, method ); 450 | } 451 | } 452 | 453 | exit: 454 | 455 | if (NULL != challengeRef) 456 | CFRelease( challengeRef ); 457 | if (NULL != responseRef) 458 | CFRelease( responseRef ); 459 | 460 | } 461 | 462 | void CFDictionaryIterator(const void* key, const void* value, void* ref) 463 | { 464 | CFStringRef strkey = (CFStringRef)key; 465 | CFStringRef strvalue = (CFStringRef)value; 466 | 467 | char* pystrkey = CStringFromCFString(strkey); 468 | char* pystrvalue = CStringFromCFString(strvalue); 469 | 470 | 471 | printf("%s: %s\n", pystrkey, pystrvalue); 472 | 473 | free(pystrkey); 474 | free(pystrvalue); 475 | } 476 | 477 | char* CStringFromCFString(CFStringRef str) 478 | { 479 | const char* bytes = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 480 | 481 | if (bytes == NULL) 482 | { 483 | char localBuffer[256]; 484 | localBuffer[0] = 0; 485 | Boolean success = ::CFStringGetCString(str, localBuffer, 256, kCFStringEncodingUTF8); 486 | if (!success) 487 | localBuffer[0] = 0; 488 | return ::strdup(localBuffer); 489 | } 490 | else 491 | { 492 | return ::strdup(bytes); 493 | } 494 | } 495 | 496 | void PrintDictionaryDictionary(const void* key, const void* value, void* ref) 497 | { 498 | CFStringUtil strkey((CFStringRef)key); 499 | CFDictionaryRef dictvalue = (CFDictionaryRef)value; 500 | 501 | printf("Dictionary Entry: \"%s\"\n", strkey.temp_str()); 502 | CFDictionaryApplyFunction(dictvalue, PrintDictionary, NULL); 503 | printf("\n"); 504 | } 505 | 506 | void PrintDictionary(const void* key, const void* value, void* ref) 507 | { 508 | CFStringUtil strkey((CFStringRef)key); 509 | if (CFGetTypeID((CFTypeRef)value) == CFStringGetTypeID()) 510 | { 511 | CFStringUtil strvalue((CFStringRef)value); 512 | 513 | printf("Key: \"%s\"; Value: \"%s\"\n", strkey.temp_str(), strvalue.temp_str()); 514 | } 515 | else if(CFGetTypeID((CFTypeRef)value) == CFArrayGetTypeID()) 516 | { 517 | CFArrayRef arrayvalue = (CFArrayRef)value; 518 | printf("Key: \"%s\"; Value: Array:\n", strkey.temp_str()); 519 | PrintArray(arrayvalue); 520 | printf("---\n"); 521 | } 522 | } 523 | 524 | CFComparisonResult CompareRecordListValues(const void *val1, const void *val2, void *context) 525 | { 526 | CFMutableArrayRef l1 = (CFMutableArrayRef)val1; 527 | CFMutableArrayRef l2 = (CFMutableArrayRef)val2; 528 | CFIndex c1 = CFArrayGetCount(l1); 529 | CFIndex c2 = CFArrayGetCount(l2); 530 | if ((c1 > 0) && (c2 > 0)) 531 | { 532 | return CFStringCompare((CFStringRef)CFArrayGetValueAtIndex(l1, 0), (CFStringRef)CFArrayGetValueAtIndex(l2, 0), NULL); 533 | } 534 | else if (c1 > 0) 535 | return kCFCompareGreaterThan; 536 | else if (c2 > 0) 537 | return kCFCompareLessThan; 538 | else 539 | return kCFCompareEqualTo; 540 | } 541 | 542 | void PrintArrayArray(CFMutableArrayRef list) 543 | { 544 | CFArraySortValues(list, CFRangeMake(0, CFArrayGetCount(list)), (CFComparatorFunction)CompareRecordListValues, NULL); 545 | for(CFIndex i = 0; i < CFArrayGetCount(list); i++) 546 | { 547 | CFMutableArrayRef array = (CFMutableArrayRef)CFArrayGetValueAtIndex(list, i); 548 | printf("Index: %ld\n", i); 549 | PrintArray(array); 550 | printf("\n"); 551 | } 552 | } 553 | 554 | void PrintArray(CFArrayRef list) 555 | { 556 | //CFArraySortValues(list, CFRangeMake(0, CFArrayGetCount(list)), (CFComparatorFunction)CFStringCompare, NULL); 557 | for(CFIndex i = 0; i < CFArrayGetCount(list); i++) 558 | { 559 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(list, i); 560 | const char* bytes = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 561 | 562 | if (bytes == NULL) 563 | { 564 | char localBuffer[256]; 565 | Boolean success; 566 | success = CFStringGetCString(str, localBuffer, 256, kCFStringEncodingUTF8); 567 | printf("%ld: %s\n", i, localBuffer); 568 | } 569 | else 570 | { 571 | printf("%ld: %s\n", i, (const char*)bytes); 572 | } 573 | } 574 | } 575 | 576 | 577 | #pragma mark ----- SASL calls 578 | 579 | #include 580 | 581 | #define kSASLMinSecurityFactor 0 582 | #define kSASLMaxSecurityFactor 65535 583 | #define kSASLMaxBufferSize 65536 584 | #define kSASLSecurityFlags 0 585 | #define kSASLPropertyNames (NULL) 586 | #define kSASLPropertyValues (NULL) 587 | 588 | typedef struct saslContext { 589 | const char *user; 590 | const char *pass; 591 | } saslContext; 592 | 593 | typedef int sasl_cbproc(); 594 | 595 | 596 | int getrealm(void *context /*__attribute__((unused))*/, 597 | int cb_id, 598 | const char **availrealms, 599 | const char **result) 600 | { 601 | #pragma unused (context) 602 | 603 | /* paranoia check */ 604 | if (cb_id != SASL_CB_GETREALM) return SASL_BADPARAM; 605 | if (!result) return SASL_BADPARAM; 606 | 607 | if ( availrealms ) { 608 | *result = *availrealms; 609 | } 610 | 611 | return SASL_OK; 612 | } 613 | 614 | int simple(void *context /*__attribute__((unused))*/, 615 | int cb_id, 616 | const char **result, 617 | unsigned *len) 618 | { 619 | saslContext *text = (saslContext *)context; 620 | 621 | //syslog(LOG_INFO, "in simple\n"); 622 | 623 | /* paranoia check */ 624 | if ( result == NULL ) 625 | return SASL_BADPARAM; 626 | 627 | *result = NULL; 628 | 629 | switch (cb_id) { 630 | case SASL_CB_USER: 631 | *result = text->user; 632 | break; 633 | 634 | case SASL_CB_AUTHNAME: 635 | *result = text->user; 636 | break; 637 | 638 | default: 639 | return SASL_BADPARAM; 640 | } 641 | 642 | if (*result != NULL && len != NULL) 643 | *len = strlen(*result); 644 | 645 | return SASL_OK; 646 | } 647 | 648 | 649 | int 650 | getsecret(sasl_conn_t *conn, 651 | void *context /*__attribute__((unused))*/, 652 | int cb_id, 653 | sasl_secret_t **psecret) 654 | { 655 | saslContext *text = (saslContext *)context; 656 | 657 | //syslog(LOG_INFO, "in getsecret\n"); 658 | 659 | /* paranoia check */ 660 | if (! conn || ! psecret || cb_id != SASL_CB_PASS) 661 | return SASL_BADPARAM; 662 | 663 | size_t pwdLen = strlen(text->pass); 664 | *psecret = (sasl_secret_t *) malloc( sizeof(sasl_secret_t) + pwdLen ); 665 | (*psecret)->len = pwdLen; 666 | strcpy((char *)(*psecret)->data, text->pass); 667 | 668 | return SASL_OK; 669 | } 670 | 671 | 672 | 673 | 674 | //---------------------------------------------------------------------------------------- 675 | // SASLClientNewContext 676 | // 677 | // Returns: A SASL context, or NULL 678 | // 679 | // must be an array with capacity for at least 5 items 680 | //---------------------------------------------------------------------------------------- 681 | 682 | sasl_conn_t *SASLClientNewContext( sasl_callback_t *callbacks, saslContext *context ) 683 | { 684 | int result = 0; 685 | sasl_conn_t *sasl_conn = NULL; 686 | sasl_security_properties_t secprops = { kSASLMinSecurityFactor, kSASLMaxSecurityFactor, 687 | kSASLMaxBufferSize, kSASLSecurityFlags, 688 | kSASLPropertyNames, kSASLPropertyValues }; 689 | 690 | result = sasl_client_init( NULL ); 691 | //printf( "sasl_client_init = %d\n", result ); 692 | if ( result != SASL_OK ) 693 | return NULL; 694 | 695 | // callbacks we support 696 | callbacks[0].id = SASL_CB_GETREALM; 697 | callbacks[0].proc = (sasl_cbproc *)&getrealm; 698 | callbacks[0].context = context; 699 | 700 | callbacks[1].id = SASL_CB_USER; 701 | callbacks[1].proc = (sasl_cbproc *)&simple; 702 | callbacks[1].context = context; 703 | 704 | callbacks[2].id = SASL_CB_AUTHNAME; 705 | callbacks[2].proc = (sasl_cbproc *)&simple; 706 | callbacks[2].context = context; 707 | 708 | callbacks[3].id = SASL_CB_PASS; 709 | callbacks[3].proc = (sasl_cbproc *)&getsecret; 710 | callbacks[3].context = context; 711 | 712 | callbacks[4].id = SASL_CB_LIST_END; 713 | callbacks[4].proc = NULL; 714 | callbacks[4].context = NULL; 715 | 716 | result = sasl_client_new( "http", "servicetest.example.com", NULL, NULL, callbacks, 0, &sasl_conn ); 717 | //printf( "sasl_client_new = %d\n", result ); 718 | if ( result != SASL_OK ) 719 | return NULL; 720 | 721 | result = sasl_setprop( sasl_conn, SASL_SEC_PROPS, &secprops ); 722 | //printf( "sasl_setprop = %d\n", result ); 723 | if ( result != SASL_OK ) { 724 | sasl_dispose( &sasl_conn ); 725 | return NULL; 726 | } 727 | 728 | return sasl_conn; 729 | } 730 | 731 | 732 | CFStringRef GetClientResponseFromSASL( const char* username, const char* pswd, const char* serverchallenge ) 733 | { 734 | int result = 0; 735 | sasl_callback_t callbacks[5] = {{0}}; 736 | saslContext sasl_context = { NULL, NULL }; 737 | sasl_conn_t *sasl_conn = NULL; 738 | const char *data = NULL; 739 | unsigned int len = 0; 740 | const char *chosenmech = NULL; 741 | CFStringRef response = NULL; 742 | 743 | sasl_context.user = username; 744 | sasl_context.pass = pswd; 745 | 746 | // Client's first move 747 | sasl_conn = SASLClientNewContext( callbacks, &sasl_context ); 748 | 749 | result = sasl_client_start( sasl_conn, "DIGEST-MD5", NULL, &data, &len, &chosenmech ); 750 | //printf( "sasl_client_start = %d, len = %d\n", result, len ); 751 | if ( result != SASL_CONTINUE ) 752 | goto done; 753 | 754 | // Client hashes the digest and responds 755 | result = sasl_client_step(sasl_conn, serverchallenge, strlen(serverchallenge), NULL, &data, &len); 756 | if (result != SASL_CONTINUE) { 757 | printf("sasl_client_step = %d\n", result); 758 | goto done; 759 | } 760 | 761 | response = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)data, len, kCFStringEncodingUTF8, false); 762 | 763 | 764 | done: 765 | if ( sasl_conn != NULL ) 766 | sasl_dispose( &sasl_conn ); 767 | 768 | return response; 769 | } 770 | 771 | //---------------------------------------------------------------------------------------- 772 | // GetDigestMD5ChallengeFromSASL 773 | // 774 | // Returns: A server challenge for DIGEST-MD5 authentication 775 | //---------------------------------------------------------------------------------------- 776 | 777 | CFStringRef GetDigestMD5ChallengeFromSASL( void ) 778 | { 779 | int result = 0; 780 | sasl_conn_t *sasl_server_conn = NULL; 781 | const char *serverOut = NULL; 782 | unsigned int serverOutLen = 0; 783 | CFStringRef challenge = NULL; 784 | 785 | // Passing a minimum security factor of 2 or more triggers the latest digest-md5 specification. 786 | // algorithm=md5-sess,qop=auth-conf. 787 | sasl_security_properties_t secprops = { 2, kSASLMaxSecurityFactor, 788 | kSASLMaxBufferSize, kSASLSecurityFlags, 789 | kSASLPropertyNames, kSASLPropertyValues }; 790 | 791 | // Get a challenge from SASL 792 | result = sasl_server_init_alt( NULL, "AppName" ); 793 | if ( result != SASL_OK ) { 794 | printf( "sasl_server_init_alt = %d\n", result ); 795 | goto done; 796 | } 797 | 798 | //"127.0.0.1;80" 799 | result = sasl_server_new( "http", "servicetest.example.com", NULL, NULL, NULL, NULL, 0, &sasl_server_conn ); 800 | if ( result != SASL_OK ) { 801 | printf( "sasl_server_init_alt = %d\n", result ); 802 | goto done; 803 | } 804 | 805 | result = sasl_setprop( sasl_server_conn, SASL_SEC_PROPS, &secprops ); 806 | 807 | result = sasl_server_start( sasl_server_conn, "DIGEST-MD5", NULL, 0, &serverOut, &serverOutLen ); 808 | if ( result != SASL_CONTINUE ) { 809 | printf( "sasl_server_start = %d\n", result ); 810 | goto done; 811 | } 812 | 813 | challenge = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)serverOut, serverOutLen, kCFStringEncodingUTF8, false); 814 | 815 | done: 816 | if ( sasl_server_conn != NULL ) 817 | sasl_dispose( &sasl_server_conn ); 818 | 819 | return challenge; 820 | } 821 | 822 | 823 | 824 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2006-2009 Apple Inc. All rights reserved. 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 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ## 16 | 17 | import opendirectory 18 | import dsattributes 19 | from dsquery import expression, match 20 | 21 | try: 22 | ref = opendirectory.odInit("/Search") 23 | if ref is None: 24 | print "Failed odInit" 25 | else: 26 | print "OK odInit" 27 | 28 | def listNodes(): 29 | l = opendirectory.listNodes(ref) 30 | if l is None: 31 | print "Failed to list nodes" 32 | else: 33 | print "\nlistNodes number of results = %d" % (len(l),) 34 | for n in l: 35 | print "Node: %s" % n 36 | 37 | def getNodeAttributes(): 38 | attrs = opendirectory.getNodeAttributes(ref, "/Search", (dsattributes.kDS1AttrSearchPath,)) 39 | if attrs is None: 40 | print "Failed to get node info" 41 | else: 42 | print "\ngetNodeAttributes number of results = %d" % (len(attrs),) 43 | for k,v in attrs.iteritems(): 44 | print "Node Attribute: %s: %s" % (k, v,) 45 | 46 | def listUsers(): 47 | d = opendirectory.listAllRecordsWithAttributes(ref, dsattributes.kDSStdRecordTypeUsers, 48 | ( 49 | dsattributes.kDS1AttrGeneratedUID, 50 | dsattributes.kDS1AttrDistinguishedName, 51 | ("dsAttrTypeStandard:JPEGPhoto", "base64"), 52 | )) 53 | if d is None: 54 | print "Failed to list users" 55 | else: 56 | names = [v for v in d.iterkeys()] 57 | names.sort() 58 | print "\nlistUsers number of results = %d" % (len(names),) 59 | for n in names: 60 | print "Name: %s" % n 61 | print "dict: %s" % str(d[n]) 62 | 63 | def listUsersCount(): 64 | d = opendirectory.listAllRecordsWithAttributes(ref, dsattributes.kDSStdRecordTypeUsers, 65 | ( 66 | dsattributes.kDS1AttrGeneratedUID, 67 | dsattributes.kDS1AttrDistinguishedName, 68 | ("dsAttrTypeStandard:JPEGPhoto", "base64"), 69 | ), 70 | 10) 71 | if d is None: 72 | print "Failed to list users" 73 | else: 74 | names = [v for v in d.iterkeys()] 75 | print "\nlistUsers 10 records, number of results = %d" % (len(names),) 76 | 77 | def listGroups(): 78 | d = opendirectory.listAllRecordsWithAttributes(ref, dsattributes.kDSStdRecordTypeGroups, 79 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDSNAttrGroupMembers,]) 80 | if d is None: 81 | print "Failed to list groups" 82 | else: 83 | names = [v for v in d.iterkeys()] 84 | names.sort() 85 | print "\nlistGroups number of results = %d" % (len(names),) 86 | for n in names: 87 | print "Name: %s" % n 88 | print "dict: %s" % str(d[n]) 89 | 90 | def listComputers(): 91 | d = opendirectory.listAllRecordsWithAttributes(ref, dsattributes.kDSStdRecordTypeComputers, 92 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrXMLPlist,]) 93 | if d is None: 94 | print "Failed to list computers" 95 | else: 96 | names = [v for v in d.iterkeys()] 97 | names.sort() 98 | print "\nlistComputers number of results = %d" % (len(names),) 99 | for n in names: 100 | print "Name: %s" % n 101 | print "dict: %s" % str(d[n]) 102 | 103 | def querySimple(title, attr, value, matchType, casei, recordType, attrs, count=0): 104 | d = opendirectory.queryRecordsWithAttribute( 105 | ref, 106 | attr, 107 | value, 108 | matchType, 109 | casei, 110 | recordType, 111 | attrs, 112 | count 113 | ) 114 | if d is None: 115 | print "Failed to query users" 116 | elif count: 117 | names = [v for v in d.iterkeys()] 118 | print "\n%s %d record limit, got number of results = %d" % (title, count, len(names),) 119 | else: 120 | names = [v for v in d.iterkeys()] 121 | names.sort() 122 | print "\n%s number of results = %d" % (title, len(names),) 123 | for n in names: 124 | print "Name: %s" % n 125 | print "dict: %s" % str(d[n]) 126 | 127 | def queryCompound(title, compound, casei, recordType, attrs): 128 | d = opendirectory.queryRecordsWithAttributes( 129 | ref, 130 | compound, 131 | casei, 132 | recordType, 133 | attrs 134 | ) 135 | if d is None: 136 | print "Failed to query users" 137 | else: 138 | names = [v for v in d.iterkeys()] 139 | names.sort() 140 | print "\n%s number of results = %d" % (title, len(names),) 141 | for n in names: 142 | print "Name: %s" % n 143 | print "dict: %s" % str(d[n]) 144 | 145 | def queryUsers(): 146 | querySimple( 147 | "queryUsers", 148 | dsattributes.kDS1AttrFirstName, 149 | "cyrus", 150 | dsattributes.eDSExact, 151 | True, 152 | dsattributes.kDSStdRecordTypeUsers, 153 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 154 | ) 155 | 156 | def queryUsersCountNotLimited(): 157 | querySimple( 158 | "queryUsers", 159 | dsattributes.kDS1AttrFirstName, 160 | "cyrus", 161 | dsattributes.eDSExact, 162 | True, 163 | dsattributes.kDSStdRecordTypeUsers, 164 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,], 165 | 2 166 | ) 167 | 168 | def queryUsersCountLimited(): 169 | querySimple( 170 | "queryUsers", 171 | dsattributes.kDS1AttrFirstName, 172 | "john", 173 | dsattributes.eDSExact, 174 | True, 175 | dsattributes.kDSStdRecordTypeUsers, 176 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,], 177 | 10 178 | ) 179 | 180 | def queryUsersCompoundOr(): 181 | queryCompound( 182 | "queryUsersCompoundOr", 183 | expression(expression.OR, 184 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSContains), 185 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSContains))).generate(), 186 | False, 187 | dsattributes.kDSStdRecordTypeUsers, 188 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 189 | ) 190 | 191 | def queryUsersCompoundOrExact(): 192 | queryCompound( 193 | "queryUsersCompoundOrExact", 194 | expression(expression.OR, 195 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSExact), 196 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSExact))).generate(), 197 | False, 198 | dsattributes.kDSStdRecordTypeUsers, 199 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 200 | ) 201 | 202 | def queryUsersCompoundAnd(): 203 | queryCompound( 204 | "queryUsersCompoundAnd", 205 | expression(expression.AND, 206 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSContains), 207 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSContains))).generate(), 208 | True, 209 | dsattributes.kDSStdRecordTypeUsers, 210 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 211 | ) 212 | 213 | def listUsers_list(): 214 | d = opendirectory.listAllRecordsWithAttributes_list(ref, dsattributes.kDSStdRecordTypeUsers, 215 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,]) 216 | if d is None: 217 | print "Failed to list users" 218 | else: 219 | d.sort(cmp=lambda x, y: x[0] < y[0]) 220 | print "\nlistUsers_list number of results = %d" % (len(d),) 221 | for name, record in d: 222 | print "Name: %s" % name 223 | print "dict: %s" % str(record) 224 | 225 | def listGroups_list(): 226 | d = opendirectory.listAllRecordsWithAttributes_list(ref, dsattributes.kDSStdRecordTypeGroups, 227 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDSNAttrGroupMembers,]) 228 | if d is None: 229 | print "Failed to list groups" 230 | else: 231 | d.sort(cmp=lambda x, y: x[0] < y[0]) 232 | print "\nlistGroups_list number of results = %d" % (len(d),) 233 | for name, record in d: 234 | print "Name: %s" % name 235 | print "dict: %s" % str(record) 236 | 237 | def listComputers_list(): 238 | d = opendirectory.listAllRecordsWithAttributes_list(ref, dsattributes.kDSStdRecordTypeComputers, 239 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrXMLPlist,]) 240 | if d is None: 241 | print "Failed to list computers" 242 | else: 243 | d.sort(cmp=lambda x, y: x[0] < y[0]) 244 | print "\nlistComputers_list number of results = %d" % (len(d),) 245 | for name, record in d: 246 | print "Name: %s" % name 247 | print "dict: %s" % str(record) 248 | 249 | def querySimple_list(title, attr, value, matchType, casei, recordType, attrs): 250 | d = opendirectory.queryRecordsWithAttribute_list( 251 | ref, 252 | attr, 253 | value, 254 | matchType, 255 | casei, 256 | recordType, 257 | attrs 258 | ) 259 | if d is None: 260 | print "Failed to query users" 261 | else: 262 | d.sort(cmp=lambda x, y: x[0] < y[0]) 263 | print "\n%s number of results = %d" % (title, len(d),) 264 | for name, record in d: 265 | print "Name: %s" % name 266 | print "dict: %s" % str(record) 267 | 268 | def queryCompound_list(title, compound, casei, recordType, attrs): 269 | d = opendirectory.queryRecordsWithAttributes_list( 270 | ref, 271 | compound, 272 | casei, 273 | recordType, 274 | attrs 275 | ) 276 | if d is None: 277 | print "Failed to query users" 278 | else: 279 | d.sort(cmp=lambda x, y: x[0] < y[0]) 280 | print "\n%s number of results = %d" % (title, len(d),) 281 | for name, record in d: 282 | print "Name: %s" % name 283 | print "dict: %s" % str(record) 284 | 285 | def queryUsers_list(): 286 | querySimple_list( 287 | "queryUsers_list", 288 | dsattributes.kDS1AttrFirstName, 289 | "cyrus", 290 | dsattributes.eDSExact, 291 | True, 292 | dsattributes.kDSStdRecordTypeUsers, 293 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 294 | ) 295 | 296 | def queryUsersCompoundOr_list(): 297 | queryCompound_list( 298 | "queryUsersCompoundOr_list", 299 | expression(expression.OR, 300 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSContains), 301 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSContains))).generate(), 302 | False, 303 | dsattributes.kDSStdRecordTypeUsers, 304 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 305 | ) 306 | 307 | def queryUsersCompoundOrExact_list(): 308 | queryCompound_list( 309 | "queryUsersCompoundOrExact_list", 310 | expression(expression.OR, 311 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSExact), 312 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSExact))).generate(), 313 | False, 314 | dsattributes.kDSStdRecordTypeUsers, 315 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 316 | ) 317 | 318 | def queryUsersCompoundAnd_list(): 319 | queryCompound_list( 320 | "queryUsersCompoundAnd_list", 321 | expression(expression.AND, 322 | (match(dsattributes.kDS1AttrFirstName, "chris", dsattributes.eDSContains), 323 | match(dsattributes.kDS1AttrLastName, "roy", dsattributes.eDSContains))).generate(), 324 | True, 325 | dsattributes.kDSStdRecordTypeUsers, 326 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 327 | ) 328 | 329 | def listResourcesPlaces_list(): 330 | d = opendirectory.listAllRecordsWithAttributes_list( 331 | ref, 332 | (dsattributes.kDSStdRecordTypeResources, dsattributes.kDSStdRecordTypePlaces,), 333 | [dsattributes.kDSNAttrRecordType, dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 334 | ) 335 | if d is None: 336 | print "Failed to list resources/places" 337 | else: 338 | d.sort(cmp=lambda x, y: x[0] < y[0]) 339 | print "\nlistResourcesPlaces_list number of results = %d" % (len(d),) 340 | for name, record in d: 341 | print "Name: %s" % name 342 | print "dict: %s" % str(record) 343 | 344 | def queryUsersGroups_list(): 345 | querySimple_list( 346 | "queryUsersGroups_list", 347 | dsattributes.kDS1AttrDistinguishedName, 348 | "burns", 349 | dsattributes.eDSContains, 350 | True, 351 | (dsattributes.kDSStdRecordTypeUsers, dsattributes.kDSStdRecordTypeGroups,), 352 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 353 | ) 354 | 355 | def queryUsersGroupsPlaces_list(): 356 | querySimple_list( 357 | "queryUsersGroupsPlaces_list", 358 | dsattributes.kDS1AttrDistinguishedName, 359 | "tom", 360 | dsattributes.eDSContains, 361 | True, 362 | (dsattributes.kDSStdRecordTypeUsers, dsattributes.kDSStdRecordTypeGroups, dsattributes.kDSStdRecordTypePlaces,), 363 | [dsattributes.kDS1AttrGeneratedUID, dsattributes.kDS1AttrDistinguishedName,] 364 | ) 365 | 366 | def authentciateBasic(): 367 | if opendirectory.authenticateUserBasic(ref, "gooeyed", "test", "test"): 368 | print "Authenticated user" 369 | else: 370 | print "Failed to authenticate user" 371 | 372 | listNodes() 373 | getNodeAttributes() 374 | 375 | listUsers() 376 | listGroups() 377 | listComputers() 378 | queryUsers() 379 | queryUsersCompoundOr() 380 | queryUsersCompoundOrExact() 381 | queryUsersCompoundAnd() 382 | listUsers_list() 383 | listGroups_list() 384 | listComputers_list() 385 | queryUsers_list() 386 | queryUsersCompoundOr_list() 387 | queryUsersCompoundOrExact_list() 388 | queryUsersCompoundAnd_list() 389 | 390 | listResourcesPlaces_list() 391 | queryUsersGroups_list() 392 | queryUsersGroupsPlaces_list() 393 | 394 | listUsersCount() 395 | queryUsersCountNotLimited() 396 | queryUsersCountLimited() 397 | 398 | #authentciateBasic() 399 | 400 | ref = None 401 | except opendirectory.ODError, ex: 402 | print ex 403 | except Exception, e: 404 | print e 405 | 406 | print "Done." 407 | -------------------------------------------------------------------------------- /test_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # Copyright (c) 2006-2008 Apple Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | ## 17 | 18 | from getpass import getpass 19 | import opendirectory 20 | import dsattributes 21 | import md5 22 | import sha 23 | import shlex 24 | 25 | algorithms = { 26 | 'md5': md5.new, 27 | 'md5-sess': md5.new, 28 | 'sha': sha.new, 29 | } 30 | 31 | # DigestCalcHA1 32 | def calcHA1( 33 | pszAlg, 34 | pszUserName, 35 | pszRealm, 36 | pszPassword, 37 | pszNonce, 38 | pszCNonce, 39 | preHA1=None 40 | ): 41 | """ 42 | @param pszAlg: The name of the algorithm to use to calculate the digest. 43 | Currently supported are md5 md5-sess and sha. 44 | 45 | @param pszUserName: The username 46 | @param pszRealm: The realm 47 | @param pszPassword: The password 48 | @param pszNonce: The nonce 49 | @param pszCNonce: The cnonce 50 | 51 | @param preHA1: If available this is a str containing a previously 52 | calculated HA1 as a hex string. If this is given then the values for 53 | pszUserName, pszRealm, and pszPassword are ignored. 54 | """ 55 | 56 | if (preHA1 and (pszUserName or pszRealm or pszPassword)): 57 | raise TypeError(("preHA1 is incompatible with the pszUserName, " 58 | "pszRealm, and pszPassword arguments")) 59 | 60 | if preHA1 is None: 61 | # We need to calculate the HA1 from the username:realm:password 62 | m = algorithms[pszAlg]() 63 | m.update(pszUserName) 64 | m.update(":") 65 | m.update(pszRealm) 66 | m.update(":") 67 | m.update(pszPassword) 68 | HA1 = m.digest() 69 | else: 70 | # We were given a username:realm:password 71 | HA1 = preHA1.decode('hex') 72 | 73 | if pszAlg == "md5-sess": 74 | m = algorithms[pszAlg]() 75 | m.update(HA1) 76 | m.update(":") 77 | m.update(pszNonce) 78 | m.update(":") 79 | m.update(pszCNonce) 80 | HA1 = m.digest() 81 | 82 | return HA1.encode('hex') 83 | 84 | # DigestCalcResponse 85 | def calcResponse( 86 | HA1, 87 | algo, 88 | pszNonce, 89 | pszNonceCount, 90 | pszCNonce, 91 | pszQop, 92 | pszMethod, 93 | pszDigestUri, 94 | pszHEntity, 95 | ): 96 | m = algorithms[algo]() 97 | m.update(pszMethod) 98 | m.update(":") 99 | m.update(pszDigestUri) 100 | if pszQop == "auth-int" or pszQop == "auth-conf": 101 | m.update(":") 102 | m.update(pszHEntity) 103 | HA2 = m.digest().encode('hex') 104 | 105 | m = algorithms[algo]() 106 | m.update(HA1) 107 | m.update(":") 108 | m.update(pszNonce) 109 | m.update(":") 110 | if pszNonceCount and pszCNonce and pszQop: 111 | m.update(pszNonceCount) 112 | m.update(":") 113 | m.update(pszCNonce) 114 | m.update(":") 115 | m.update(pszQop) 116 | m.update(":") 117 | m.update(HA2) 118 | respHash = m.digest().encode('hex') 119 | return respHash 120 | 121 | 122 | attempts = 100 123 | 124 | 125 | def doAuthDigest(username, password, qop, algorithm, cipher): 126 | failures = 0 127 | 128 | realm = "host.example.com" 129 | nonce = "128446648710842461101646794502" 130 | nc = "00000001" 131 | cnonce = "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE=" 132 | uri = "http://host.example.com" 133 | method = "GET" 134 | entity = "00000000000000000000000000000000" 135 | maxbuf = "65536" 136 | 137 | result = opendirectory.queryRecordsWithAttribute_list( 138 | od, 139 | dsattributes.kDSNAttrRecordName, 140 | username, 141 | dsattributes.eDSExact, 142 | False, 143 | dsattributes.kDSStdRecordTypeUsers, 144 | [dsattributes.kDSNAttrMetaNodeLocation]) 145 | if not result: 146 | print "Failed to get record for user: %s" % (username,) 147 | return 148 | nodename = result[0][1][dsattributes.kDSNAttrMetaNodeLocation] 149 | 150 | print( ' User node= "%s"' % nodename) 151 | adUser = nodename.startswith("/Active Directory/") 152 | 153 | for _ignore_x in xrange(attempts): 154 | if adUser: 155 | 156 | challenge = opendirectory.getDigestMD5ChallengeFromActiveDirectory(od, nodename) 157 | if not challenge: 158 | print "Failed to get Active Directory challenge for user: %s" % (username,) 159 | return 160 | # parse challenge 161 | 162 | l = shlex.shlex(challenge) 163 | l.wordchars = l.wordchars + "_-" 164 | l.whitespace = l.whitespace + "=," 165 | auth = {} 166 | while 1: 167 | k = l.get_token() 168 | if not k: 169 | break 170 | v = l.get_token() 171 | if not v: 172 | break 173 | v = v.strip('"') # this strip is kind of a hack, should remove matched leading and trailing double quotes 174 | 175 | auth[k.strip()] = v.strip() 176 | 177 | # get expected response parameters from challenge 178 | nonce = auth["nonce"] 179 | #nonce = "+Upgraded+v17fa28b0e0cb4c483144a0d568259ca0102de13e7b48ff9261cfa9748b93f83cc09d8ee50638c6d9794e1b4f8485a7dee" 180 | 181 | if auth.get("digest-uri", False): 182 | uri = auth["digest-uri"] 183 | 184 | qopstr = auth.get("qop", False) 185 | if qop not in qopstr.split(","): 186 | print "WARINING: input qop=%s not in AD challenge qop=\"%s\"" % (qop, qopstr,) 187 | 188 | if auth.get("realm", False): 189 | realm = auth["realm"] 190 | 191 | algostr = auth.get("algorithm", "") 192 | if algorithm.lower() != algostr.lower(): 193 | print "WARINING: input algorithm=%s not in AD challenge algorithm=%s" % (algorithm, algostr,) 194 | 195 | cipherstr = auth.get("cipher", "") 196 | if cipher.lower() not in cipherstr.lower().split(","): 197 | print "WARINING: input cipher=%s not in AD challenge cipher=\"%s\"" % (cipher, cipherstr,) 198 | 199 | 200 | if auth.get("maxbuf", False): 201 | maxbuf = auth["maxbuf"] 202 | 203 | method = "AUTHENTICATE" 204 | 205 | else: 206 | 207 | if qop: 208 | challenge = 'realm="%s", nonce="%s", algorithm=%s, qop="%s"' % (realm, nonce, algorithm, qop,) 209 | else: 210 | challenge = 'realm="%s", nonce="%s", algorithm=%s' % (realm, nonce, algorithm,) 211 | 212 | 213 | expected = calcResponse( 214 | calcHA1(algorithm.lower(), username, realm, password, nonce, cnonce), 215 | algorithm.lower(), nonce, nc, cnonce, qop, method, uri, entity 216 | ) 217 | 218 | if qop: 219 | response = ('username="%s",realm="%s",algorithm=%s,' 220 | 'nonce="%s",cnonce="%s",nc=%s,qop=%s,' 221 | 'cipher=%s,maxbuf=%s,digest-uri="%s",response=%s' % (username, realm, algorithm, 222 | nonce, cnonce, nc, qop, 223 | cipher, maxbuf, uri, expected )) 224 | else: 225 | response = ('Digest username="%s", uri="%s", response=%s' % (username, uri, expected, )) 226 | 227 | print " Challenge: %s" % (challenge,) 228 | print " Response: %s" % (response, ) 229 | 230 | 231 | if adUser: 232 | success = opendirectory.authenticateUserDigestToActiveDirectory( 233 | od, 234 | nodename, 235 | username, 236 | response, 237 | ) 238 | else: 239 | success = opendirectory.authenticateUserDigest( 240 | od, 241 | nodename, 242 | username, 243 | challenge, 244 | response, 245 | method 246 | ) 247 | 248 | if not success: 249 | failures += 1 250 | 251 | print "\n%d failures out of %d attempts for Digest.\n\n" % (failures, attempts) 252 | 253 | def doAuthBasic(username, password): 254 | failures = 0 255 | 256 | result = opendirectory.queryRecordsWithAttribute_list( 257 | od, 258 | dsattributes.kDSNAttrRecordName, 259 | username, 260 | dsattributes.eDSExact, 261 | False, 262 | dsattributes.kDSStdRecordTypeUsers, 263 | [dsattributes.kDSNAttrMetaNodeLocation]) 264 | if not result: 265 | print "Failed to get record for user: %s" % (username,) 266 | return 267 | nodename = result[0][1][dsattributes.kDSNAttrMetaNodeLocation] 268 | 269 | for _ignore_x in xrange(attempts): 270 | success = opendirectory.authenticateUserBasic( 271 | od, 272 | nodename, 273 | username, 274 | password, 275 | ) 276 | 277 | if not success: 278 | failures += 1 279 | 280 | print "\n%d failures out of %d attempts for Basic.\n\n" % (failures, attempts) 281 | """ 282 | search = raw_input("DS search path: ") 283 | user = raw_input("User: ") 284 | pswd = getpass("Password: ") 285 | attempts = int(raw_input("Number of attempts: ")) 286 | """ 287 | 288 | # to test, bind your client to Active Directory that contains the user specified below 289 | 290 | search = "/Search" 291 | user = "servicetest" 292 | pswd = "pass" 293 | attempts = 10 294 | 295 | od = opendirectory.odInit(search) 296 | 297 | doAuthBasic(user, pswd) 298 | doAuthDigest(user, pswd, "auth-conf", "md5-sess", "rc4") 299 | doAuthDigest(user, pswd, "auth-conf", "MD5-sess", "RC4") 300 | 301 | # to test, bind your client to an Open Directory master that contains the user specified below 302 | 303 | user = "testuser" 304 | pswd = "test" 305 | doAuthBasic(user, pswd) 306 | doAuthDigest(user, pswd, None, "md5", None) 307 | #doAuthDigest(user, pswd, None, "md5-sess", "rc4") # fails 308 | 309 | doAuthDigest(user, pswd, "auth", "md5", "rc4") 310 | doAuthDigest(user, pswd, "auth", "md5-sess", "rc4") 311 | 312 | doAuthDigest(user, pswd, "auth-int", "md5", "rc4") 313 | doAuthDigest(user, pswd, "auth-int", "md5-sess", "rc4") 314 | 315 | #doAuthDigest(user, pswd, "auth-conf", "md5", "rc4") # fails 316 | doAuthDigest(user, pswd, "auth-conf", "md5-sess", "rc4") 317 | 318 | --------------------------------------------------------------------------------