Repository: humiaozuzu/YaH3C Branch: master Commit: c0d873c6ca99 Files: 18 Total size: 36.3 KB Directory structure: gitextract_27jb11da/ ├── .gitignore ├── AUTHORS ├── CHANGES ├── LICENSE ├── README.md ├── scripts/ │ └── yah3c ├── setup.py └── yah3c/ ├── __init__.py ├── colorama/ │ ├── __init__.py │ ├── ansi.py │ ├── ansitowin32.py │ ├── initialise.py │ ├── win32.py │ └── winterm.py ├── eapauth.py ├── eappacket.py ├── usermgr.py └── yah3c.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ### /Users/maple/.gitignore-boilerplates/Python.gitignore *.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml #Translations *.mo #Mr Developer .mr.developer.cfg .project .pydevproject ### /Users/maple/.gitignore-boilerplates/Global/OSX.gitignore .DS_Store Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes ### /Users/maple/.gitignore-boilerplates/Global/Linux.gitignore .* !.gitignore *~ ================================================ FILE: AUTHORS ================================================ Author ------ Maple Contributors ------------ - qiao - houqp - tigersoldier ================================================ FILE: CHANGES ================================================ YaH3C Changelog ============== Here is the full list of changes between each YaH3C release. Version 0.5 ----------- Released on September 24th 2012 * add command line argument support * add dhcp support * refactory usermgr module Version 0.4 ----------- Released on September 20th 2012 * remove plugin module Version 0.3 ----------- Released on April 1th 2012 * add eap-md5 support Version 0.2 ----------- Released on December 29th 2011 * complete refactory * plugins support Version 0.1 ----------- Released on November 14th 2011 * Initial commit ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2010-2012 YaH3C Authors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ YaH3C ===== YaH3C 是用于校园网认证的客户端,支持中山大学东校区。 感谢 [lpy](https://github.com/lpy) 为 OS X 提供的[版本](https://github.com/lpy/Yah3c)。 为什么不用iNode --------------- * i开头完全是对Apple的侮辱嘛 = = * 强制添加启动项 * gui和cli使用的udp交互,很多时候Linux/Mac下掉线完全是因为实现的效率太低 * 安装脚本居然是自删除的 * 默认强制记录日志文件,增加CPU负载,减少硬盘寿命,一段时间后就过GB了(是否有敏感信息不得而知了) * Linux下仅仅支持Ubuntu 32位系统 * Mac下安装后居然要重启 * 使用的第三方库乱放和HomeBrew冲突 依赖 ------------ * 主流Linux发行版,包括OpenWrt/DD-WRT * Python2 安装 ------------ 首先,从github上下载,可以直接利用`git clone`,也可以下载压缩包自己解压然后安装。下面以git为例,如果没有则需要先安装: ```bash # Ubuntu/Debian sudo apt-get install git # ArchLinux sudo pacman -S git ``` 然后,从项目中clone下来并安装 ```bash git clone git://github.com/humiaozuzu/YaH3C.git cd YaH3C sudo python setup.py install ``` **ArchLinux**默认安装的python是python3,你需要手动安装python2。 使用 ---- 完整的联网过程有2步,首先使用本客户端通过交换机的认证,然后获取ip。 ### 认证 程序运行时必须要有root权限: ```bash sudo yah3c ``` 根据程序的提示输入账号密码就可以开始认证了,有些选项如果看不懂请直接按`Enter`。 ### 获取ip 因为YaH3C仅仅是**认证**客户端,所以通过认证后你需要自己获取ip联网,不过为了方便还是添加了dhcp支持。 如果没有指定dhcp的命令,你可以在认证成功后使用自己喜欢的网络管理工具获取IP,如NetworkManager或Wicd。 YaH3C支持基本的命令行参数,执行`yah3c -h`可以看到支持的命令行参数 ``` bash $ yah3c -h usage: yah3c [-h] [-u USERNAME] [-debug] Yet Another H3C Authentication Client optional arguments: -h, --help show this help message and exit -u USERNAME, --username USERNAME Login in with this username -debug Enable debugging mode ``` 如执行`sudo yah3c -u Maple`可以自动认证`Maple`这个帐号 配置文件格式 --------- 用户的登陆信息按照如下的格式保存在文件`/etc/yah3c.conf`中: ``` ini [account] # 你的帐户 password = 123456 # 密码 ethernet_interface = eth0 # 使用的网卡,默认为eth0 dhcp_command = dhcpcd # 验证成功后使用的dhcp命令(dhcpcd/dhclient),默认为空 daemon = True # 验证成功后是否变成daemon进程,默认为是 ``` ScreenShots ----------- 认证成功: ![success](https://raw.github.com/humiaozuzu/YaH3C/master/screenshots/success.png) 认证失败: ![failure](https://raw.github.com/humiaozuzu/YaH3C/master/screenshots/failure.png) Todo ---- * ~~添加BSD BPF 支持,这样在OS X也可以使用了~~ * 完善收集调试信息的功能,方便用户提交认证信息 * 完善对H3C协议的支持 Thanks ------ * [qiao](https://github.com/qiao) - Write python installation script for YaH3C * [houqp](https://github.com/houqp) - Refered to houqp's [pyh3c](https://github.com/houqp/pyh3c) * [tigersoldier](https://github.com/tigersoldier) - Write EAP-Md5 for YaH3C License ------- YaH3C的代码使用MIT License发布,此外,禁止使用YaH3C以及YaH3C的修改程序用于商业目的(比如交叉编译到路由进行销售等行为) ================================================ FILE: scripts/yah3c ================================================ #!/usr/bin/env python if __name__ == '__main__': import yah3c.yah3c yah3c.yah3c.main() ================================================ FILE: setup.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup import yah3c.yah3c setup(name='yah3c', version=yah3c.yah3c.__version__, description='A program for h3c authentication in SYSU east campus.', author='maple', author_email='maplevalley8@gmail.com', url='https://github.com/humiaozuzu/YaH3C', download_url='https://github.com/humiaozuzu/YaH3C', license='MIT', packages=['yah3c', 'yah3c/colorama'], scripts=['scripts/yah3c'], ) ================================================ FILE: yah3c/__init__.py ================================================ ================================================ FILE: yah3c/colorama/__init__.py ================================================ from .initialise import init, deinit, reinit from .ansi import Fore, Back, Style from .ansitowin32 import AnsiToWin32 VERSION = '0.2.4' ================================================ FILE: yah3c/colorama/ansi.py ================================================ ''' This module generates ANSI character codes to printing colors to terminals. See: http://en.wikipedia.org/wiki/ANSI_escape_code ''' CSI = '\033[' def code_to_chars(code): return CSI + str(code) + 'm' class AnsiCodes(object): def __init__(self, codes): for name in dir(codes): if not name.startswith('_'): value = getattr(codes, name) setattr(self, name, code_to_chars(value)) class AnsiFore: BLACK = 30 RED = 31 GREEN = 32 YELLOW = 33 BLUE = 34 MAGENTA = 35 CYAN = 36 WHITE = 37 RESET = 39 class AnsiBack: BLACK = 40 RED = 41 GREEN = 42 YELLOW = 43 BLUE = 44 MAGENTA = 45 CYAN = 46 WHITE = 47 RESET = 49 class AnsiStyle: BRIGHT = 1 DIM = 2 NORMAL = 22 RESET_ALL = 0 Fore = AnsiCodes( AnsiFore ) Back = AnsiCodes( AnsiBack ) Style = AnsiCodes( AnsiStyle ) ================================================ FILE: yah3c/colorama/ansitowin32.py ================================================ import re import sys from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style from .winterm import WinTerm, WinColor, WinStyle from .win32 import windll if windll is not None: winterm = WinTerm() def is_a_tty(stream): return hasattr(stream, 'isatty') and stream.isatty() class StreamWrapper(object): ''' Wraps a stream (such as stdout), acting as a transparent proxy for all attribute access apart from method 'write()', which is delegated to our Converter instance. ''' def __init__(self, wrapped, converter): # double-underscore everything to prevent clashes with names of # attributes on the wrapped stream object. self.__wrapped = wrapped self.__convertor = converter def __getattr__(self, name): return getattr(self.__wrapped, name) def write(self, text): self.__convertor.write(text) class AnsiToWin32(object): ''' Implements a 'write()' method which, on Windows, will strip ANSI character sequences from the text, and if outputting to a tty, will convert them into win32 function calls. ''' ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') def __init__(self, wrapped, convert=None, strip=None, autoreset=False): # The wrapped stream (normally sys.stdout or sys.stderr) self.wrapped = wrapped # should we reset colors to defaults after every .write() self.autoreset = autoreset # create the proxy wrapping our output stream self.stream = StreamWrapper(wrapped, self) on_windows = sys.platform.startswith('win') # should we strip ANSI sequences from our output? if strip is None: strip = on_windows self.strip = strip # should we should convert ANSI sequences into win32 calls? if convert is None: convert = on_windows and is_a_tty(wrapped) self.convert = convert # dict of ansi codes to win32 functions and parameters self.win32_calls = self.get_win32_calls() # are we wrapping stderr? self.on_stderr = self.wrapped is sys.stderr def should_wrap(self): ''' True if this class is actually needed. If false, then the output stream will not be affected, nor will win32 calls be issued, so wrapping stdout is not actually required. This will generally be False on non-Windows platforms, unless optional functionality like autoreset has been requested using kwargs to init() ''' return self.convert or self.strip or self.autoreset def get_win32_calls(self): if self.convert and winterm: return { AnsiStyle.RESET_ALL: (winterm.reset_all, ), AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), AnsiFore.RED: (winterm.fore, WinColor.RED), AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), AnsiFore.WHITE: (winterm.fore, WinColor.GREY), AnsiFore.RESET: (winterm.fore, ), AnsiBack.BLACK: (winterm.back, WinColor.BLACK), AnsiBack.RED: (winterm.back, WinColor.RED), AnsiBack.GREEN: (winterm.back, WinColor.GREEN), AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), AnsiBack.BLUE: (winterm.back, WinColor.BLUE), AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), AnsiBack.CYAN: (winterm.back, WinColor.CYAN), AnsiBack.WHITE: (winterm.back, WinColor.GREY), AnsiBack.RESET: (winterm.back, ), } def write(self, text): if self.strip or self.convert: self.write_and_convert(text) else: self.wrapped.write(text) self.wrapped.flush() if self.autoreset: self.reset_all() def reset_all(self): if self.convert: self.call_win32('m', (0,)) elif is_a_tty(self.wrapped): self.wrapped.write(Style.RESET_ALL) def write_and_convert(self, text): ''' Write the given text to our wrapped stream, stripping any ANSI sequences from the text, and optionally converting them into win32 calls. ''' cursor = 0 for match in self.ANSI_RE.finditer(text): start, end = match.span() self.write_plain_text(text, cursor, start) self.convert_ansi(*match.groups()) cursor = end self.write_plain_text(text, cursor, len(text)) def write_plain_text(self, text, start, end): if start < end: self.wrapped.write(text[start:end]) self.wrapped.flush() def convert_ansi(self, paramstring, command): if self.convert: params = self.extract_params(paramstring) self.call_win32(command, params) def extract_params(self, paramstring): def split(paramstring): for p in paramstring.split(';'): if p != '': yield int(p) return tuple(split(paramstring)) def call_win32(self, command, params): if params == []: params = [0] if command == 'm': for param in params: if param in self.win32_calls: func_args = self.win32_calls[param] func = func_args[0] args = func_args[1:] kwargs = dict(on_stderr=self.on_stderr) func(*args, **kwargs) elif command in ('H', 'f'): # set cursor position func = winterm.set_cursor_position func(params, on_stderr=self.on_stderr) elif command in ('J'): func = winterm.erase_data func(params, on_stderr=self.on_stderr) ================================================ FILE: yah3c/colorama/initialise.py ================================================ import atexit import sys from .ansitowin32 import AnsiToWin32 orig_stdout = sys.stdout orig_stderr = sys.stderr wrapped_stdout = sys.stdout wrapped_stderr = sys.stderr atexit_done = False def reset_all(): AnsiToWin32(orig_stdout).reset_all() def init(autoreset=False, convert=None, strip=None, wrap=True): if not wrap and any([autoreset, convert, strip]): raise ValueError('wrap=False conflicts with any other arg=True') global wrapped_stdout, wrapped_stderr sys.stdout = wrapped_stdout = \ wrap_stream(orig_stdout, convert, strip, autoreset, wrap) sys.stderr = wrapped_stderr = \ wrap_stream(orig_stderr, convert, strip, autoreset, wrap) global atexit_done if not atexit_done: atexit.register(reset_all) atexit_done = True def deinit(): sys.stdout = orig_stdout sys.stderr = orig_stderr def reinit(): sys.stdout = wrapped_stdout sys.stderr = wrapped_stdout def wrap_stream(stream, convert, strip, autoreset, wrap): if wrap: wrapper = AnsiToWin32(stream, convert=convert, strip=strip, autoreset=autoreset) if wrapper.should_wrap(): stream = wrapper.stream return stream ================================================ FILE: yah3c/colorama/win32.py ================================================ # from winbase.h STDOUT = -11 STDERR = -12 try: from ctypes import windll except ImportError: windll = None SetConsoleTextAttribute = lambda *_: None else: from ctypes import ( byref, Structure, c_char, c_short, c_uint32, c_ushort ) handles = { STDOUT: windll.kernel32.GetStdHandle(STDOUT), STDERR: windll.kernel32.GetStdHandle(STDERR), } SHORT = c_short WORD = c_ushort DWORD = c_uint32 TCHAR = c_char class COORD(Structure): """struct in wincon.h""" _fields_ = [ ('X', SHORT), ('Y', SHORT), ] class SMALL_RECT(Structure): """struct in wincon.h.""" _fields_ = [ ("Left", SHORT), ("Top", SHORT), ("Right", SHORT), ("Bottom", SHORT), ] class CONSOLE_SCREEN_BUFFER_INFO(Structure): """struct in wincon.h.""" _fields_ = [ ("dwSize", COORD), ("dwCursorPosition", COORD), ("wAttributes", WORD), ("srWindow", SMALL_RECT), ("dwMaximumWindowSize", COORD), ] def __str__(self): return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( self.dwSize.Y, self.dwSize.X , self.dwCursorPosition.Y, self.dwCursorPosition.X , self.wAttributes , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X ) def GetConsoleScreenBufferInfo(stream_id=STDOUT): handle = handles[stream_id] csbi = CONSOLE_SCREEN_BUFFER_INFO() success = windll.kernel32.GetConsoleScreenBufferInfo( handle, byref(csbi)) return csbi def SetConsoleTextAttribute(stream_id, attrs): handle = handles[stream_id] return windll.kernel32.SetConsoleTextAttribute(handle, attrs) def SetConsoleCursorPosition(stream_id, position): position = COORD(*position) # If the position is out of range, do nothing. if position.Y <= 0 or position.X <= 0: return # Adjust for Windows' SetConsoleCursorPosition: # 1. being 0-based, while ANSI is 1-based. # 2. expecting (x,y), while ANSI uses (y,x). adjusted_position = COORD(position.Y - 1, position.X - 1) # Adjust for viewport's scroll position sr = GetConsoleScreenBufferInfo(STDOUT).srWindow adjusted_position.Y += sr.Top adjusted_position.X += sr.Left # Resume normal processing handle = handles[stream_id] return windll.kernel32.SetConsoleCursorPosition(handle, adjusted_position) def FillConsoleOutputCharacter(stream_id, char, length, start): handle = handles[stream_id] char = TCHAR(char) length = DWORD(length) num_written = DWORD(0) # Note that this is hard-coded for ANSI (vs wide) bytes. success = windll.kernel32.FillConsoleOutputCharacterA( handle, char, length, start, byref(num_written)) return num_written.value def FillConsoleOutputAttribute(stream_id, attr, length, start): ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' handle = handles[stream_id] attribute = WORD(attr) length = DWORD(length) num_written = DWORD(0) # Note that this is hard-coded for ANSI (vs wide) bytes. return windll.kernel32.FillConsoleOutputAttribute( handle, attribute, length, start, byref(num_written)) ================================================ FILE: yah3c/colorama/winterm.py ================================================ from . import win32 # from wincon.h class WinColor(object): BLACK = 0 BLUE = 1 GREEN = 2 CYAN = 3 RED = 4 MAGENTA = 5 YELLOW = 6 GREY = 7 # from wincon.h class WinStyle(object): NORMAL = 0x00 # dim text, dim background BRIGHT = 0x08 # bright text, dim background class WinTerm(object): def __init__(self): self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes self.set_attrs(self._default) self._default_fore = self._fore self._default_back = self._back self._default_style = self._style def get_attrs(self): return self._fore + self._back * 16 + self._style def set_attrs(self, value): self._fore = value & 7 self._back = (value >> 4) & 7 self._style = value & WinStyle.BRIGHT def reset_all(self, on_stderr=None): self.set_attrs(self._default) self.set_console(attrs=self._default) def fore(self, fore=None, on_stderr=False): if fore is None: fore = self._default_fore self._fore = fore self.set_console(on_stderr=on_stderr) def back(self, back=None, on_stderr=False): if back is None: back = self._default_back self._back = back self.set_console(on_stderr=on_stderr) def style(self, style=None, on_stderr=False): if style is None: style = self._default_style self._style = style self.set_console(on_stderr=on_stderr) def set_console(self, attrs=None, on_stderr=False): if attrs is None: attrs = self.get_attrs() handle = win32.STDOUT if on_stderr: handle = win32.STDERR win32.SetConsoleTextAttribute(handle, attrs) def set_cursor_position(self, position=None, on_stderr=False): if position is None: #I'm not currently tracking the position, so there is no default. #position = self.get_position() return handle = win32.STDOUT if on_stderr: handle = win32.STDERR win32.SetConsoleCursorPosition(handle, position) def erase_data(self, mode=0, on_stderr=False): # 0 (or None) should clear from the cursor to the end of the screen. # 1 should clear from the cursor to the beginning of the screen. # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) # # At the moment, I only support mode 2. From looking at the API, it # should be possible to calculate a different number of bytes to clear, # and to do so relative to the cursor position. if mode[0] not in (2,): return handle = win32.STDOUT if on_stderr: handle = win32.STDERR # here's where we'll home the cursor coord_screen = win32.COORD(0,0) csbi = win32.GetConsoleScreenBufferInfo(handle) # get the number of character cells in the current buffer dw_con_size = csbi.dwSize.X * csbi.dwSize.Y # fill the entire screen with blanks win32.FillConsoleOutputCharacter(handle, ord(' '), dw_con_size, coord_screen) # now set the buffer's attributes accordingly win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); # put the cursor at (0, 0) win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) ================================================ FILE: yah3c/eapauth.py ================================================ """ EAP authentication handler This module sents EAPOL begin/logoff packet and parses received EAP packet """ __all__ = ["EAPAuth"] import socket import os, sys, pwd from subprocess import call from colorama import Fore, Style, init # init() # required in Windows from eappacket import * def display_prompt(color, string): prompt = color + Style.BRIGHT + '==> ' + Style.RESET_ALL prompt += Style.BRIGHT + string + Style.RESET_ALL print prompt def display_packet(packet): # print ethernet_header infomation print 'Ethernet Header Info: ' print '\tFrom: ' + repr(packet[0:6]) print '\tTo: ' + repr(packet[6:12]) print '\tType: ' + repr(packet[12:14]) class EAPAuth: def __init__(self, login_info): # bind the h3c client to the EAP protocal self.client = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETHERTYPE_PAE)) self.client.bind((login_info['ethernet_interface'], ETHERTYPE_PAE)) # get local ethernet card address self.mac_addr = self.client.getsockname()[4] self.ethernet_header = get_ethernet_header(self.mac_addr, PAE_GROUP_ADDR, ETHERTYPE_PAE) self.has_sent_logoff = False self.login_info = login_info self.version_info = '\x06\x07bjQ7SE8BZ3MqHhs3clMregcDY3Y=\x20\x20' def send_start(self): # sent eapol start packet eap_start_packet = self.ethernet_header + get_EAPOL(EAPOL_START) self.client.send(eap_start_packet) display_prompt(Fore.GREEN, 'Sending EAPOL start') def send_logoff(self): # sent eapol logoff packet eap_logoff_packet = self.ethernet_header + get_EAPOL(EAPOL_LOGOFF) self.client.send(eap_logoff_packet) self.has_sent_logoff = True display_prompt(Fore.GREEN, 'Sending EAPOL logoff') def send_response_id(self, packet_id): self.client.send(self.ethernet_header + get_EAPOL(EAPOL_EAPPACKET, get_EAP(EAP_RESPONSE, packet_id, EAP_TYPE_ID, self.version_info + self.login_info['username']))) def send_response_md5(self, packet_id, md5data): md5 = self.login_info['password'][0:16] if len(md5) < 16: md5 = md5 + '\x00' * (16 - len (md5)) chap = [] for i in xrange(0, 16): chap.append(chr(ord(md5[i]) ^ ord(md5data[i]))) resp = chr(len(chap)) + ''.join(chap) + self.login_info['username'] eap_packet = self.ethernet_header + get_EAPOL(EAPOL_EAPPACKET, get_EAP(EAP_RESPONSE, packet_id, EAP_TYPE_MD5, resp)) try: self.client.send(eap_packet) except socket.error, msg: print "Connection error!" exit(-1) def send_response_h3c(self, packet_id): resp = chr(len(self.login_info['password'])) + self.login_info['password'] + self.login_info['username'] eap_packet = self.ethernet_header + get_EAPOL(EAPOL_EAPPACKET, get_EAP(EAP_RESPONSE, packet_id, EAP_TYPE_H3C, resp)) try: self.client.send(eap_packet) except socket.error, msg: print "Connection error!" exit(-1) def display_login_message(self, msg): """ display the messages received form the radius server, including the error meaasge after logging failed or other meaasge from networking centre """ try: print msg.decode('gbk') except UnicodeDecodeError: print msg def EAP_handler(self, eap_packet): vers, type, eapol_len = unpack("!BBH",eap_packet[:4]) if type != EAPOL_EAPPACKET: display_prompt(Fore.YELLOW, 'Got unknown EAPOL type %i' % type) # EAPOL_EAPPACKET type code, id, eap_len = unpack("!BBH", eap_packet[4:8]) if code == EAP_SUCCESS: display_prompt(Fore.YELLOW, 'Got EAP Success') if self.login_info['dhcp_command']: display_prompt(Fore.YELLOW, 'Obtaining IP Address:') call([self.login_info['dhcp_command'], self.login_info['ethernet_interface']]) if self.login_info['daemon'] == 'True': daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log') elif code == EAP_FAILURE: if (self.has_sent_logoff): display_prompt(Fore.YELLOW, 'Logoff Successfully!') #self.display_login_message(eap_packet[10:]) else: display_prompt(Fore.YELLOW, 'Got EAP Failure') #self.display_login_message(eap_packet[10:]) exit(-1) elif code == EAP_RESPONSE: display_prompt(Fore.YELLOW, 'Got Unknown EAP Response') elif code == EAP_REQUEST: reqtype = unpack("!B", eap_packet[8:9])[0] reqdata = eap_packet[9:4 + eap_len] if reqtype == EAP_TYPE_ID: display_prompt(Fore.YELLOW, 'Got EAP Request for identity') self.send_response_id(id) display_prompt(Fore.GREEN, 'Sending EAP response with identity = [%s]' % self.login_info['username']) elif reqtype == EAP_TYPE_H3C: display_prompt(Fore.YELLOW, 'Got EAP Request for Allocation') self.send_response_h3c(id) display_prompt(Fore.GREEN, 'Sending EAP response with password') elif reqtype == EAP_TYPE_MD5: data_len = unpack("!B", reqdata[0:1])[0] md5data = reqdata[1:1 + data_len] display_prompt(Fore.YELLOW, 'Got EAP Request for MD5-Challenge') self.send_response_md5(id, md5data) display_prompt(Fore.GREEN, 'Sending EAP response with password') else: display_prompt(Fore.YELLOW, 'Got unknown Request type (%i)' % reqtype) elif code==10 and id==5: self.display_login_message(eap_packet[12:]) else: display_prompt(Fore.YELLOW, 'Got unknown EAP code (%i)' % code) def serve_forever(self): try: self.send_start() while True: eap_packet = self.client.recv(1600) # strip the ethernet_header and handle self.EAP_handler(eap_packet[14:]) except KeyboardInterrupt: print Fore.RED + Style.BRIGHT + 'Interrupted by user' + Style.RESET_ALL self.send_logoff() except socket.error , msg: print "Connection error: %s" %msg exit(-1) def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): '''This forks the current process into a daemon. The stdin, stdout, and stderr arguments are file names that will be opened and be used to replace the standard file descriptors in sys.stdin, sys.stdout, and sys.stderr. These arguments are optional and default to /dev/null. Note that stderr is opened unbuffered, so if it shares a file with stdout then interleaved output may not appear in the order that you expect. ''' # Do first fork. try: pid = os.fork() if pid > 0: sys.exit(0) # Exit first parent. except OSError, e: sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) sys.exit(1) # Decouple from parent environment. os.chdir("/") os.umask(0) os.setsid() # Do second fork. try: pid = os.fork() if pid > 0: sys.exit(0) # Exit second parent. except OSError, e: sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) ) sys.exit(1) # Now I am a daemon! # Redirect standard file descriptors. si = open(stdin, 'r') so = open(stdout, 'a+') se = open(stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) ================================================ FILE: yah3c/eappacket.py ================================================ from struct import * ## Constants # Reference: http://tools.ietf.org/html/rfc3748 ETHERTYPE_PAE = 0x888e PAE_GROUP_ADDR = "\x01\x80\xc2\x00\x00\x03" BROADCAST_ADDR = "\xff\xff\xff\xff\xff\xff" EAPOL_VERSION = 1 EAPOL_EAPPACKET = 0 # packet info for EAPOL_EAPPACKET EAPOL_START = 1 EAPOL_LOGOFF = 2 EAPOL_KEY = 3 EAPOL_ASF = 4 EAP_REQUEST = 1 EAP_RESPONSE = 2 EAP_SUCCESS = 3 EAP_FAILURE = 4 # packet info followed by EAP_RESPONSE # 1 Identity # 2 Notification # 3 Nak (Response only) # 4 MD5-Challenge # 5 One Time Password (OTP) # 6 Generic Token Card (GTC) # 254 Expanded Types # 255 Experimental use EAP_TYPE_ID = 1 # identity EAP_TYPE_MD5 = 4 # md5 Challenge EAP_TYPE_H3C = 7 # H3C eap packet(used for SYSU east campus) ### Packet builders def get_EAPOL(type, payload=""): return pack("!BBH", EAPOL_VERSION, type, len(payload))+payload def get_EAP(code, id, type=0, data=""): if code in [EAP_SUCCESS, EAP_FAILURE]: return pack("!BBH", code, id, 4) else: return pack("!BBHB", code, id, 5+len(data), type)+data def get_ethernet_header(src, dst, type): return dst+src+pack("!H",type) ================================================ FILE: yah3c/usermgr.py ================================================ """ User Management Module This module reads the 'users.conf' file and gets all users's info. """ __all__ = ["UserMgr"] import ConfigParser class UserMgr: """User Manager The format of the user_info is: user_info = { "username": "maple", "password": "valley", "ethernet_interface": "eth0", "dhcp_command": "dhcpcd", "daemon": True, # following has not implemented yet "carry_version_info": True, "broadcast_logoff": False "packet_type": "unicast" } """ def __init__(self, path=None): if path is None: self.users_cfg_path = '/etc/yah3c.conf' else: self.users_cfg_path = path self.config = ConfigParser.ConfigParser() self.config.read(self.users_cfg_path) def save_and_reload(self): fp = open(self.users_cfg_path, 'w') self.config.write(fp) fp.close() self.config.read(self.users_cfg_path) def get_user_number(self): return len(self.config.sections()) def get_all_users_info(self): users_info = [] for username in self.config.sections(): user_info = dict(self.config.items(username)) user_info['username'] = username users_info.append(user_info) return users_info def get_user_info(self, username): user_info = dict(self.config.items(username)) user_info['username'] = username return user_info def add_user(self, user_info): self.config.add_section(user_info['username']) self.update_user_info(user_info) def remove_user(self, username): self.config.remove_section(username) self.save_and_reload() def update_user_info(self, user_info): self.config.set(user_info['username'], 'password', user_info['password']) self.config.set(user_info['username'], 'ethernet_interface', user_info['ethernet_interface']) self.config.set(user_info['username'], 'dhcp_command', user_info['dhcp_command']) self.config.set(user_info['username'], 'daemon', user_info['daemon']) self.save_and_reload() ================================================ FILE: yah3c/yah3c.py ================================================ #!/usr/bin/env python # -*- coding:utf-8 -*- """ Main program for YaH3C. """ __version__ = '0.5' import os, sys import ConfigParser import getpass import argparse import logging import eapauth import usermgr def parse_arguments(): parser = argparse.ArgumentParser(description='Yet Another H3C Authentication Client', prog='yah3c') parser.add_argument('-u', '--username', help='Login in with this username') # parser.add_argument('-p', '--password', # help='Password') # parser.add_argument('-i', '--interface', default='eth0', # help='Etherent interface used. Set as eth0 by default.') # parser.add_argument('-d', '--daemon', action='store_true', # help='Fork to background after authentication.') # parser.add_argument('-D', '--dhcp', # help='DHCP cmd used to obtain ip after authentication.') parser.add_argument('-debug', action='store_true', help='Enable debugging mode') args = parser.parse_args() return args def prompt_user_info(): username = raw_input('Input username: ') while True: password = getpass.getpass('Input password: ') password_again = getpass.getpass('Input again: ') if password == password_again: break else: print 'Password do not match!' dev = raw_input('Decice(eth0 by default): ') if not dev: dev = 'eth0' choice = raw_input('Forked to background after authentication(Yes by default)\n: ') if choice == 'n' or choice == 'N': daemon = False else: daemon = True dhcp_cmd = raw_input('Dhcp command(Press Enter to pass): ') if not dhcp_cmd: dhcp_cmd = '' return { 'username': username, 'password': password, 'ethernet_interface': dev, 'daemon': daemon, 'dhcp_command': dhcp_cmd } def enter_interactive_usermanager(): um = usermgr.UserMgr() if um.get_user_number() == 0: choice = raw_input('No user conf file found, creat a new one?\n: ') if choice == 'y' or choice == 'Y': login_info = prompt_user_info() um.add_user(login_info) else: exit(-1) # user has been created or already have users users_info = um.get_all_users_info() print '0 - add a new user' for i, user_info in enumerate(users_info): print '%d - %s(%s)' %(i + 1, user_info['username'], user_info['ethernet_interface']) while True: try: choice = int(raw_input('Your choice: ')) except ValueError: print 'Please input a valid number!' else: break; if choice == 0: try: user_info = prompt_user_info() um.add_user(user_info) except ConfigParser.DuplicateSectionError: print 'User already exist!' exit(-1) else: return users_info[choice - 1] def start_yah3c(login_info): yah3c = eapauth.EAPAuth(login_info) yah3c.serve_forever() def main(): args = parse_arguments() args = vars(args) # check for root privilege if not (os.getuid() == 0): print (u'亲,要加sudo!') exit(-1) # check if debugging mode enabled if args['debug'] is True: logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logging.debug('Debugging mode enabled.') logging.debug(args) # if no username specified then enter interactive mode if args['username'] is None: login_info = enter_interactive_usermanager() logging.debug(login_info) start_yah3c(login_info) # if there is username, then get it's info um = usermgr.UserMgr() login_info = um.get_user_info(args['username']) logging.debug(login_info) start_yah3c(login_info) if __name__ == "__main__": main()