| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
1 |
# Tunnel - TODO |
|
2 |
# |
|
3 |
# (C) 2010 Luke Slater, Steve 'Ashcrow' Milner |
|
4 |
# |
|
5 |
# This program is free software: you can redistribute it and/or modify |
|
6 |
# it under the terms of the GNU Affero General Public License as published by |
|
7 |
# the Free Software Foundation, either version 3 of the License, or |
|
8 |
# (at your option) any later version. |
|
9 |
# |
|
10 |
# This program is distributed in the hope that it will be useful, |
|
11 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
# GNU Affero General Public License for more details. |
|
14 |
# |
|
15 |
# You should have received a copy of the GNU Affero General Public License |
|
16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 |
""" |
|
18 |
Authenticate XMPP user. |
|
19 |
""" |
|
20 |
|
|
21 |
import logging |
| d6e1fc4 by Steve 'Ashcrow' Milner at 2010-05-02 |
22 |
import os |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
23 |
import struct |
|
24 |
import sys |
|
25 |
|
|
26 |
from django.core.management.base import BaseCommand |
|
27 |
from django.contrib.auth.models import User, check_password |
|
28 |
from django.conf import settings |
|
29 |
|
|
30 |
|
|
31 |
class Command(BaseCommand): |
|
32 |
""" |
| bdf8717 by Steve 'Ashcrow' Milner at 2010-04-11 |
33 |
Acts as an auth service for ejabberd through ejabberds external auth |
|
34 |
option. See contrib/ejabberd/ejabber.cfg for an example configuration. |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
35 |
""" |
|
36 |
|
|
37 |
help = "Runs an ejabberd auth service" |
|
38 |
|
|
39 |
def __init__(self, *args, **kwargs): |
|
40 |
""" |
|
41 |
Creation of the ejabberd atuh bridge service. |
|
42 |
|
|
43 |
:Parameters: |
|
44 |
- `args`: all non-keyword arguments |
|
45 |
- `kwargs`: all keyword arguments |
|
46 |
""" |
|
47 |
BaseCommand.__init__(self, *args, **kwargs) |
| d6e1fc4 by Steve 'Ashcrow' Milner at 2010-05-02 |
48 |
try: |
|
49 |
log_level = int(settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG_LEVEL) |
|
50 |
except: |
|
51 |
log_level = logging.INFO |
|
52 |
# If we can write to the log do so, else fail back to the console |
|
53 |
if os.access(settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG, os.W_OK): |
|
54 |
logging.basicConfig( |
|
55 |
level=log_level, |
|
56 |
format='%(asctime)s %(levelname)s %(message)s', |
|
57 |
filename=settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG, |
|
58 |
filemode='a') |
|
59 |
else: |
|
60 |
logging.basicConfig( |
|
61 |
level=log_level, |
|
62 |
format='%(asctime)s %(levelname)s %(message)s', |
|
63 |
stream=sys.stderr) |
|
64 |
logging.warn(('Could not write to ' + |
|
65 |
settings.TUNNEL_EJABBERD_AUTH_GATEWAY_LOG + |
|
66 |
'. Falling back to stderr ...')) |
|
67 |
logging.info(('ejabberd_auth_bridge process started' + |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
68 |
' (more than one is common)')) |
|
69 |
|
|
70 |
def _generate_response(self, success=False): |
|
71 |
""" |
|
72 |
Creates and sends a response back to the ejabberd server. |
|
73 |
|
|
74 |
:Parameters |
|
75 |
- `success`: boolean if we should respond successful or not |
|
76 |
""" |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
77 |
logging.debug('Generating a response ...') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
78 |
result = 0 |
|
79 |
if success: |
|
80 |
result = 1 |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
81 |
logging.debug('Sending response of ' + str(result)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
82 |
sys.stdout.write(struct.pack('>hh', 2, result)) |
|
83 |
sys.stdout.flush() |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
84 |
logging.debug('Response of ' + str(result) + ' sent') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
85 |
|
|
86 |
def _handle_isuser(self, username): |
|
87 |
""" |
|
88 |
Handles the isuer ejabberd command. |
|
89 |
|
|
90 |
:Parameters: |
|
91 |
- `username`: the user name to verify exists |
|
92 |
""" |
|
93 |
try: |
|
94 |
user = User.objects.get(username=username) |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
95 |
logging.debug('Found user with username ' + str(username)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
96 |
self._generate_response(True) |
|
97 |
except User.DoesNotExist: |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
98 |
logging.debug('No username ' + str(username)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
99 |
self._generate_response(False) |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
100 |
except Exception, ex: |
|
101 |
logging.fatal('Unhandled error: ' + str(ex)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
102 |
|
|
103 |
def _handle_auth(self, username, password): |
|
104 |
""" |
|
105 |
Handles authentication of the user. |
|
106 |
|
|
107 |
:Parameters: |
|
108 |
- `username`: the username to verify |
|
109 |
- `password`: the password to verify with the user |
|
110 |
""" |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
111 |
logging.debug('Starting auth check') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
112 |
try: |
|
113 |
user = User.objects.get(username=username) |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
114 |
logging.debug('Found username ' + str(username)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
115 |
if check_password(password, user.password): |
|
116 |
self._generate_response(True) |
|
117 |
logging.info(username + ' has logged in') |
|
118 |
profile = user.get_profile() |
|
119 |
# Tunnel specific ..... |
|
120 |
if not profile.logged_in: |
|
121 |
try: |
|
122 |
profile.logged_in = True |
|
123 |
profile.save() |
|
124 |
except Exception, ex: |
|
125 |
# Couldn't update the profile ... |
|
126 |
logging.warn("Could not save profile:" + str(ex)) |
|
127 |
logging.debug('Updated ' + username + ' profile status') |
|
128 |
# End Tunnel specific |
|
129 |
else: |
|
130 |
self._generate_response(False) |
|
131 |
logging.info(username + ' failed auth') |
|
132 |
except User.DoesNotExist: |
|
133 |
logging.info(username + ' is not a valid user') |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
134 |
self._generate_response(False) |
|
135 |
except Exception, ex: |
|
136 |
logging.fatal('Unhandled error: ' + str(ex)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
137 |
|
|
138 |
def handle(self, **options): |
|
139 |
""" |
|
140 |
How to check if a user is valid |
|
141 |
|
|
142 |
:Parameters: |
|
143 |
- `options`: keyword arguments |
|
144 |
""" |
|
145 |
try: |
|
146 |
# Serve forever |
|
147 |
while True: |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
148 |
logging.debug('Loop restarting') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
149 |
# Verify the information checks out |
|
150 |
try: |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
151 |
logging.debug('Waiting for data') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
152 |
length = sys.stdin.read(2) |
|
153 |
size = struct.unpack('>h', length)[0] |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
154 |
logging.debug('Data is of size ' + str(size)) |
|
155 |
logging.debug('Attempting to read the data') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
156 |
input = sys.stdin.read(size).split(':') |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
157 |
logging.debug('Input: ' + str(input)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
158 |
operation = input.pop(0) |
|
159 |
except Exception, ex: |
|
160 |
# It wasn't even in the right format if we get here ... |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
161 |
logging.debug( |
|
162 |
"Data was not in the right format: " + str(ex)) |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
163 |
self._generate_response(False) |
|
164 |
continue |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
165 |
logging.debug('Checking operation ...') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
166 |
if operation == 'auth': |
|
167 |
logging.info( |
|
168 |
'Auth request being processed for ' + input[1]) |
|
169 |
self._handle_auth(input[0], input[2]) |
|
170 |
elif operation == 'isuser': |
|
171 |
logger.info('Asked if ' + input[0] + ' is a user') |
|
172 |
self._handle_isuser() |
|
173 |
elif operation == 'setpass': |
|
174 |
logger.info('Asked if to change password for ' + input[0]) |
|
175 |
# Do not support this (Tunnel specific) |
|
176 |
self._generate_repsonse(False) |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
177 |
else: |
|
178 |
logging.warn('Operation "' + operation + '" unknown!') |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
179 |
except KeyboardInterrupt: |
| a254d79 by Steve 'Ashcrow' Milner at 2010-05-01 |
180 |
logging.debug("Received Keyboard Interrupt") |
| a22611f by Steve 'Ashcrow' Milner at 2010-04-11 |
181 |
raise SystemExit(0) |
|
182 |
|
|
183 |
def __del__(self): |
|
184 |
""" |
|
185 |
What to do when we are shut off. |
|
186 |
""" |
|
187 |
logging.info('ejabberd_auth_bridge process stopped') |