| 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 |
| 22 |
import os |
| 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 |
""" |
| 33 |
Acts as an auth service for ejabberd through ejabberds external auth |
| 34 |
option. See contrib/ejabberd/ejabber.cfg for an example configuration. |
| 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) |
| 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' + |
| 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 |
""" |
| 77 |
logging.debug('Generating a response ...') |
| 78 |
result = 0 |
| 79 |
if success: |
| 80 |
result = 1 |
| 81 |
logging.debug('Sending response of ' + str(result)) |
| 82 |
sys.stdout.write(struct.pack('>hh', 2, result)) |
| 83 |
sys.stdout.flush() |
| 84 |
logging.debug('Response of ' + str(result) + ' sent') |
| 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) |
| 95 |
logging.debug('Found user with username ' + str(username)) |
| 96 |
self._generate_response(True) |
| 97 |
except User.DoesNotExist: |
| 98 |
logging.debug('No username ' + str(username)) |
| 99 |
self._generate_response(False) |
| 100 |
except Exception, ex: |
| 101 |
logging.fatal('Unhandled error: ' + str(ex)) |
| 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 |
""" |
| 111 |
logging.debug('Starting auth check') |
| 112 |
try: |
| 113 |
user = User.objects.get(username=username) |
| 114 |
logging.debug('Found username ' + str(username)) |
| 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') |
| 134 |
self._generate_response(False) |
| 135 |
except Exception, ex: |
| 136 |
logging.fatal('Unhandled error: ' + str(ex)) |
| 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: |
| 148 |
logging.debug('Loop restarting') |
| 149 |
# Verify the information checks out |
| 150 |
try: |
| 151 |
logging.debug('Waiting for data') |
| 152 |
length = sys.stdin.read(2) |
| 153 |
size = struct.unpack('>h', length)[0] |
| 154 |
logging.debug('Data is of size ' + str(size)) |
| 155 |
logging.debug('Attempting to read the data') |
| 156 |
input = sys.stdin.read(size).split(':') |
| 157 |
logging.debug('Input: ' + str(input)) |
| 158 |
operation = input.pop(0) |
| 159 |
except Exception, ex: |
| 160 |
# It wasn't even in the right format if we get here ... |
| 161 |
logging.debug( |
| 162 |
"Data was not in the right format: " + str(ex)) |
| 163 |
self._generate_response(False) |
| 164 |
continue |
| 165 |
logging.debug('Checking operation ...') |
| 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) |
| 177 |
else: |
| 178 |
logging.warn('Operation "' + operation + '" unknown!') |
| 179 |
except KeyboardInterrupt: |
| 180 |
logging.debug("Received Keyboard Interrupt") |
| 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') |