Merge pull request #499 from lopezvg/master
SMB client: versión 1.1.25 de pysmb y nuevos canales con Enlaces de Emergencia
This commit is contained in:
@@ -577,7 +577,8 @@ def findvideos(item):
|
||||
return item #... y nos vamos
|
||||
|
||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
if not item.videolibray_emergency_urls:
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
|
||||
#Ahora tratamos los enlaces .torrent
|
||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"id": "intervenidos_channels_list",
|
||||
"type": "text",
|
||||
"label": "Lista de canales y clones de NewPct1 intervenidos y orden de sustitución de URLs",
|
||||
"default": "('0', 'canal_org', 'canal_des', 'url_org', 'url_des', 'patron1', 'patron2', 'patron3', 'patron4', 'patron5', 'content_inc', 'content_exc', 'ow_force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)([^0-9]+-)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-\\d+-(Temporada-).html', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-(\\d+)-', '', 'tvshow, season', '', 'force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-([^.]+).html', '', '', '', 'movie', '', 'force'), ('0', 'mejortorrent', 'mejortorrent', 'http://www.mejortorrent.com/', 'http://www.mejortorrent.org/', '', '', '', '', '', '*', '', 'force'), ('0', 'plusdede', 'megadede', 'https://www.plusdede.com', 'https://www.megadede.com', '', '', '', '', '', '*', '', 'auto')",
|
||||
"default": "('0', 'canal_org', 'canal_des', 'url_org', 'url_des', 'patron1', 'patron2', 'patron3', 'patron4', 'patron5', 'content_inc', 'content_exc', 'ow_force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)([^0-9]+-)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-\\d+-(Temporada-).html', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-(\\d+)-', '', 'tvshow, season', '', 'force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-([^.]+).html', '', '', '', 'movie', '', 'force'), ('0', 'mejortorrent', 'mejortorrent', 'http://www.mejortorrent.com/', 'http://www.mejortorrent.org/', '', '', '', '', '', '*', '', 'force'), ('1', 'plusdede', 'megadede', 'https://www.plusdede.com', 'https://www.megadede.com', '', '', '', '', '', '*', '', 'auto'), ('1', 'newpct1', 'descargas2020', 'http://www.newpct1.com', 'http://descargas2020.com', '', '', '', '', '', '*', '', 'force')",
|
||||
"enabled": true,
|
||||
"visible": false
|
||||
},
|
||||
|
||||
@@ -45,6 +45,28 @@
|
||||
"VOSE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls",
|
||||
"type": "list",
|
||||
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||
"default": 1,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"No",
|
||||
"Guardar",
|
||||
"Borrar",
|
||||
"Actualizar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls_torrents",
|
||||
"type": "bool",
|
||||
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": "!eq(-1,'No')"
|
||||
},
|
||||
{
|
||||
"id": "include_in_newest_torrent",
|
||||
"type": "bool",
|
||||
|
||||
@@ -355,7 +355,7 @@ def listado(item):
|
||||
title = re.sub(r'[\(|\[]\s+[\)|\]]', '', title)
|
||||
title = title.replace('()', '').replace('[]', '').strip().lower().title()
|
||||
|
||||
item_local.from_title = title.strip().lower().title() #Guardamos esta etiqueta para posible desambiguación de título
|
||||
item_local.from_title = title.strip().lower().title() #Guardamos esta etiqueta para posible desambiguación de título
|
||||
|
||||
#Salvamos el título según el tipo de contenido
|
||||
if item_local.contentType == "movie":
|
||||
@@ -387,8 +387,8 @@ def listado(item):
|
||||
|
||||
title = '%s' % curr_page
|
||||
|
||||
if cnt_matches + 1 >= last_title: #Si hemos pintado ya todo lo de esta página...
|
||||
cnt_matches = 0 #... la próxima pasada leeremos otra página
|
||||
if cnt_matches + 1 >= last_title: #Si hemos pintado ya todo lo de esta página...
|
||||
cnt_matches = 0 #... la próxima pasada leeremos otra página
|
||||
next_page_url = re.sub(r'page=(\d+)', r'page=' + str(int(re.search('\d+', next_page_url).group()) + 1), next_page_url)
|
||||
|
||||
itemlist.append(Item(channel=item.channel, action="listado", title=">> Página siguiente " + title, url=next_page_url, extra=item.extra, extra2=item.extra2, last_page=str(last_page), curr_page=str(curr_page + 1), cnt_matches=str(cnt_matches)))
|
||||
@@ -399,10 +399,10 @@ def listado(item):
|
||||
def findvideos(item):
|
||||
logger.info()
|
||||
itemlist = []
|
||||
itemlist_t = [] #Itemlist total de enlaces
|
||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||
itemlist_t = [] #Itemlist total de enlaces
|
||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||
if not item.language:
|
||||
item.language = ['CAST'] #Castellano por defecto
|
||||
item.language = ['CAST'] #Castellano por defecto
|
||||
matches = []
|
||||
item.category = categoria
|
||||
|
||||
@@ -412,22 +412,53 @@ def findvideos(item):
|
||||
#logger.debug(item)
|
||||
|
||||
matches = item.url
|
||||
if not matches: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web: " + item)
|
||||
if not matches: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web: " + str(item))
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
#logger.debug(matches)
|
||||
|
||||
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||
item.emergency_urls.append(matches) #Salvamnos matches...
|
||||
|
||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
if not item.videolibray_emergency_urls:
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
|
||||
#Ahora tratamos los enlaces .torrent
|
||||
for scrapedurl, quality in matches: #leemos los magnets con la diferentes calidades
|
||||
for scrapedurl, quality in matches: #leemos los magnets con la diferentes calidades
|
||||
#Generamos una copia de Item para trabajar sobre ella
|
||||
item_local = item.clone()
|
||||
|
||||
item_local.url = scrapedurl
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls[0].append(scrapedurl) #guardamos la url y pasamos a la siguiente
|
||||
continue
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||
if item.armagedon:
|
||||
item_local.url = item.emergency_urls[0][0] #... ponemos la emergencia como primaria
|
||||
del item.emergency_urls[0][0] #Una vez tratado lo limpiamos
|
||||
|
||||
size = ''
|
||||
if not item.armagedon:
|
||||
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||
if size:
|
||||
quality += ' [%s]' % size
|
||||
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||
quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % quality
|
||||
|
||||
#Añadimos la calidad y copiamos la duración
|
||||
item_local.quality = quality
|
||||
@@ -445,9 +476,9 @@ def findvideos(item):
|
||||
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
||||
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
||||
|
||||
item_local.alive = "??" #Calidad del link sin verificar
|
||||
item_local.action = "play" #Visualizar vídeo
|
||||
item_local.server = "torrent" #Servidor Torrent
|
||||
item_local.alive = "??" #Calidad del link sin verificar
|
||||
item_local.action = "play" #Visualizar vídeo
|
||||
item_local.server = "torrent" #Servidor Torrent
|
||||
|
||||
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
||||
|
||||
@@ -459,6 +490,9 @@ def findvideos(item):
|
||||
|
||||
#logger.debug(item_local)
|
||||
|
||||
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||
return item #... nos vamos
|
||||
|
||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||
else:
|
||||
|
||||
@@ -44,6 +44,28 @@
|
||||
"VOSE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls",
|
||||
"type": "list",
|
||||
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||
"default": 1,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"No",
|
||||
"Guardar",
|
||||
"Borrar",
|
||||
"Actualizar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls_torrents",
|
||||
"type": "bool",
|
||||
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": "!eq(-1,'No')"
|
||||
},
|
||||
{
|
||||
"id": "timeout_downloadpage",
|
||||
"type": "list",
|
||||
|
||||
@@ -372,6 +372,7 @@ def findvideos(item):
|
||||
if not item.language:
|
||||
item.language = ['CAST'] #Castellano por defecto
|
||||
matches = []
|
||||
subtitles = []
|
||||
item.category = categoria
|
||||
|
||||
#logger.debug(item)
|
||||
@@ -389,51 +390,74 @@ def findvideos(item):
|
||||
if not data:
|
||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
#Extraemos el thumb
|
||||
if not item.thumbnail:
|
||||
item.thumbnail = scrapertools.find_single_match(data, patron) #guardamos thumb si no existe
|
||||
|
||||
#Extraemos quality, audio, year, country, size, scrapedlanguage
|
||||
patron = '<\/script><\/div><ul>(?:<li><label>Fecha de estreno <\/label>[^<]+<\/li>)?(?:<li><label>Genero <\/label>[^<]+<\/li>)?(?:<li><label>Calidad <\/label>([^<]+)<\/li>)?(?:<li><label>Audio <\/label>([^<]+)<\/li>)?(?:<li><label>Fecha <\/label>.*?(\d+)<\/li>)?(?:<li><label>Pais de Origen <\/label>([^<]+)<\/li>)?(?:<li><label>Tamaño <\/label>([^<]+)<\/li>)?(<li> Idioma[^<]+<img src=.*?<br \/><\/li>)?'
|
||||
try:
|
||||
quality, audio, year, country, size, scrapedlanguage = scrapertools.find_single_match(data, patron)
|
||||
except:
|
||||
quality = ''
|
||||
audio = ''
|
||||
year = ''
|
||||
country = ''
|
||||
size = ''
|
||||
scrapedlanguage = ''
|
||||
if quality: item.quality = quality
|
||||
if audio: item.quality += ' %s' % audio.strip()
|
||||
if not item.infoLabels['year'] and year: item.infoLabels['year'] = year
|
||||
if size: item.quality += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||
if size: item.title += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||
language = []
|
||||
matches = re.compile('(\d+.png)', re.DOTALL).findall(scrapedlanguage)
|
||||
for lang in matches:
|
||||
if "1.png" in lang and not 'CAST' in language: language += ['CAST']
|
||||
if "512.png" in lang and not 'LAT' in language: language += ['LAT']
|
||||
if ("1.png" not in lang and "512.png" not in lang) and not 'VOSE' in language: language += ['VOSE']
|
||||
if language: item.language = language
|
||||
|
||||
#Extraemos los enlaces .torrent
|
||||
##Modalidad de varios archivos
|
||||
patron = '<div class="fichadescargat"><\/div><div class="table-responsive"[^>]+>.*?<\/thead><tbody>(.*?)<\/tbody><\/table><\/div>'
|
||||
if scrapertools.find_single_match(data, patron):
|
||||
data_torrents = scrapertools.find_single_match(data, patron)
|
||||
patron = '<tr><td>.*?<\/td><td><a href="([^"]+)"[^>]+><[^>]+><\/a><\/td><\/tr>'
|
||||
#Modalidad de un archivo
|
||||
else:
|
||||
data_torrents = data
|
||||
patron = '<div class="fichasubtitulos">.*?<\/div><\/li><\/ul>.*?<a href="([^"]+)"'
|
||||
matches = re.compile(patron, re.DOTALL).findall(data_torrents)
|
||||
if not matches: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
if not item.armagedon:
|
||||
#Extraemos el thumb
|
||||
if not item.thumbnail:
|
||||
item.thumbnail = scrapertools.find_single_match(data, patron) #guardamos thumb si no existe
|
||||
|
||||
#Extraemos quality, audio, year, country, size, scrapedlanguage
|
||||
patron = '<\/script><\/div><ul>(?:<li><label>Fecha de estreno <\/label>[^<]+<\/li>)?(?:<li><label>Genero <\/label>[^<]+<\/li>)?(?:<li><label>Calidad <\/label>([^<]+)<\/li>)?(?:<li><label>Audio <\/label>([^<]+)<\/li>)?(?:<li><label>Fecha <\/label>.*?(\d+)<\/li>)?(?:<li><label>Pais de Origen <\/label>([^<]+)<\/li>)?(?:<li><label>Tamaño <\/label>([^<]+)<\/li>)?(<li> Idioma[^<]+<img src=.*?<br \/><\/li>)?'
|
||||
try:
|
||||
quality = ''
|
||||
audio = ''
|
||||
year = ''
|
||||
country = ''
|
||||
size = ''
|
||||
scrapedlanguage = ''
|
||||
quality, audio, year, country, size, scrapedlanguage = scrapertools.find_single_match(data, patron)
|
||||
except:
|
||||
pass
|
||||
if quality: item.quality = quality
|
||||
if audio: item.quality += ' %s' % audio.strip()
|
||||
if not item.infoLabels['year'] and year: item.infoLabels['year'] = year
|
||||
if size: item.quality += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||
if size:
|
||||
item.title = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item.title) #Quitamos size de título, si lo traía
|
||||
item.title += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||
|
||||
language = []
|
||||
matches_lang = re.compile('(\d+.png)', re.DOTALL).findall(scrapedlanguage)
|
||||
for lang in matches_lang:
|
||||
if "1.png" in lang and not 'CAST' in language: language += ['CAST']
|
||||
if "512.png" in lang and not 'LAT' in language: language += ['LAT']
|
||||
if ("1.png" not in lang and "512.png" not in lang) and not 'VOSE' in language: language += ['VOSE']
|
||||
if language: item.language = language
|
||||
|
||||
#Extraemos los enlaces .torrent
|
||||
#Modalidad de varios archivos
|
||||
patron = '<div class="fichadescargat"><\/div><div class="table-responsive"[^>]+>.*?<\/thead><tbody>(.*?)<\/tbody><\/table><\/div>'
|
||||
if scrapertools.find_single_match(data, patron):
|
||||
data_torrents = scrapertools.find_single_match(data, patron)
|
||||
patron = '<tr><td>.*?<\/td><td><a href="([^"]+)"[^>]+><[^>]+><\/a><\/td><\/tr>'
|
||||
#Modalidad de un archivo
|
||||
else:
|
||||
data_torrents = data
|
||||
patron = '<div class="fichasubtitulos">.*?<\/div><\/li><\/ul>.*?<a href="([^"]+)"'
|
||||
matches = re.compile(patron, re.DOTALL).findall(data_torrents)
|
||||
if not matches: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
else: #SERIES: ya viene con las urls
|
||||
data = item.url #inicio data por compatibilidad
|
||||
@@ -447,11 +471,22 @@ def findvideos(item):
|
||||
del item.subtitle
|
||||
else:
|
||||
subtitle = scrapertools.find_single_match(data, patron).replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
||||
data_subtitle = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)", "", httptools.downloadpage(subtitle, timeout=timeout).data)
|
||||
patron = '<tbody>(<tr class="fichserietabla_b">.*?<\/tr>)<\/tbody>' #salvamos el bloque
|
||||
data_subtitle = scrapertools.find_single_match(data_subtitle, patron)
|
||||
patron = '<tr class="fichserietabla_b">.*?<a href="([^"]+)"'
|
||||
subtitles = re.compile(patron, re.DOTALL).findall(data_subtitle) #Creamos una lista con todos los sub-títulos
|
||||
|
||||
try:
|
||||
data_subtitle = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)", "", httptools.downloadpage(subtitle, timeout=timeout).data)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not data_subtitle:
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
patron = '<tbody>(<tr class="fichserietabla_b">.*?<\/tr>)<\/tbody>' #salvamos el bloque
|
||||
data_subtitle = scrapertools.find_single_match(data_subtitle, patron)
|
||||
patron = '<tr class="fichserietabla_b">.*?<a href="([^"]+)"'
|
||||
subtitles = re.compile(patron, re.DOTALL).findall(data_subtitle) #Creamos una lista con todos los sub-títulos
|
||||
if subtitles:
|
||||
item.subtitle = []
|
||||
for subtitle in subtitles:
|
||||
@@ -460,29 +495,49 @@ def findvideos(item):
|
||||
|
||||
#logger.debug("PATRON: " + patron)
|
||||
#logger.debug(matches)
|
||||
#logger.debug(subtitles)
|
||||
#logger.debug(data)
|
||||
|
||||
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||
item.emergency_urls.append(subtitles) #Salvamnos matches de los subtítulos
|
||||
|
||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
if not item.videolibray_emergency_urls:
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
|
||||
#Ahora tratamos los enlaces .torrent
|
||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||
#Generamos una copia de Item para trabajar sobre ella
|
||||
item_local = item.clone()
|
||||
|
||||
item_local.url = scrapedurl.replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls[0].append(scrapedurl) #guardamos la url y pasamos a la siguiente
|
||||
continue
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||
if item.armagedon:
|
||||
item_local.url = item.emergency_urls[0][0] #... ponemos la emergencia como primaria
|
||||
del item.emergency_urls[0][0] #Una vez tratado lo limpiamos
|
||||
|
||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||
if not size:
|
||||
size = scrapertools.find_single_match(item_local.quality, '\s*\[(\d+,?\d*?\s\w\s*[b|B])\]')
|
||||
if not size and not item.armagedon:
|
||||
size = generictools.get_torrent_size(scrapedurl) #Buscamos el tamaño en el .torrent
|
||||
if size:
|
||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
item_local.title = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||
item_local.quality = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||
|
||||
#Ahora pintamos el link del Torrent
|
||||
item_local.url = scrapedurl.replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||
|
||||
#Preparamos título y calidad, quitamos etiquetas vacías
|
||||
@@ -506,6 +561,9 @@ def findvideos(item):
|
||||
#logger.debug("TORRENT: " + scrapedurl + " / title gen/torr: " + item.title + " / " + item_local.title + " / calidad: " + item_local.quality + " / content: " + item_local.contentTitle + " / " + item_local.contentSerieName)
|
||||
#logger.debug(item_local)
|
||||
|
||||
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||
return item #... nos vamos
|
||||
|
||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||
else:
|
||||
|
||||
@@ -45,6 +45,28 @@
|
||||
"VOSE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls",
|
||||
"type": "list",
|
||||
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||
"default": 1,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"No",
|
||||
"Guardar",
|
||||
"Borrar",
|
||||
"Actualizar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls_torrents",
|
||||
"type": "bool",
|
||||
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": "!eq(-1,'No')"
|
||||
},
|
||||
{
|
||||
"id": "include_in_newest_peliculas",
|
||||
"type": "bool",
|
||||
|
||||
@@ -345,56 +345,100 @@ def findvideos(item):
|
||||
if not data:
|
||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||
if not item.armagedon:
|
||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||
if not matches: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
item = generictools.web_intervenida(item, data) #Verificamos que no haya sido clausurada
|
||||
if item.intervencion: #Sí ha sido clausurada judicialmente
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist) #Llamamos al método para el pintado del error
|
||||
else:
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
#logger.debug("PATRON: " + patron)
|
||||
#logger.debug(matches)
|
||||
#logger.debug(data)
|
||||
|
||||
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||
|
||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
if not item.videolibray_emergency_urls:
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
|
||||
#Ahora tratamos los enlaces .torrent
|
||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||
if 'javascript' in scrapedurl: #evitamos la basura
|
||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||
if 'javascript' in scrapedurl: #evitamos la basura
|
||||
continue
|
||||
|
||||
url = urlparse.urljoin(host, scrapedurl)
|
||||
#Leemos la siguiente página, que es de verdad donde está el magnet/torrent
|
||||
try:
|
||||
data = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)| ", "", httptools.downloadpage(url, timeout=timeout).data)
|
||||
data = unicode(data, "utf-8", errors="replace").encode("utf-8")
|
||||
except:
|
||||
pass
|
||||
|
||||
patron = "window.open\('([^']+)'"
|
||||
url = scrapertools.find_single_match(data, patron)
|
||||
if not url: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
continue #si no hay más datos, algo no funciona, pasamos al siguiente
|
||||
url = ''
|
||||
if not item.armagedon:
|
||||
url = urlparse.urljoin(host, scrapedurl)
|
||||
#Leemos la siguiente página, que es de verdad donde está el magnet/torrent
|
||||
try:
|
||||
data = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)| ", "", httptools.downloadpage(url, timeout=timeout).data)
|
||||
data = unicode(data, "utf-8", errors="replace").encode("utf-8")
|
||||
except:
|
||||
pass
|
||||
|
||||
patron = "window.open\('([^']+)'"
|
||||
url = scrapertools.find_single_match(data, patron)
|
||||
if not url: #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
continue #si no hay más datos, algo no funciona, pasamos al siguiente
|
||||
|
||||
#Generamos una copia de Item para trabajar sobre ella
|
||||
item_local = item.clone()
|
||||
|
||||
|
||||
item_local.url = urlparse.urljoin(host, url)
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls[0].append(item_local.url) #guardamos la url y pasamos a la siguiente
|
||||
continue
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||
if item.armagedon:
|
||||
item_local.url = item.emergency_urls[0][0] #Restauramos la url
|
||||
if len(item.emergency_urls[0]) > 1:
|
||||
del item.emergency_urls[0][0]
|
||||
|
||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||
if not size:
|
||||
size = scrapertools.find_single_match(item_local.quality, '\s?\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||
if not size and not item.armagedon:
|
||||
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||
if size:
|
||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||
item_local.title = re.sub(r'\s?\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
item_local.quality = re.sub(r'\s?\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||
|
||||
#Ahora pintamos el link del Torrent
|
||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||
@@ -421,6 +465,9 @@ def findvideos(item):
|
||||
|
||||
#logger.debug(item_local)
|
||||
|
||||
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||
return item #... nos vamos
|
||||
|
||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||
else:
|
||||
|
||||
@@ -52,6 +52,9 @@ def list_movies(item, silent=False):
|
||||
|
||||
head_nfo, new_item = videolibrarytools.read_nfo(nfo_path)
|
||||
|
||||
if not new_item: #Si no ha leído bien el .nfo, pasamos a la siguiente
|
||||
continue
|
||||
|
||||
if len(new_item.library_urls) > 1:
|
||||
multicanal = True
|
||||
else:
|
||||
|
||||
@@ -47,6 +47,28 @@
|
||||
"VOSE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls",
|
||||
"type": "list",
|
||||
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||
"default": 1,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"No",
|
||||
"Guardar",
|
||||
"Borrar",
|
||||
"Actualizar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "emergency_urls_torrents",
|
||||
"type": "bool",
|
||||
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": "!eq(-1,'No')"
|
||||
},
|
||||
{
|
||||
"id": "timeout_downloadpage",
|
||||
"type": "list",
|
||||
|
||||
@@ -488,6 +488,8 @@ def findvideos(item):
|
||||
itemlist = []
|
||||
itemlist_t = [] #Itemlist total de enlaces
|
||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||
titles = [] #Títulos de servidores Directos
|
||||
urls = [] #Urls de servidores Directos
|
||||
if not item.language:
|
||||
item.language = ['CAST'] #Castellano por defecto
|
||||
matches = []
|
||||
@@ -512,33 +514,75 @@ def findvideos(item):
|
||||
if not data:
|
||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches de torrents
|
||||
titles = item.emergency_urls[2] #Restauramos títulos de servidores Directos
|
||||
urls = item.emergency_urls[3] #Restauramos urls de servidores Directos
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||
if not item.armagedon:
|
||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||
if not matches and not scrapertools.find_single_match(data, 'data-TPlayerNv="Opt\d+">.*? <span>(.*?)</span></li>'): #error
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
item = generictools.web_intervenida(item, data) #Verificamos que no haya sido clausurada
|
||||
if item.intervencion: #Sí ha sido clausurada judicialmente
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist) #Llamamos al método para el pintado del error
|
||||
else:
|
||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||
matches = item.emergency_urls[1] #Restauramos matches de torrents
|
||||
titles = item.emergency_urls[2] #Restauramos títulos de servidores Directos
|
||||
urls = item.emergency_urls[3] #Restauramos urls de servidores Directos
|
||||
item.armagedon = True #Marcamos la situación como catastrófica
|
||||
else:
|
||||
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||
return item #Devolvemos el Item de la llamada
|
||||
else:
|
||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||
|
||||
#logger.debug("PATRON: " + patron)
|
||||
#logger.debug(matches)
|
||||
#logger.debug(data)
|
||||
logger.debug(data)
|
||||
|
||||
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||
|
||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
if not item.videolibray_emergency_urls:
|
||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||
|
||||
#Ahora tratamos los enlaces .torrent
|
||||
for scrapedurl, scrapedserver, language, quality in matches: #leemos los torrents con la diferentes calidades
|
||||
for scrapedurl, scrapedserver, language, quality in matches: #leemos los torrents con la diferentes calidades
|
||||
#Generamos una copia de Item para trabajar sobre ella
|
||||
item_local = item.clone()
|
||||
|
||||
if 'torrent' not in scrapedserver.lower(): #Si es un servidor Directo, lo dejamos para luego
|
||||
if 'torrent' not in scrapedserver.lower(): #Si es un servidor Directo, lo dejamos para luego
|
||||
continue
|
||||
|
||||
item_local.url = scrapedurl
|
||||
if '.io/' in item_local.url:
|
||||
item_local.url = re.sub(r'http.?:\/\/\w+\.\w+\/', host, item_local.url) #Aseguramos el dominio del canal
|
||||
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls[0].append(item_local.url) #guardamos la url y pasamos a la siguiente
|
||||
continue
|
||||
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||
if item.armagedon:
|
||||
item_local.url = item.emergency_urls[0][0] #Restauramos la url
|
||||
if len(item.emergency_urls[0]) > 1:
|
||||
del item.emergency_urls[0][0]
|
||||
|
||||
#Detectamos idiomas
|
||||
if ("latino" in scrapedurl.lower() or "latino" in language.lower()) and "LAT" not in item_local.language:
|
||||
item_local.language += ['LAT']
|
||||
@@ -554,17 +598,19 @@ def findvideos(item):
|
||||
item_local.quality = quality
|
||||
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'):
|
||||
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
||||
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||
|
||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||
if not size:
|
||||
if not size and not item.armagedon:
|
||||
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||
if size:
|
||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||
|
||||
#Ahora pintamos el link del Torrent
|
||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||
@@ -577,9 +623,9 @@ def findvideos(item):
|
||||
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
||||
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
||||
|
||||
item_local.alive = "??" #Calidad del link sin verificar
|
||||
item_local.action = "play" #Visualizar vídeo
|
||||
item_local.server = "torrent" #Servidor Torrent
|
||||
item_local.alive = "??" #Calidad del link sin verificar
|
||||
item_local.action = "play" #Visualizar vídeo
|
||||
item_local.server = "torrent" #Servidor Torrent
|
||||
|
||||
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
||||
|
||||
@@ -601,8 +647,15 @@ def findvideos(item):
|
||||
#Ahora tratamos los Servidores Directos
|
||||
itemlist_t = [] #Itemlist total de enlaces
|
||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||
titles = re.compile('data-TPlayerNv="Opt\d+">.*? <span>(.*?)</span></li>', re.DOTALL).findall(data)
|
||||
urls = re.compile('id="Opt\d+"><iframe[^>]+src="([^"]+)"', re.DOTALL).findall(data)
|
||||
if not item.armagedon:
|
||||
titles = re.compile('data-..layer..="Opt\d+">(?:<span>)?.*?\s?(?:<strong>.*?<\/strong>)?(?:<\/span>)?<span>(.*?)<\/span><\/li>', re.DOTALL).findall(data)
|
||||
urls = re.compile('id="Opt\d+"><iframe[^>]+src="([^"]+)"', re.DOTALL).findall(data)
|
||||
|
||||
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||
if item.videolibray_emergency_urls:
|
||||
item.emergency_urls.append(titles) #Salvamnos matches de los títulos...
|
||||
item.emergency_urls.append(urls) #Salvamnos matches de las urls...
|
||||
return item #... y nos vamos
|
||||
|
||||
#Recorremos la lista de servidores Directos, excluyendo YouTube para trailers
|
||||
if len(titles) == len(urls):
|
||||
@@ -618,8 +671,11 @@ def findvideos(item):
|
||||
title = titles[0]
|
||||
|
||||
if "goo.gl" in urls[i]:
|
||||
urls[i] = httptools.downloadpage(urls[i], follow_redirects=False, only_headers=True)\
|
||||
.headers.get("location", "")
|
||||
try:
|
||||
urls[i] = httptools.downloadpage(urls[i], follow_redirects=False, only_headers=True)\
|
||||
.headers.get("location", "")
|
||||
except:
|
||||
continue
|
||||
|
||||
videourl = servertools.findvideos(urls[i]) #Buscamos la url del vídeo
|
||||
|
||||
@@ -661,6 +717,8 @@ def findvideos(item):
|
||||
item_local.quality = quality #Añadimos la calidad
|
||||
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'): #Añadimos la duración
|
||||
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
||||
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||
item_local.title = '[COLOR yellow][%s][/COLOR] [COLOR yellow][%s][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (alive, server.capitalize(), item_local.quality, str(item_local.language))
|
||||
|
||||
#Preparamos título y calidad, quitamos etiquetas vacías
|
||||
|
||||
@@ -576,3 +576,24 @@ def remove_tags(title):
|
||||
return title_without_tags
|
||||
else:
|
||||
return title
|
||||
|
||||
|
||||
def remove_smb_credential(path):
|
||||
"""
|
||||
devuelve el path sin contraseña/usuario para paths de SMB
|
||||
@param path: ruta
|
||||
@type path: str
|
||||
@return: cadena sin credenciales
|
||||
@rtype: str
|
||||
"""
|
||||
logger.info()
|
||||
|
||||
if not path.startswith("smb://"):
|
||||
return path
|
||||
|
||||
path_without_credentials = scrapertools.find_single_match(path, '^smb:\/\/(?:[^;\n]+;)?(?:[^:@\n]+[:|@])?(?:[^@\n]+@)?(.*?$)')
|
||||
|
||||
if path_without_credentials:
|
||||
return ('smb://' + path_without_credentials)
|
||||
else:
|
||||
return path
|
||||
|
||||
@@ -828,7 +828,7 @@ def caching_torrents(url, torrents_path=None, timeout=10, lookup=False, data_tor
|
||||
return torrents_path #Si hay un error, devolvemos el "path" vacío
|
||||
torrent_file = response.data
|
||||
|
||||
if not scrapertools.find_single_match(torrent_file, '^d\d+:\w+\d+:'): #No es un archivo .torrent (RAR, ZIP, HTML,..., vacío)
|
||||
if not scrapertools.find_single_match(torrent_file, '^d\d+:.*?\d+:'): #No es un archivo .torrent (RAR, ZIP, HTML,..., vacío)
|
||||
logger.error('No es un archivo Torrent: ' + url)
|
||||
torrents_path = ''
|
||||
if data_torrent:
|
||||
|
||||
@@ -1164,7 +1164,7 @@ def post_tmdb_findvideos(item, itemlist):
|
||||
title_gen = '[COLOR yellow]%s [/COLOR][ALT]: %s' % (item.category.capitalize(), title_gen)
|
||||
#elif (config.get_setting("quit_channel_name", "videolibrary") == 1 or item.channel == channel_py) and item.contentChannel == "videolibrary":
|
||||
else:
|
||||
title_gen = '%s: %s' % (item.category.capitalize(), title_gen)
|
||||
title_gen = '[COLOR white]%s: %s' % (item.category.capitalize(), title_gen)
|
||||
|
||||
#Si intervención judicial, alerto!!!
|
||||
if item.intervencion:
|
||||
@@ -1768,7 +1768,6 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
item_back = item.clone()
|
||||
it_back = item.clone()
|
||||
ow_force_param = True
|
||||
channel_enabled = False
|
||||
update_stat = 0
|
||||
delete_stat = 0
|
||||
canal_org_des_list = []
|
||||
@@ -1883,13 +1882,15 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
"""
|
||||
|
||||
try:
|
||||
if item.url: #Viene de actualización de videoteca de series
|
||||
if item.url and not channel_py in item.url and it.emergency_urls: #Viene de actualización de videoteca de series
|
||||
#Analizamos si el canal ya tiene las urls de emergencia: guardar o borrar
|
||||
if (config.get_setting("emergency_urls", item.channel) == 1 and (not item.emergency_urls or (item.emergency_urls and not item.emergency_urls.get(channel_alt, False)))) or (config.get_setting("emergency_urls", item.channel) == 2 and item.emergency_urls.get(channel_alt, False)) or config.get_setting("emergency_urls", item.channel) == 3 or emergency_urls_force:
|
||||
intervencion += ", ('1', '%s', '%s', '', '', '', '', '', '', '', '*', '%s', 'emerg')" % (channel_alt, channel_alt, config.get_setting("emergency_urls", item.channel))
|
||||
|
||||
elif it.library_urls: #Viene de "listar peliculas´"
|
||||
for canal_vid, url_vid in it.library_urls.items(): #Se recorre "item.library_urls" para buscar canales candidatos
|
||||
if canal_vid == channel_py: #Si tiene Newcpt1 en canal, es un error
|
||||
continue
|
||||
canal_vid_alt = "'%s'" % canal_vid
|
||||
if canal_vid_alt in fail_over_list: #Se busca si es un clone de newpct1
|
||||
channel_bis = channel_py
|
||||
@@ -1903,7 +1904,7 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
#Ahora tratamos las webs intervenidas, tranformamos la url, el nfo y borramos los archivos obsoletos de la serie
|
||||
if channel not in intervencion and channel_py_alt not in intervencion and category not in intervencion and channel_alt != 'videolibrary': #lookup
|
||||
if (channel not in intervencion and channel_py_alt not in intervencion and category not in intervencion and channel_alt != 'videolibrary') or not item.infoLabels: #lookup
|
||||
return (item, it, overwrite) #... el canal/clone está listado
|
||||
|
||||
import ast
|
||||
@@ -1915,7 +1916,7 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
for activo, canal_org, canal_des, url_org, url_des, patron1, patron2, patron3, patron4, patron5, content_inc, content_exc, ow_force in intervencion_list:
|
||||
opt = ''
|
||||
#Es esta nuestra entrada?
|
||||
if activo == '1' and (canal_org == channel_alt or canal_org == item.channel or channel_alt == 'videolibrary' or ow_force == 'del' or ow_force == 'emerg'):
|
||||
if activo == '1' and (canal_org == channel_alt or canal_org == item.category.lower() or channel_alt == 'videolibrary' or ow_force == 'del' or ow_force == 'emerg'):
|
||||
|
||||
if ow_force == 'del' or ow_force == 'emerg': #Si es un borrado de estructuras erroneas, hacemos un proceso aparte
|
||||
canal_des_def = canal_des #Si hay canal de sustitución para item.library_urls, lo usamos
|
||||
@@ -1981,10 +1982,15 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
continue
|
||||
if item.contentType in content_exc: #Está el contenido excluido?
|
||||
continue
|
||||
channel_enabled = 0
|
||||
channel_enabled_alt = 1
|
||||
if item.channel != channel_py:
|
||||
channel_enabled = channeltools.is_enabled(channel_alt) #Verificamos que el canal esté inactivo
|
||||
channel_enabled_alt = config.get_setting('enabled', channel_alt)
|
||||
channel_enabled = channel_enabled * channel_enabled_alt #Si está inactivo en algún sitio, tomamos eso
|
||||
try:
|
||||
if channeltools.is_enabled(channel_alt): channel_enabled = 1 #Verificamos que el canal esté inactivo
|
||||
if config.get_setting('enabled', channel_alt) == False: channel_enabled_alt = 0
|
||||
channel_enabled = channel_enabled * channel_enabled_alt #Si está inactivo en algún sitio, tomamos eso
|
||||
except:
|
||||
pass
|
||||
if channel_enabled == 1 and canal_org != canal_des: #Si el canal está activo, puede ser solo...
|
||||
continue #... una intervención que afecte solo a una región
|
||||
if ow_force == 'no' and it.library_urls: #Esta regla solo vale para findvideos...
|
||||
@@ -2012,6 +2018,9 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
url += scrapertools.find_single_match(url_total, patron5) #La aplicamos a url
|
||||
if url:
|
||||
url_total = url #Guardamos la suma de los resultados intermedios
|
||||
if item.channel == channel_py or channel in fail_over_list: #Si es Newpct1...
|
||||
if item.contentType == "tvshow":
|
||||
url_total = re.sub(r'\/\d+\/?$', '', url_total) #parece que con el título encuentra la serie, normalmente...
|
||||
update_stat += 1 #Ya hemos actualizado algo
|
||||
canal_org_des_list += [(canal_org, canal_des, url_total, opt, ow_force)] #salvamos el resultado para su proceso
|
||||
|
||||
@@ -2019,13 +2028,10 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
if (update_stat > 0 and path != False) or item.ow_force == '1':
|
||||
logger.error('** Lista de Actualizaciones a realizar: ' + str(canal_org_des_list))
|
||||
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por todas las "parejas" cambiadas
|
||||
url_total_def = url_total
|
||||
if ow_force_def != 'del' and ow_force_def != 'emerg':
|
||||
url_total_def = url_total
|
||||
if (item.channel == channel_py or channel in fail_over_list): #Si es Newpct1...
|
||||
if item.contentType == "tvshow":
|
||||
url_total_def = re.sub(r'\/\d+\/?$', '', url_total) #parece que con el título encuentra la serie, normalmente...
|
||||
if item.url:
|
||||
item.url = url_total_def #Salvamos la url convertida
|
||||
item.url = url_total #Salvamos la url convertida
|
||||
if item.library_urls:
|
||||
item.library_urls.pop(canal_org_def, None)
|
||||
item.library_urls.update({canal_des_def: url_total})
|
||||
@@ -2048,10 +2054,18 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
||||
#Verificamos que las webs de los canales estén activas antes de borrar los .json, para asegurar que se pueden regenerar
|
||||
i = 0
|
||||
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por las "parejas" a borrar
|
||||
if "magnet:" in url_total or type(url_total) != str: #Si la url es un Magnet, o es una lista, pasamos
|
||||
i += 1
|
||||
continue
|
||||
try:
|
||||
response = httptools.downloadpage(url_total, only_headers=True)
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error('Web ' + canal_des_def.upper() + ' ERROR. Regla no procesada: ' + str(canal_org_des_list[i]))
|
||||
item = item_back.clone() #Restauro las imágenes inciales
|
||||
it = it_back.clone()
|
||||
item.torrent_caching_fail = True #Marcamos el proceso como fallido
|
||||
return (item, it, False)
|
||||
if not response.sucess:
|
||||
logger.error('Web ' + canal_des_def.upper() + ' INACTIVA. Regla no procesada: ' + str(canal_org_des_list[i]))
|
||||
item = item_back.clone() #Restauro las imágenes inciales
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
from platformcode import logger
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
GitHub = 'https://github.com/miketeo/pysmb' #buscar aquí de vez en cuando la última versiónde SMB-pysmb, y actualizar en Alfa
|
||||
vesion_actual_pysmb = '1.1.25' #actualizada el 25/11/2018
|
||||
|
||||
remote = None
|
||||
|
||||
|
||||
def parse_url(url):
|
||||
# logger.info("Url: %s" % url)
|
||||
url = url.strip()
|
||||
import re
|
||||
patron = "^smb://(?:([^;\n]+);)?(?:([^:@\n]+)[:|@])?(?:([^@\n]+)@)?([^/]+)/([^/\n]+)([/]?.*?)$"
|
||||
domain, user, password, server_name, share_name, path = re.compile(patron, re.DOTALL).match(url).groups()
|
||||
|
||||
@@ -29,8 +32,7 @@ def parse_url(url):
|
||||
|
||||
|
||||
def get_server_name_ip(server):
|
||||
import re
|
||||
if re.compile("^\d+.\d+.\d+.\d+$").findall(server):
|
||||
if re.compile("^\d+.\d+.\d+.\d+$").findall(server) or re.compile("^([^\.]+\.(?:[^\.]+\.)?(?:\w+)?)$").findall(server):
|
||||
server_ip = server
|
||||
server_name = None
|
||||
else:
|
||||
|
||||
@@ -1,149 +1,144 @@
|
||||
import logging
|
||||
import random
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
|
||||
from base import NBNS, NotConnectedError
|
||||
from nmb_constants import TYPE_SERVER
|
||||
|
||||
|
||||
class NetBIOS(NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NetBIOS')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if self.broadcast:
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
if listen_port:
|
||||
self.sock.bind(( '', listen_port ))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the underlying and free resources.
|
||||
|
||||
The NetBIOS instance should not be used to perform any operations after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
def write(self, data, ip, port):
|
||||
assert self.sock, 'Socket is already closed'
|
||||
self.sock.sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
return self._pollForNetBIOSPacket(trn_id, timeout)
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNetNameQuery(trn_id, False)
|
||||
self.write(data, ip, port)
|
||||
ret = self._pollForQueryPacket(trn_id, timeout)
|
||||
if ret:
|
||||
return map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret))
|
||||
else:
|
||||
return None
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _pollForNetBIOSPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def _pollForQueryPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
import os, logging, random, socket, time, select
|
||||
from base import NBNS, NotConnectedError
|
||||
from nmb_constants import TYPE_CLIENT, TYPE_SERVER, TYPE_WORKSTATION
|
||||
|
||||
class NetBIOS(NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NetBIOS')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if self.broadcast:
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
if listen_port:
|
||||
self.sock.bind(( '', listen_port ))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the underlying and free resources.
|
||||
|
||||
The NetBIOS instance should not be used to perform any operations after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
def write(self, data, ip, port):
|
||||
assert self.sock, 'Socket is already closed'
|
||||
self.sock.sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
return self._pollForNetBIOSPacket(trn_id, timeout)
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNetNameQuery(trn_id, False)
|
||||
self.write(data, ip, port)
|
||||
ret = self._pollForQueryPacket(trn_id, timeout)
|
||||
if ret:
|
||||
return map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret))
|
||||
else:
|
||||
return None
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _pollForNetBIOSPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def _pollForQueryPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
@@ -1,140 +1,136 @@
|
||||
import logging
|
||||
import random
|
||||
import socket
|
||||
import time
|
||||
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
|
||||
from base import NBNS
|
||||
from nmb_constants import TYPE_SERVER
|
||||
|
||||
IP_QUERY, NAME_QUERY = range(2)
|
||||
|
||||
class NetBIOSTimeout(Exception):
|
||||
"""Raised in NBNSProtocol via Deferred.errback method when queryName method has timeout waiting for reply"""
|
||||
pass
|
||||
|
||||
class NBNSProtocol(DatagramProtocol, NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NBNSProtocol')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NBNSProtocol instance.
|
||||
|
||||
This automatically calls reactor.listenUDP method to start listening for incoming packets, so you **must not** call the listenUDP method again.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.pending_trns = { } # TRN ID -> ( expiry_time, name, Deferred instance )
|
||||
self.transport = reactor.listenUDP(listen_port, self)
|
||||
if self.broadcast:
|
||||
self.transport.getHandle().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
|
||||
def datagramReceived(self, data, from_info):
|
||||
host, port = from_info
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
# pending transaction exists for trn_id - handle it and remove from queue
|
||||
if trn_id in self.pending_trns:
|
||||
_, ip, d = self.pending_trns.pop(trn_id)
|
||||
if ip is NAME_QUERY:
|
||||
# decode as query packet
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
d.callback(ret)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
# We don't use the transport.write method directly as it keeps raising DeprecationWarning for ip='<broadcast>'
|
||||
self.transport.getHandle().sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the returned Deferred instance will be called with a NetBIOSTimeout exception.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of IP addresses in dotted notation (aaa.bbb.ccc.ddd).
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, name, d )
|
||||
return d
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of names of the machine at *ip*.
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNetNameQuery(trn_id)
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
d2 = defer.Deferred()
|
||||
d2.addErrback(d.errback)
|
||||
|
||||
def stripCode(ret):
|
||||
if ret is not None: # got valid response. Somehow the callback is also called when there is an error.
|
||||
d.callback(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
|
||||
|
||||
d2.addCallback(stripCode)
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, NAME_QUERY, d2 )
|
||||
return d
|
||||
|
||||
def stopProtocol(self):
|
||||
DatagramProtocol.stopProtocol(self)
|
||||
|
||||
def cleanupPendingTrns(self):
|
||||
now = time.time()
|
||||
|
||||
# reply should have been received in the past
|
||||
expired = filter(lambda (trn_id, (expiry_time, name, d)): expiry_time < now, self.pending_trns.iteritems())
|
||||
|
||||
# remove expired items from dict + call errback
|
||||
def expire_item(item):
|
||||
trn_id, (expiry_time, name, d) = item
|
||||
|
||||
del self.pending_trns[trn_id]
|
||||
try:
|
||||
d.errback(NetBIOSTimeout(name))
|
||||
except: pass
|
||||
|
||||
map(expire_item, expired)
|
||||
|
||||
if self.transport:
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
|
||||
import os, logging, random, socket, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
from nmb_constants import TYPE_SERVER
|
||||
from base import NBNS
|
||||
|
||||
IP_QUERY, NAME_QUERY = range(2)
|
||||
|
||||
class NetBIOSTimeout(Exception):
|
||||
"""Raised in NBNSProtocol via Deferred.errback method when queryName method has timeout waiting for reply"""
|
||||
pass
|
||||
|
||||
class NBNSProtocol(DatagramProtocol, NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NBNSProtocol')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NBNSProtocol instance.
|
||||
|
||||
This automatically calls reactor.listenUDP method to start listening for incoming packets, so you **must not** call the listenUDP method again.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.pending_trns = { } # TRN ID -> ( expiry_time, name, Deferred instance )
|
||||
self.transport = reactor.listenUDP(listen_port, self)
|
||||
if self.broadcast:
|
||||
self.transport.getHandle().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
|
||||
def datagramReceived(self, data, from_info):
|
||||
host, port = from_info
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
# pending transaction exists for trn_id - handle it and remove from queue
|
||||
if trn_id in self.pending_trns:
|
||||
_, ip, d = self.pending_trns.pop(trn_id)
|
||||
if ip is NAME_QUERY:
|
||||
# decode as query packet
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
d.callback(ret)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
# We don't use the transport.write method directly as it keeps raising DeprecationWarning for ip='<broadcast>'
|
||||
self.transport.getHandle().sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the returned Deferred instance will be called with a NetBIOSTimeout exception.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of IP addresses in dotted notation (aaa.bbb.ccc.ddd).
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, name, d )
|
||||
return d
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of names of the machine at *ip*.
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNetNameQuery(trn_id)
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
d2 = defer.Deferred()
|
||||
d2.addErrback(d.errback)
|
||||
|
||||
def stripCode(ret):
|
||||
if ret is not None: # got valid response. Somehow the callback is also called when there is an error.
|
||||
d.callback(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
|
||||
|
||||
d2.addCallback(stripCode)
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, NAME_QUERY, d2 )
|
||||
return d
|
||||
|
||||
def stopProtocol(self):
|
||||
DatagramProtocol.stopProtocol(self)
|
||||
|
||||
def cleanupPendingTrns(self):
|
||||
now = time.time()
|
||||
|
||||
# reply should have been received in the past
|
||||
expired = filter(lambda (trn_id, (expiry_time, name, d)): expiry_time < now, self.pending_trns.iteritems())
|
||||
|
||||
# remove expired items from dict + call errback
|
||||
def expire_item(item):
|
||||
trn_id, (expiry_time, name, d) = item
|
||||
|
||||
del self.pending_trns[trn_id]
|
||||
try:
|
||||
d.errback(NetBIOSTimeout(name))
|
||||
except: pass
|
||||
|
||||
map(expire_item, expired)
|
||||
|
||||
if self.transport:
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
|
||||
@@ -1,179 +1,181 @@
|
||||
import logging
|
||||
|
||||
from nmb_constants import *
|
||||
from nmb_structs import *
|
||||
from utils import encode_name
|
||||
|
||||
|
||||
class NMBSession:
|
||||
|
||||
log = logging.getLogger('NMB.NMBSession')
|
||||
|
||||
def __init__(self, my_name, remote_name, host_type = TYPE_SERVER, is_direct_tcp = False):
|
||||
self.my_name = my_name.upper()
|
||||
self.remote_name = remote_name.upper()
|
||||
self.host_type = host_type
|
||||
self.data_buf = ''
|
||||
|
||||
if is_direct_tcp:
|
||||
self.data_nmb = DirectTCPSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_DirectTCP
|
||||
else:
|
||||
self.data_nmb = NMBSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_NetBIOS
|
||||
|
||||
#
|
||||
# Overridden Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def onNMBSessionMessage(self, flags, data):
|
||||
pass
|
||||
|
||||
def onNMBSessionOK(self):
|
||||
pass
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def feedData(self, data):
|
||||
self.data_buf = self.data_buf + data
|
||||
|
||||
offset = 0
|
||||
while True:
|
||||
length = self.data_nmb.decode(self.data_buf, offset)
|
||||
if length == 0:
|
||||
break
|
||||
elif length > 0:
|
||||
offset += length
|
||||
self._processNMBSessionPacket(self.data_nmb)
|
||||
else:
|
||||
raise NMBError
|
||||
|
||||
if offset > 0:
|
||||
self.data_buf = self.data_buf[offset:]
|
||||
|
||||
def sendNMBMessage(self, data):
|
||||
self.sendNMBPacket(SESSION_MESSAGE, data)
|
||||
|
||||
def requestNMBSession(self):
|
||||
my_name_encoded = encode_name(self.my_name, TYPE_WORKSTATION)
|
||||
remote_name_encoded = encode_name(self.remote_name, self.host_type)
|
||||
self.sendNMBPacket(SESSION_REQUEST, remote_name_encoded + my_name_encoded)
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _processNMBSessionPacket(self, packet):
|
||||
if packet.type == SESSION_MESSAGE:
|
||||
self.onNMBSessionMessage(packet.flags, packet.data)
|
||||
elif packet.type == POSITIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionOK()
|
||||
elif packet.type == NEGATIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionFailed()
|
||||
else:
|
||||
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
|
||||
|
||||
def _sendNMBPacket_NetBIOS(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x01FFFF
|
||||
flags = 0
|
||||
if length > 0xFFFF:
|
||||
flags |= 0x01
|
||||
length &= 0xFFFF
|
||||
self.write(struct.pack('>BBH', packet_type, flags, length) + data)
|
||||
|
||||
def _sendNMBPacket_DirectTCP(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x00FFFFFF
|
||||
self.write(struct.pack('>I', length) + data)
|
||||
|
||||
|
||||
class NBNS:
|
||||
|
||||
log = logging.getLogger('NMB.NBNS')
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>HHHHHH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
raise NotImplementedError
|
||||
|
||||
def decodePacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
|
||||
if opcode == 0x0000 and is_response:
|
||||
name_len = ord(data[self.HEADER_STRUCT_SIZE])
|
||||
offset = self.HEADER_STRUCT_SIZE+2+name_len+8 # constant 2 for the padding bytes before/after the Name and constant 8 for the Type, Class and TTL fields in the Answer section after the Name
|
||||
record_count = (struct.unpack('>H', data[offset:offset+2])[0]) / 6
|
||||
|
||||
offset += 4 # Constant 4 for the Data Length and Flags field
|
||||
ret = [ ]
|
||||
for i in range(0, record_count):
|
||||
ret.append('%d.%d.%d.%d' % struct.unpack('4B', (data[offset:offset + 4])))
|
||||
offset += 6
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
|
||||
def prepareNameQuery(self, trn_id, name, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0110) or 0x0100, 1, 0, 0, 0)
|
||||
payload = encode_name(name, 0x20) + '\x00\x20\x00\x01'
|
||||
|
||||
return header + payload
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def decodeIPQueryPacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0]
|
||||
|
||||
if numnames > 0:
|
||||
ret = [ ]
|
||||
offset = self.HEADER_STRUCT_SIZE + 45
|
||||
|
||||
for i in range(0, numnames):
|
||||
mynme = data[offset:offset + 15]
|
||||
mynme = mynme.strip()
|
||||
ret.append(( mynme, ord(data[offset+15]) ))
|
||||
offset += 18
|
||||
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def prepareNetNameQuery(self, trn_id, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0010) or 0x0000, 1, 0, 0, 0)
|
||||
payload = encode_name('*', 0) + '\x00\x21\x00\x01'
|
||||
|
||||
return header + payload
|
||||
|
||||
import struct, logging, random
|
||||
from nmb_constants import *
|
||||
from nmb_structs import *
|
||||
from utils import encode_name
|
||||
|
||||
class NMBSession:
|
||||
|
||||
log = logging.getLogger('NMB.NMBSession')
|
||||
|
||||
def __init__(self, my_name, remote_name, host_type = TYPE_SERVER, is_direct_tcp = False):
|
||||
self.my_name = my_name.upper()
|
||||
self.remote_name = remote_name.upper()
|
||||
self.host_type = host_type
|
||||
self.data_buf = ''
|
||||
|
||||
if is_direct_tcp:
|
||||
self.data_nmb = DirectTCPSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_DirectTCP
|
||||
else:
|
||||
self.data_nmb = NMBSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_NetBIOS
|
||||
|
||||
#
|
||||
# Overridden Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def onNMBSessionMessage(self, flags, data):
|
||||
pass
|
||||
|
||||
def onNMBSessionOK(self):
|
||||
pass
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def feedData(self, data):
|
||||
self.data_buf = self.data_buf + data
|
||||
|
||||
offset = 0
|
||||
while True:
|
||||
length = self.data_nmb.decode(self.data_buf, offset)
|
||||
if length == 0:
|
||||
break
|
||||
elif length > 0:
|
||||
offset += length
|
||||
self._processNMBSessionPacket(self.data_nmb)
|
||||
else:
|
||||
raise NMBError
|
||||
|
||||
if offset > 0:
|
||||
self.data_buf = self.data_buf[offset:]
|
||||
|
||||
def sendNMBMessage(self, data):
|
||||
self.sendNMBPacket(SESSION_MESSAGE, data)
|
||||
|
||||
def requestNMBSession(self):
|
||||
my_name_encoded = encode_name(self.my_name, TYPE_WORKSTATION)
|
||||
remote_name_encoded = encode_name(self.remote_name, self.host_type)
|
||||
self.sendNMBPacket(SESSION_REQUEST, remote_name_encoded + my_name_encoded)
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _processNMBSessionPacket(self, packet):
|
||||
if packet.type == SESSION_MESSAGE:
|
||||
self.onNMBSessionMessage(packet.flags, packet.data)
|
||||
elif packet.type == POSITIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionOK()
|
||||
elif packet.type == NEGATIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionFailed()
|
||||
elif packet.type == SESSION_KEEPALIVE:
|
||||
# Discard keepalive packets - [RFC1002]: 5.2.2.1
|
||||
pass
|
||||
else:
|
||||
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
|
||||
|
||||
def _sendNMBPacket_NetBIOS(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x01FFFF
|
||||
flags = 0
|
||||
if length > 0xFFFF:
|
||||
flags |= 0x01
|
||||
length &= 0xFFFF
|
||||
self.write(struct.pack('>BBH', packet_type, flags, length) + data)
|
||||
|
||||
def _sendNMBPacket_DirectTCP(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x00FFFFFF
|
||||
self.write(struct.pack('>I', length) + data)
|
||||
|
||||
|
||||
class NBNS:
|
||||
|
||||
log = logging.getLogger('NMB.NBNS')
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>HHHHHH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
raise NotImplementedError
|
||||
|
||||
def decodePacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
|
||||
if opcode == 0x0000 and is_response:
|
||||
name_len = ord(data[self.HEADER_STRUCT_SIZE])
|
||||
offset = self.HEADER_STRUCT_SIZE+2+name_len+8 # constant 2 for the padding bytes before/after the Name and constant 8 for the Type, Class and TTL fields in the Answer section after the Name
|
||||
record_count = (struct.unpack('>H', data[offset:offset+2])[0]) / 6
|
||||
|
||||
offset += 4 # Constant 4 for the Data Length and Flags field
|
||||
ret = [ ]
|
||||
for i in range(0, record_count):
|
||||
ret.append('%d.%d.%d.%d' % struct.unpack('4B', (data[offset:offset + 4])))
|
||||
offset += 6
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
|
||||
def prepareNameQuery(self, trn_id, name, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0110) or 0x0100, 1, 0, 0, 0)
|
||||
payload = encode_name(name, 0x20) + '\x00\x20\x00\x01'
|
||||
|
||||
return header + payload
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def decodeIPQueryPacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0]
|
||||
|
||||
if numnames > 0:
|
||||
ret = [ ]
|
||||
offset = self.HEADER_STRUCT_SIZE + 45
|
||||
|
||||
for i in range(0, numnames):
|
||||
mynme = data[offset:offset + 15]
|
||||
mynme = mynme.strip()
|
||||
ret.append(( mynme, ord(data[offset+15]) ))
|
||||
offset += 18
|
||||
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def prepareNetNameQuery(self, trn_id, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0010) or 0x0000, 1, 0, 0, 0)
|
||||
payload = encode_name('*', 0) + '\x00\x21\x00\x01'
|
||||
|
||||
return header + payload
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
|
||||
# Default port for NetBIOS name service
|
||||
NETBIOS_NS_PORT = 137
|
||||
|
||||
# Default port for NetBIOS session service
|
||||
NETBIOS_SESSION_PORT = 139
|
||||
|
||||
# Owner Node Type Constants
|
||||
NODE_B = 0x00
|
||||
NODE_P = 0x01
|
||||
NODE_M = 0x10
|
||||
NODE_RESERVED = 0x11
|
||||
|
||||
# Name Type Constants
|
||||
TYPE_UNKNOWN = 0x01
|
||||
TYPE_WORKSTATION = 0x00
|
||||
TYPE_CLIENT = 0x03
|
||||
TYPE_SERVER = 0x20
|
||||
TYPE_DOMAIN_MASTER = 0x1B
|
||||
TYPE_MASTER_BROWSER = 0x1D
|
||||
TYPE_BROWSER = 0x1E
|
||||
|
||||
TYPE_NAMES = { TYPE_UNKNOWN: 'Unknown',
|
||||
TYPE_WORKSTATION: 'Workstation',
|
||||
TYPE_CLIENT: 'Client',
|
||||
TYPE_SERVER: 'Server',
|
||||
TYPE_MASTER_BROWSER: 'Master Browser',
|
||||
TYPE_BROWSER: 'Browser Server',
|
||||
TYPE_DOMAIN_MASTER: 'Domain Master'
|
||||
}
|
||||
|
||||
# Values for Session Packet Type field in Session Packets
|
||||
SESSION_MESSAGE = 0x00
|
||||
SESSION_REQUEST = 0x81
|
||||
POSITIVE_SESSION_RESPONSE = 0x82
|
||||
NEGATIVE_SESSION_RESPONSE = 0x83
|
||||
REGTARGET_SESSION_RESPONSE = 0x84
|
||||
SESSION_KEEPALIVE = 0x85
|
||||
|
||||
# Default port for NetBIOS name service
|
||||
NETBIOS_NS_PORT = 137
|
||||
|
||||
# Default port for NetBIOS session service
|
||||
NETBIOS_SESSION_PORT = 139
|
||||
|
||||
# Owner Node Type Constants
|
||||
NODE_B = 0x00
|
||||
NODE_P = 0x01
|
||||
NODE_M = 0x10
|
||||
NODE_RESERVED = 0x11
|
||||
|
||||
# Name Type Constants
|
||||
TYPE_UNKNOWN = 0x01
|
||||
TYPE_WORKSTATION = 0x00
|
||||
TYPE_CLIENT = 0x03
|
||||
TYPE_SERVER = 0x20
|
||||
TYPE_DOMAIN_MASTER = 0x1B
|
||||
TYPE_MASTER_BROWSER = 0x1D
|
||||
TYPE_BROWSER = 0x1E
|
||||
|
||||
TYPE_NAMES = { TYPE_UNKNOWN: 'Unknown',
|
||||
TYPE_WORKSTATION: 'Workstation',
|
||||
TYPE_CLIENT: 'Client',
|
||||
TYPE_SERVER: 'Server',
|
||||
TYPE_MASTER_BROWSER: 'Master Browser',
|
||||
TYPE_BROWSER: 'Browser Server',
|
||||
TYPE_DOMAIN_MASTER: 'Domain Master'
|
||||
}
|
||||
|
||||
# Values for Session Packet Type field in Session Packets
|
||||
SESSION_MESSAGE = 0x00
|
||||
SESSION_REQUEST = 0x81
|
||||
POSITIVE_SESSION_RESPONSE = 0x82
|
||||
NEGATIVE_SESSION_RESPONSE = 0x83
|
||||
REGTARGET_SESSION_RESPONSE = 0x84
|
||||
SESSION_KEEPALIVE = 0x85
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
|
||||
import struct
|
||||
|
||||
class NMBError(Exception): pass
|
||||
|
||||
|
||||
class NotConnectedError(NMBError):
|
||||
"""
|
||||
Raisd when the underlying NMB connection has been disconnected or not connected yet
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NMBSessionMessage:
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>BBH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.type = 0
|
||||
self.flags = 0
|
||||
self.data = ''
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
self.type, self.flags, length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])
|
||||
|
||||
if self.flags & 0x01:
|
||||
length |= 0x010000
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
|
||||
|
||||
class DirectTCPSessionMessage(NMBSessionMessage):
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>I'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])[0]
|
||||
|
||||
if length >> 24 != 0:
|
||||
raise NMBError("Invalid protocol header for Direct TCP session message")
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
|
||||
import struct
|
||||
|
||||
class NMBError(Exception): pass
|
||||
|
||||
|
||||
class NotConnectedError(NMBError):
|
||||
"""
|
||||
Raisd when the underlying NMB connection has been disconnected or not connected yet
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NMBSessionMessage:
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>BBH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.type = 0
|
||||
self.flags = 0
|
||||
self.data = ''
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
self.type, self.flags, length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])
|
||||
|
||||
if self.flags & 0x01:
|
||||
length |= 0x010000
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
|
||||
|
||||
class DirectTCPSessionMessage(NMBSessionMessage):
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>I'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])[0]
|
||||
|
||||
if length >> 24 != 0:
|
||||
raise NMBError("Invalid protocol header for Direct TCP session message")
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def encode_name(name, type, scope = None):
|
||||
"""
|
||||
Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
|
||||
"""
|
||||
if name == '*':
|
||||
name = name + '\0' * 15
|
||||
elif len(name) > 15:
|
||||
name = name[:15] + chr(type)
|
||||
else:
|
||||
name = string.ljust(name, 15) + chr(type)
|
||||
|
||||
def _do_first_level_encoding(m):
|
||||
s = ord(m.group(0))
|
||||
return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
|
||||
|
||||
encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
|
||||
if scope:
|
||||
encoded_scope = ''
|
||||
for s in string.split(scope, '.'):
|
||||
encoded_scope = encoded_scope + chr(len(s)) + s
|
||||
return encoded_name + encoded_scope + '\0'
|
||||
else:
|
||||
return encoded_name + '\0'
|
||||
|
||||
|
||||
def decode_name(name):
|
||||
name_length = ord(name[0])
|
||||
assert name_length == 32
|
||||
|
||||
def _do_first_level_decoding(m):
|
||||
s = m.group(0)
|
||||
return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
|
||||
|
||||
decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
|
||||
if name[33] == '\0':
|
||||
return 34, decoded_name, ''
|
||||
else:
|
||||
decoded_domain = ''
|
||||
offset = 34
|
||||
while 1:
|
||||
domain_length = ord(name[offset])
|
||||
if domain_length == 0:
|
||||
break
|
||||
decoded_domain = '.' + name[offset:offset + domain_length]
|
||||
offset = offset + domain_length
|
||||
return offset + 1, decoded_name, decoded_domain
|
||||
|
||||
import string, re
|
||||
|
||||
|
||||
def encode_name(name, type, scope = None):
|
||||
"""
|
||||
Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
|
||||
"""
|
||||
if name == '*':
|
||||
name = name + '\0' * 15
|
||||
elif len(name) > 15:
|
||||
name = name[:15] + chr(type)
|
||||
else:
|
||||
name = string.ljust(name, 15) + chr(type)
|
||||
|
||||
def _do_first_level_encoding(m):
|
||||
s = ord(m.group(0))
|
||||
return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
|
||||
|
||||
encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
|
||||
if scope:
|
||||
encoded_scope = ''
|
||||
for s in string.split(scope, '.'):
|
||||
encoded_scope = encoded_scope + chr(len(s)) + s
|
||||
return encoded_name + encoded_scope + '\0'
|
||||
else:
|
||||
return encoded_name + '\0'
|
||||
|
||||
|
||||
def decode_name(name):
|
||||
name_length = ord(name[0])
|
||||
assert name_length == 32
|
||||
|
||||
def _do_first_level_decoding(m):
|
||||
s = m.group(0)
|
||||
return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
|
||||
|
||||
decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
|
||||
if name[33] == '\0':
|
||||
return 34, decoded_name, ''
|
||||
else:
|
||||
decoded_domain = ''
|
||||
offset = 34
|
||||
while 1:
|
||||
domain_length = ord(name[offset])
|
||||
if domain_length == 0:
|
||||
break
|
||||
decoded_domain = '.' + name[offset:offset + domain_length]
|
||||
offset = offset + domain_length
|
||||
return offset + 1, decoded_name, decoded_domain
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +1,97 @@
|
||||
import mimetools
|
||||
import mimetypes
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib2
|
||||
from urllib import (unquote, addinfourl, splitport, splitattr, splituser, splitpasswd)
|
||||
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
USE_NTLM = True
|
||||
MACHINE_NAME = None
|
||||
|
||||
class SMBHandler(urllib2.BaseHandler):
|
||||
|
||||
def smb_open(self, req):
|
||||
global USE_NTLM, MACHINE_NAME
|
||||
|
||||
host = req.get_host()
|
||||
if not host:
|
||||
raise urllib2.URLError('SMB error: no host given')
|
||||
host, port = splitport(host)
|
||||
if port is None:
|
||||
port = 139
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
# username/password handling
|
||||
user, host = splituser(host)
|
||||
if user:
|
||||
user, passwd = splitpasswd(user)
|
||||
else:
|
||||
passwd = None
|
||||
host = unquote(host)
|
||||
user = user or ''
|
||||
|
||||
domain = ''
|
||||
if ';' in user:
|
||||
domain, user = user.split(';', 1)
|
||||
|
||||
passwd = passwd or ''
|
||||
myname = MACHINE_NAME or self.generateClientMachineName()
|
||||
|
||||
n = NetBIOS()
|
||||
names = n.queryIPForName(host)
|
||||
if names:
|
||||
server_name = names[0]
|
||||
else:
|
||||
raise urllib2.URLError('SMB error: Hostname does not reply back with its machine name')
|
||||
|
||||
path, attrs = splitattr(req.get_selector())
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
dirs = path.split('/')
|
||||
dirs = map(unquote, dirs)
|
||||
service, path = dirs[0], '/'.join(dirs[1:])
|
||||
|
||||
try:
|
||||
conn = SMBConnection(user, passwd, myname, server_name, domain=domain, use_ntlm_v2 = USE_NTLM)
|
||||
conn.connect(host, port)
|
||||
|
||||
if req.has_data():
|
||||
data_fp = req.get_data()
|
||||
filelen = conn.storeFile(service, path, data_fp)
|
||||
|
||||
headers = "Content-length: 0\n"
|
||||
fp = StringIO("")
|
||||
else:
|
||||
fp = self.createTempFile()
|
||||
file_attrs, retrlen = conn.retrieveFile(service, path, fp)
|
||||
fp.seek(0)
|
||||
|
||||
headers = ""
|
||||
mtype = mimetypes.guess_type(req.get_full_url())[0]
|
||||
if mtype:
|
||||
headers += "Content-type: %s\n" % mtype
|
||||
if retrlen is not None and retrlen >= 0:
|
||||
headers += "Content-length: %d\n" % retrlen
|
||||
|
||||
sf = StringIO(headers)
|
||||
headers = mimetools.Message(sf)
|
||||
|
||||
return addinfourl(fp, headers, req.get_full_url())
|
||||
except Exception, ex:
|
||||
raise urllib2.URLError, ('smb error: %s' % ex), sys.exc_info()[2]
|
||||
|
||||
def createTempFile(self):
|
||||
return tempfile.TemporaryFile()
|
||||
|
||||
def generateClientMachineName(self):
|
||||
hostname = socket.gethostname()
|
||||
if hostname:
|
||||
return hostname.split('.')[0]
|
||||
return 'SMB%d' % os.getpid()
|
||||
|
||||
import os, sys, socket, urllib2, mimetypes, mimetools, tempfile
|
||||
from urllib import (unwrap, unquote, splittype, splithost, quote,
|
||||
addinfourl, splitport, splittag,
|
||||
splitattr, ftpwrapper, splituser, splitpasswd, splitvalue)
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
USE_NTLM = True
|
||||
MACHINE_NAME = None
|
||||
|
||||
class SMBHandler(urllib2.BaseHandler):
|
||||
|
||||
def smb_open(self, req):
|
||||
global USE_NTLM, MACHINE_NAME
|
||||
|
||||
host = req.get_host()
|
||||
if not host:
|
||||
raise urllib2.URLError('SMB error: no host given')
|
||||
host, port = splitport(host)
|
||||
if port is None:
|
||||
port = 139
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
# username/password handling
|
||||
user, host = splituser(host)
|
||||
if user:
|
||||
user, passwd = splitpasswd(user)
|
||||
else:
|
||||
passwd = None
|
||||
host = unquote(host)
|
||||
user = user or ''
|
||||
|
||||
domain = ''
|
||||
if ';' in user:
|
||||
domain, user = user.split(';', 1)
|
||||
|
||||
passwd = passwd or ''
|
||||
myname = MACHINE_NAME or self.generateClientMachineName()
|
||||
|
||||
n = NetBIOS()
|
||||
names = n.queryIPForName(host)
|
||||
if names:
|
||||
server_name = names[0]
|
||||
else:
|
||||
raise urllib2.URLError('SMB error: Hostname does not reply back with its machine name')
|
||||
|
||||
path, attrs = splitattr(req.get_selector())
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
dirs = path.split('/')
|
||||
dirs = map(unquote, dirs)
|
||||
service, path = dirs[0], '/'.join(dirs[1:])
|
||||
|
||||
try:
|
||||
conn = SMBConnection(user, passwd, myname, server_name, domain=domain, use_ntlm_v2 = USE_NTLM)
|
||||
conn.connect(host, port)
|
||||
|
||||
if req.has_data():
|
||||
data_fp = req.get_data()
|
||||
filelen = conn.storeFile(service, path, data_fp)
|
||||
|
||||
headers = "Content-length: 0\n"
|
||||
fp = StringIO("")
|
||||
else:
|
||||
fp = self.createTempFile()
|
||||
file_attrs, retrlen = conn.retrieveFile(service, path, fp)
|
||||
fp.seek(0)
|
||||
|
||||
headers = ""
|
||||
mtype = mimetypes.guess_type(req.get_full_url())[0]
|
||||
if mtype:
|
||||
headers += "Content-type: %s\n" % mtype
|
||||
if retrlen is not None and retrlen >= 0:
|
||||
headers += "Content-length: %d\n" % retrlen
|
||||
|
||||
sf = StringIO(headers)
|
||||
headers = mimetools.Message(sf)
|
||||
|
||||
return addinfourl(fp, headers, req.get_full_url())
|
||||
except Exception, ex:
|
||||
raise urllib2.URLError, ('smb error: %s' % ex), sys.exc_info()[2]
|
||||
|
||||
def createTempFile(self):
|
||||
return tempfile.TemporaryFile()
|
||||
|
||||
def generateClientMachineName(self):
|
||||
hostname = socket.gethostname()
|
||||
if hostname:
|
||||
return hostname.split('.')[0]
|
||||
return 'SMB%d' % os.getpid()
|
||||
|
||||
@@ -1,398 +1,409 @@
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import ClientFactory, Protocol
|
||||
|
||||
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
from smb_structs import *
|
||||
|
||||
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
||||
|
||||
|
||||
class SMBProtocol(Protocol, SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBProtocol')
|
||||
|
||||
#
|
||||
# Protocol Methods
|
||||
#
|
||||
|
||||
def connectionMade(self):
|
||||
self.factory.instance = self
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.factory.instance == self:
|
||||
self.instance = None
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.feedData(data)
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
self.transport.write(data)
|
||||
|
||||
def onAuthOK(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthOK()
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
def onAuthFailed(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthFailed()
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _cleanupPendingRequests(self):
|
||||
if self.factory.instance == self:
|
||||
now = time.time()
|
||||
to_remove = []
|
||||
for mid, r in self.pending_requests.iteritems():
|
||||
if r.expiry_time < now:
|
||||
try:
|
||||
r.errback(SMBTimeout())
|
||||
except Exception: pass
|
||||
to_remove.append(mid)
|
||||
|
||||
for mid in to_remove:
|
||||
del self.pending_requests[mid]
|
||||
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
|
||||
class SMBProtocolFactory(ClientFactory):
|
||||
|
||||
protocol = SMBProtocol
|
||||
log = logging.getLogger('SMB.SMBFactory')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server.
|
||||
Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``.
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.my_name = my_name
|
||||
self.remote_name = remote_name
|
||||
self.domain = domain
|
||||
self.use_ntlm_v2 = use_ntlm_v2
|
||||
self.sign_options = sign_options
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
|
||||
|
||||
#
|
||||
# Public Property
|
||||
#
|
||||
|
||||
@property
|
||||
def isReady(self):
|
||||
"""A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations."""
|
||||
return bool(self.instance and self.instance.has_authenticated)
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.instance and self.instance.is_using_smb2
|
||||
|
||||
#
|
||||
# Public Methods for Callbacks
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
File operations can proceed when this method has been called.
|
||||
"""
|
||||
pass
|
||||
|
||||
def onAuthFailed(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
|
||||
If you want to retry authenticating from this method,
|
||||
1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``)
|
||||
2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag.
|
||||
3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection
|
||||
"""
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listShares(d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
The default *search* value will query for all read-only, hidden, system, archive files and directories.
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
|
||||
return d
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime*
|
||||
instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0L, -1L, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
|
||||
return d
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged
|
||||
by the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteFiles(service_name, path_file_pattern, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def createDirectory(self, service_name, path):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._createDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def deleteDirectory(self, service_name, path):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def rename(self, service_name, old_path, new_path):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param string data: Data to send to the remote server.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._echo(data, d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def closeConnection(self):
|
||||
"""
|
||||
Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
self.instance.transport.loseConnection()
|
||||
|
||||
#
|
||||
# ClientFactory methods
|
||||
# (Do not touch these unless you know what you are doing)
|
||||
#
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp)
|
||||
p.factory = self
|
||||
return p
|
||||
|
||||
import os, logging, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import ClientFactory, Protocol
|
||||
from smb_constants import *
|
||||
from smb_structs import *
|
||||
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
|
||||
|
||||
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
||||
|
||||
|
||||
class SMBProtocol(Protocol, SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBProtocol')
|
||||
|
||||
#
|
||||
# Protocol Methods
|
||||
#
|
||||
|
||||
def connectionMade(self):
|
||||
self.factory.instance = self
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.factory.instance == self:
|
||||
self.instance = None
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.feedData(data)
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
self.transport.write(data)
|
||||
|
||||
def onAuthOK(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthOK()
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
def onAuthFailed(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthFailed()
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _cleanupPendingRequests(self):
|
||||
if self.factory.instance == self:
|
||||
now = time.time()
|
||||
to_remove = []
|
||||
for mid, r in self.pending_requests.iteritems():
|
||||
if r.expiry_time < now:
|
||||
try:
|
||||
r.errback(SMBTimeout())
|
||||
except Exception: pass
|
||||
to_remove.append(mid)
|
||||
|
||||
for mid in to_remove:
|
||||
del self.pending_requests[mid]
|
||||
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
|
||||
class SMBProtocolFactory(ClientFactory):
|
||||
|
||||
protocol = SMBProtocol
|
||||
log = logging.getLogger('SMB.SMBFactory')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server.
|
||||
Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``.
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.my_name = my_name
|
||||
self.remote_name = remote_name
|
||||
self.domain = domain
|
||||
self.use_ntlm_v2 = use_ntlm_v2
|
||||
self.sign_options = sign_options
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
|
||||
|
||||
#
|
||||
# Public Property
|
||||
#
|
||||
|
||||
@property
|
||||
def isReady(self):
|
||||
"""A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations."""
|
||||
return bool(self.instance and self.instance.has_authenticated)
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.instance and self.instance.is_using_smb2
|
||||
|
||||
#
|
||||
# Public Methods for Callbacks
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
File operations can proceed when this method has been called.
|
||||
"""
|
||||
pass
|
||||
|
||||
def onAuthFailed(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
|
||||
If you want to retry authenticating from this method,
|
||||
1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``)
|
||||
2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag.
|
||||
3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection
|
||||
"""
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listShares(d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||
|
||||
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
|
||||
return d
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime*
|
||||
instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0L, -1L, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
|
||||
return d
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged
|
||||
by the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteFiles(service_name, path_file_pattern, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def createDirectory(self, service_name, path):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._createDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def deleteDirectory(self, service_name, path):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def rename(self, service_name, old_path, new_path):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._echo(data, d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def closeConnection(self):
|
||||
"""
|
||||
Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
self.instance.transport.loseConnection()
|
||||
|
||||
#
|
||||
# ClientFactory methods
|
||||
# (Do not touch these unless you know what you are doing)
|
||||
#
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp)
|
||||
p.factory = self
|
||||
return p
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,249 +1,248 @@
|
||||
import hmac
|
||||
import random
|
||||
import struct
|
||||
|
||||
from utils.pyDes import des
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
hashlib.new('md4')
|
||||
|
||||
def MD4(): return hashlib.new('md4')
|
||||
except ( ImportError, ValueError ):
|
||||
from utils.md4 import MD4
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
def MD5(s): return hashlib.md5(s)
|
||||
except ImportError:
|
||||
import md5
|
||||
def MD5(s): return md5.new(s)
|
||||
|
||||
################
|
||||
# NTLMv2 Methods
|
||||
################
|
||||
|
||||
# The following constants are defined in accordance to [MS-NLMP]: 2.2.2.5
|
||||
|
||||
NTLM_NegotiateUnicode = 0x00000001
|
||||
NTLM_NegotiateOEM = 0x00000002
|
||||
NTLM_RequestTarget = 0x00000004
|
||||
NTLM_Unknown9 = 0x00000008
|
||||
NTLM_NegotiateSign = 0x00000010
|
||||
NTLM_NegotiateSeal = 0x00000020
|
||||
NTLM_NegotiateDatagram = 0x00000040
|
||||
NTLM_NegotiateLanManagerKey = 0x00000080
|
||||
NTLM_Unknown8 = 0x00000100
|
||||
NTLM_NegotiateNTLM = 0x00000200
|
||||
NTLM_NegotiateNTOnly = 0x00000400
|
||||
NTLM_Anonymous = 0x00000800
|
||||
NTLM_NegotiateOemDomainSupplied = 0x00001000
|
||||
NTLM_NegotiateOemWorkstationSupplied = 0x00002000
|
||||
NTLM_Unknown6 = 0x00004000
|
||||
NTLM_NegotiateAlwaysSign = 0x00008000
|
||||
NTLM_TargetTypeDomain = 0x00010000
|
||||
NTLM_TargetTypeServer = 0x00020000
|
||||
NTLM_TargetTypeShare = 0x00040000
|
||||
NTLM_NegotiateExtendedSecurity = 0x00080000
|
||||
NTLM_NegotiateIdentify = 0x00100000
|
||||
NTLM_Unknown5 = 0x00200000
|
||||
NTLM_RequestNonNTSessionKey = 0x00400000
|
||||
NTLM_NegotiateTargetInfo = 0x00800000
|
||||
NTLM_Unknown4 = 0x01000000
|
||||
NTLM_NegotiateVersion = 0x02000000
|
||||
NTLM_Unknown3 = 0x04000000
|
||||
NTLM_Unknown2 = 0x08000000
|
||||
NTLM_Unknown1 = 0x10000000
|
||||
NTLM_Negotiate128 = 0x20000000
|
||||
NTLM_NegotiateKeyExchange = 0x40000000
|
||||
NTLM_Negotiate56 = 0x80000000
|
||||
|
||||
NTLM_FLAGS = NTLM_NegotiateUnicode | \
|
||||
NTLM_RequestTarget | \
|
||||
NTLM_NegotiateNTLM | \
|
||||
NTLM_NegotiateAlwaysSign | \
|
||||
NTLM_NegotiateExtendedSecurity | \
|
||||
NTLM_NegotiateTargetInfo | \
|
||||
NTLM_NegotiateVersion | \
|
||||
NTLM_Negotiate128 | \
|
||||
NTLM_NegotiateKeyExchange | \
|
||||
NTLM_Negotiate56
|
||||
|
||||
def generateNegotiateMessage():
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.1
|
||||
"""
|
||||
s = struct.pack('<8sII8s8s8s',
|
||||
'NTLMSSP\0', 0x01, NTLM_FLAGS,
|
||||
'\0' * 8, # Domain
|
||||
'\0' * 8, # Workstation
|
||||
'\x06\x00\x72\x17\x00\x00\x00\x0F') # Version [MS-NLMP]: 2.2.2.10
|
||||
return s
|
||||
|
||||
|
||||
def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.3
|
||||
"""
|
||||
FORMAT = '<8sIHHIHHIHHIHHIHHIHHII'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
lm_response_length = len(lm_response)
|
||||
lm_response_offset = FORMAT_SIZE
|
||||
nt_response_length = len(nt_response)
|
||||
nt_response_offset = lm_response_offset + lm_response_length
|
||||
domain_unicode = domain.encode('UTF-16LE')
|
||||
domain_length = len(domain_unicode)
|
||||
domain_offset = nt_response_offset + nt_response_length
|
||||
|
||||
padding = ''
|
||||
if domain_offset % 2 != 0:
|
||||
padding = '\0'
|
||||
domain_offset += 1
|
||||
|
||||
user_unicode = user.encode('UTF-16LE')
|
||||
user_length = len(user_unicode)
|
||||
user_offset = domain_offset + domain_length
|
||||
workstation_unicode = workstation.encode('UTF-16LE')
|
||||
workstation_length = len(workstation_unicode)
|
||||
workstation_offset = user_offset + user_length
|
||||
session_key_length = len(session_key)
|
||||
session_key_offset = workstation_offset + workstation_length
|
||||
|
||||
auth_flags = challenge_flags
|
||||
auth_flags &= ~NTLM_NegotiateVersion
|
||||
|
||||
s = struct.pack(FORMAT,
|
||||
'NTLMSSP\0', 0x03,
|
||||
lm_response_length, lm_response_length, lm_response_offset,
|
||||
nt_response_length, nt_response_length, nt_response_offset,
|
||||
domain_length, domain_length, domain_offset,
|
||||
user_length, user_length, user_offset,
|
||||
workstation_length, workstation_length, workstation_offset,
|
||||
session_key_length, session_key_length, session_key_offset,
|
||||
auth_flags)
|
||||
|
||||
return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key
|
||||
|
||||
|
||||
def decodeChallengeMessage(ntlm_data):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.2
|
||||
- [MS-NLMP]: 2.2.2.1 (AV_PAIR)
|
||||
"""
|
||||
FORMAT = '<8sIHHII8s8sHHI'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
signature, message_type, \
|
||||
targetname_len, targetname_maxlen, targetname_offset, \
|
||||
flags, challenge, _, \
|
||||
targetinfo_len, targetinfo_maxlen, targetinfo_offset, \
|
||||
= struct.unpack(FORMAT, ntlm_data[:FORMAT_SIZE])
|
||||
|
||||
assert signature == 'NTLMSSP\0'
|
||||
assert message_type == 0x02
|
||||
|
||||
return challenge, flags, ntlm_data[targetinfo_offset:targetinfo_offset+targetinfo_len]
|
||||
|
||||
|
||||
def generateChallengeResponseV2(password, user, server_challenge, server_info, domain = '', client_challenge = None):
|
||||
client_timestamp = '\0' * 8
|
||||
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
ntlm_hash = d.digest() # The NT password hash
|
||||
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
||||
temp = client_timestamp + client_challenge + domain.encode('UTF-16LE') + server_info
|
||||
|
||||
nt_challenge_response = hmac.new(response_key, server_challenge + temp).digest()
|
||||
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge
|
||||
session_key = hmac.new(response_key, nt_challenge_response).digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
|
||||
################
|
||||
# NTLMv1 Methods
|
||||
################
|
||||
|
||||
def expandDesKey(key):
|
||||
"""Expand the key from a 7-byte password key into a 8-byte DES key"""
|
||||
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
|
||||
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
|
||||
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
|
||||
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
|
||||
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
|
||||
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
|
||||
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
|
||||
s = s + chr((ord(key[6]) & 0x7f) << 1)
|
||||
return s
|
||||
|
||||
|
||||
def DESL(K, D):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.4)
|
||||
- [MS-NLMP]: Section 6
|
||||
"""
|
||||
d1 = des(expandDesKey(K[0:7]))
|
||||
d2 = des(expandDesKey(K[7:14]))
|
||||
d3 = des(expandDesKey(K[14:16] + '\0' * 5))
|
||||
return d1.encrypt(D) + d2.encrypt(D) + d3.encrypt(D)
|
||||
|
||||
|
||||
def generateChallengeResponseV1(password, server_challenge, has_extended_security = False, client_challenge = None):
|
||||
"""
|
||||
Generate a NTLMv1 response
|
||||
|
||||
@param password: User password string
|
||||
@param server_challange: A 8-byte challenge string sent from the server
|
||||
@param has_extended_security: A boolean value indicating whether NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is enabled in the NTLM negFlag
|
||||
@param client_challenge: A 8-byte string representing client challenge. If None, it will be generated randomly if needed by the response generation
|
||||
@return: a tuple of ( NT challenge response string, LM challenge response string )
|
||||
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.3 and 2.8.3.4)
|
||||
- [MS-NLMP]: 3.3.1
|
||||
"""
|
||||
_password = (password.upper() + '\0' * 14)[:14]
|
||||
d1 = des(expandDesKey(_password[:7]))
|
||||
d2 = des(expandDesKey(_password[7:]))
|
||||
lm_response_key = d1.encrypt("KGS!@#$%") + d2.encrypt("KGS!@#$%") # LM password hash. In [MS-NLMP], this is the result of LMOWFv1 function
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
nt_response_key = d.digest() # In [MS-NLMP], this is the result of NTOWFv1 function
|
||||
|
||||
if has_extended_security:
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
lm_challenge_response = client_challenge + '\0'*16
|
||||
nt_challenge_response = DESL(nt_response_key, MD5(server_challenge + client_challenge).digest()[0:8])
|
||||
else:
|
||||
nt_challenge_response = DESL(nt_response_key, server_challenge) # The result after DESL is the NT response
|
||||
lm_challenge_response = DESL(lm_response_key, server_challenge) # The result after DESL is the LM response
|
||||
|
||||
d = MD4()
|
||||
d.update(nt_response_key)
|
||||
session_key = d.digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
import types, hmac, binascii, struct, random
|
||||
from utils.pyDes import des
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
hashlib.new('md4')
|
||||
|
||||
def MD4(): return hashlib.new('md4')
|
||||
except ( ImportError, ValueError ):
|
||||
from utils.md4 import MD4
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
def MD5(s): return hashlib.md5(s)
|
||||
except ImportError:
|
||||
import md5
|
||||
def MD5(s): return md5.new(s)
|
||||
|
||||
################
|
||||
# NTLMv2 Methods
|
||||
################
|
||||
|
||||
# The following constants are defined in accordance to [MS-NLMP]: 2.2.2.5
|
||||
|
||||
NTLM_NegotiateUnicode = 0x00000001
|
||||
NTLM_NegotiateOEM = 0x00000002
|
||||
NTLM_RequestTarget = 0x00000004
|
||||
NTLM_Unknown9 = 0x00000008
|
||||
NTLM_NegotiateSign = 0x00000010
|
||||
NTLM_NegotiateSeal = 0x00000020
|
||||
NTLM_NegotiateDatagram = 0x00000040
|
||||
NTLM_NegotiateLanManagerKey = 0x00000080
|
||||
NTLM_Unknown8 = 0x00000100
|
||||
NTLM_NegotiateNTLM = 0x00000200
|
||||
NTLM_NegotiateNTOnly = 0x00000400
|
||||
NTLM_Anonymous = 0x00000800
|
||||
NTLM_NegotiateOemDomainSupplied = 0x00001000
|
||||
NTLM_NegotiateOemWorkstationSupplied = 0x00002000
|
||||
NTLM_Unknown6 = 0x00004000
|
||||
NTLM_NegotiateAlwaysSign = 0x00008000
|
||||
NTLM_TargetTypeDomain = 0x00010000
|
||||
NTLM_TargetTypeServer = 0x00020000
|
||||
NTLM_TargetTypeShare = 0x00040000
|
||||
NTLM_NegotiateExtendedSecurity = 0x00080000
|
||||
NTLM_NegotiateIdentify = 0x00100000
|
||||
NTLM_Unknown5 = 0x00200000
|
||||
NTLM_RequestNonNTSessionKey = 0x00400000
|
||||
NTLM_NegotiateTargetInfo = 0x00800000
|
||||
NTLM_Unknown4 = 0x01000000
|
||||
NTLM_NegotiateVersion = 0x02000000
|
||||
NTLM_Unknown3 = 0x04000000
|
||||
NTLM_Unknown2 = 0x08000000
|
||||
NTLM_Unknown1 = 0x10000000
|
||||
NTLM_Negotiate128 = 0x20000000
|
||||
NTLM_NegotiateKeyExchange = 0x40000000
|
||||
NTLM_Negotiate56 = 0x80000000
|
||||
|
||||
NTLM_FLAGS = NTLM_NegotiateUnicode | \
|
||||
NTLM_RequestTarget | \
|
||||
NTLM_NegotiateNTLM | \
|
||||
NTLM_NegotiateAlwaysSign | \
|
||||
NTLM_NegotiateExtendedSecurity | \
|
||||
NTLM_NegotiateTargetInfo | \
|
||||
NTLM_NegotiateVersion | \
|
||||
NTLM_Negotiate128 | \
|
||||
NTLM_NegotiateKeyExchange | \
|
||||
NTLM_Negotiate56
|
||||
|
||||
def generateNegotiateMessage():
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.1
|
||||
"""
|
||||
s = struct.pack('<8sII8s8s8s',
|
||||
'NTLMSSP\0', 0x01, NTLM_FLAGS,
|
||||
'\0' * 8, # Domain
|
||||
'\0' * 8, # Workstation
|
||||
'\x06\x00\x72\x17\x00\x00\x00\x0F') # Version [MS-NLMP]: 2.2.2.10
|
||||
return s
|
||||
|
||||
|
||||
def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.3
|
||||
"""
|
||||
FORMAT = '<8sIHHIHHIHHIHHIHHIHHII'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
lm_response_length = len(lm_response)
|
||||
lm_response_offset = FORMAT_SIZE
|
||||
nt_response_length = len(nt_response)
|
||||
nt_response_offset = lm_response_offset + lm_response_length
|
||||
domain_unicode = domain.encode('UTF-16LE')
|
||||
domain_length = len(domain_unicode)
|
||||
domain_offset = nt_response_offset + nt_response_length
|
||||
|
||||
padding = ''
|
||||
if domain_offset % 2 != 0:
|
||||
padding = '\0'
|
||||
domain_offset += 1
|
||||
|
||||
user_unicode = user.encode('UTF-16LE')
|
||||
user_length = len(user_unicode)
|
||||
user_offset = domain_offset + domain_length
|
||||
workstation_unicode = workstation.encode('UTF-16LE')
|
||||
workstation_length = len(workstation_unicode)
|
||||
workstation_offset = user_offset + user_length
|
||||
session_key_length = len(session_key)
|
||||
session_key_offset = workstation_offset + workstation_length
|
||||
|
||||
auth_flags = challenge_flags
|
||||
auth_flags &= ~NTLM_NegotiateVersion
|
||||
|
||||
s = struct.pack(FORMAT,
|
||||
'NTLMSSP\0', 0x03,
|
||||
lm_response_length, lm_response_length, lm_response_offset,
|
||||
nt_response_length, nt_response_length, nt_response_offset,
|
||||
domain_length, domain_length, domain_offset,
|
||||
user_length, user_length, user_offset,
|
||||
workstation_length, workstation_length, workstation_offset,
|
||||
session_key_length, session_key_length, session_key_offset,
|
||||
auth_flags)
|
||||
|
||||
return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key
|
||||
|
||||
|
||||
def decodeChallengeMessage(ntlm_data):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.2
|
||||
- [MS-NLMP]: 2.2.2.1 (AV_PAIR)
|
||||
"""
|
||||
FORMAT = '<8sIHHII8s8sHHI'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
signature, message_type, \
|
||||
targetname_len, targetname_maxlen, targetname_offset, \
|
||||
flags, challenge, _, \
|
||||
targetinfo_len, targetinfo_maxlen, targetinfo_offset, \
|
||||
= struct.unpack(FORMAT, ntlm_data[:FORMAT_SIZE])
|
||||
|
||||
assert signature == 'NTLMSSP\0'
|
||||
assert message_type == 0x02
|
||||
|
||||
return challenge, flags, ntlm_data[targetinfo_offset:targetinfo_offset+targetinfo_len]
|
||||
|
||||
|
||||
def generateChallengeResponseV2(password, user, server_challenge, server_info, domain = '', client_challenge = None):
|
||||
client_timestamp = '\0' * 8
|
||||
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
ntlm_hash = d.digest() # The NT password hash
|
||||
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
||||
temp = '\x01\x01' + '\0'*6 + client_timestamp + client_challenge + '\0'*4 + server_info
|
||||
ntproofstr = hmac.new(response_key, server_challenge + temp).digest()
|
||||
|
||||
nt_challenge_response = ntproofstr + temp
|
||||
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge
|
||||
session_key = hmac.new(response_key, ntproofstr).digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
|
||||
################
|
||||
# NTLMv1 Methods
|
||||
################
|
||||
|
||||
def expandDesKey(key):
|
||||
"""Expand the key from a 7-byte password key into a 8-byte DES key"""
|
||||
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
|
||||
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
|
||||
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
|
||||
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
|
||||
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
|
||||
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
|
||||
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
|
||||
s = s + chr((ord(key[6]) & 0x7f) << 1)
|
||||
return s
|
||||
|
||||
|
||||
def DESL(K, D):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.4)
|
||||
- [MS-NLMP]: Section 6
|
||||
"""
|
||||
d1 = des(expandDesKey(K[0:7]))
|
||||
d2 = des(expandDesKey(K[7:14]))
|
||||
d3 = des(expandDesKey(K[14:16] + '\0' * 5))
|
||||
return d1.encrypt(D) + d2.encrypt(D) + d3.encrypt(D)
|
||||
|
||||
|
||||
def generateChallengeResponseV1(password, server_challenge, has_extended_security = False, client_challenge = None):
|
||||
"""
|
||||
Generate a NTLMv1 response
|
||||
|
||||
@param password: User password string
|
||||
@param server_challange: A 8-byte challenge string sent from the server
|
||||
@param has_extended_security: A boolean value indicating whether NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is enabled in the NTLM negFlag
|
||||
@param client_challenge: A 8-byte string representing client challenge. If None, it will be generated randomly if needed by the response generation
|
||||
@return: a tuple of ( NT challenge response string, LM challenge response string )
|
||||
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.3 and 2.8.3.4)
|
||||
- [MS-NLMP]: 3.3.1
|
||||
"""
|
||||
_password = (password.upper() + '\0' * 14)[:14]
|
||||
d1 = des(expandDesKey(_password[:7]))
|
||||
d2 = des(expandDesKey(_password[7:]))
|
||||
lm_response_key = d1.encrypt("KGS!@#$%") + d2.encrypt("KGS!@#$%") # LM password hash. In [MS-NLMP], this is the result of LMOWFv1 function
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
nt_response_key = d.digest() # In [MS-NLMP], this is the result of NTOWFv1 function
|
||||
|
||||
if has_extended_security:
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
lm_challenge_response = client_challenge + '\0'*16
|
||||
nt_challenge_response = DESL(nt_response_key, MD5(server_challenge + client_challenge).digest()[0:8])
|
||||
else:
|
||||
nt_challenge_response = DESL(nt_response_key, server_challenge) # The result after DESL is the NT response
|
||||
lm_challenge_response = DESL(lm_response_key, server_challenge) # The result after DESL is the LM response
|
||||
|
||||
d = MD4()
|
||||
d.update(nt_response_key)
|
||||
session_key = d.digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
367
plugin.video.alfa/lib/sambatools/smb/security_descriptors.py
Normal file
367
plugin.video.alfa/lib/sambatools/smb/security_descriptors.py
Normal file
@@ -0,0 +1,367 @@
|
||||
"""
|
||||
This module implements security descriptors, and the partial structures
|
||||
used in them, as specified in [MS-DTYP].
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
# Security descriptor control flags
|
||||
# [MS-DTYP]: 2.4.6
|
||||
SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
|
||||
SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
|
||||
SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
|
||||
SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
|
||||
SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
|
||||
SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
|
||||
SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
|
||||
SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
|
||||
SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
|
||||
SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
|
||||
SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
|
||||
SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
|
||||
SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
|
||||
SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
|
||||
SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
|
||||
SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
|
||||
|
||||
# ACE types
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_TYPE_ACCESS_ALLOWED = 0x00
|
||||
ACE_TYPE_ACCESS_DENIED = 0x01
|
||||
ACE_TYPE_SYSTEM_AUDIT = 0x02
|
||||
ACE_TYPE_SYSTEM_ALARM = 0x03
|
||||
ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
|
||||
ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
|
||||
ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
|
||||
ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
|
||||
ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
|
||||
ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
|
||||
|
||||
# ACE flags
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_FLAG_OBJECT_INHERIT = 0x01
|
||||
ACE_FLAG_CONTAINER_INHERIT = 0x02
|
||||
ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
|
||||
ACE_FLAG_INHERIT_ONLY = 0x08
|
||||
ACE_FLAG_INHERITED = 0x10
|
||||
ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
|
||||
ACE_FLAG_FAILED_ACCESS = 0x80
|
||||
|
||||
# Pre-defined well-known SIDs
|
||||
# [MS-DTYP]: 2.4.2.4
|
||||
SID_NULL = "S-1-0-0"
|
||||
SID_EVERYONE = "S-1-1-0"
|
||||
SID_LOCAL = "S-1-2-0"
|
||||
SID_CONSOLE_LOGON = "S-1-2-1"
|
||||
SID_CREATOR_OWNER = "S-1-3-0"
|
||||
SID_CREATOR_GROUP = "S-1-3-1"
|
||||
SID_OWNER_SERVER = "S-1-3-2"
|
||||
SID_GROUP_SERVER = "S-1-3-3"
|
||||
SID_OWNER_RIGHTS = "S-1-3-4"
|
||||
SID_NT_AUTHORITY = "S-1-5"
|
||||
SID_DIALUP = "S-1-5-1"
|
||||
SID_NETWORK = "S-1-5-2"
|
||||
SID_BATCH = "S-1-5-3"
|
||||
SID_INTERACTIVE = "S-1-5-4"
|
||||
SID_SERVICE = "S-1-5-6"
|
||||
SID_ANONYMOUS = "S-1-5-7"
|
||||
SID_PROXY = "S-1-5-8"
|
||||
SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
|
||||
SID_PRINCIPAL_SELF = "S-1-5-10"
|
||||
SID_AUTHENTICATED_USERS = "S-1-5-11"
|
||||
SID_RESTRICTED_CODE = "S-1-5-12"
|
||||
SID_TERMINAL_SERVER_USER = "S-1-5-13"
|
||||
SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
|
||||
SID_THIS_ORGANIZATION = "S-1-5-15"
|
||||
SID_IUSR = "S-1-5-17"
|
||||
SID_LOCAL_SYSTEM = "S-1-5-18"
|
||||
SID_LOCAL_SERVICE = "S-1-5-19"
|
||||
SID_NETWORK_SERVICE = "S-1-5-20"
|
||||
SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
|
||||
SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
|
||||
SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
|
||||
SID_BUILTIN_USERS = "S-1-5-32-545"
|
||||
SID_BUILTIN_GUESTS = "S-1-5-32-546"
|
||||
SID_POWER_USERS = "S-1-5-32-547"
|
||||
SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
|
||||
SID_SERVER_OPERATORS = "S-1-5-32-549"
|
||||
SID_PRINTER_OPERATORS = "S-1-5-32-550"
|
||||
SID_BACKUP_OPERATORS = "S-1-5-32-551"
|
||||
SID_REPLICATOR = "S-1-5-32-552"
|
||||
SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
|
||||
SID_REMOTE_DESKTOP = "S-1-5-32-555"
|
||||
SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
|
||||
SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
|
||||
SID_PERFMON_USERS = "S-1-5-32-558"
|
||||
SID_PERFLOG_USERS = "S-1-5-32-559"
|
||||
SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
|
||||
SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
|
||||
SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
|
||||
SID_IIS_IUSRS = "S-1-5-32-568"
|
||||
SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
|
||||
SID_EVENT_LOG_READERS = "S-1-5-32-573"
|
||||
SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
|
||||
SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
|
||||
SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
|
||||
SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
|
||||
SID_HYPER_V_ADMINS = "S-1-5-32-578"
|
||||
SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
|
||||
SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
|
||||
SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
|
||||
SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
|
||||
SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
|
||||
SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
|
||||
SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
|
||||
SID_NT_SERVICE = "S-1-5-80"
|
||||
SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
|
||||
SID_LOCAL_ACCOUNT = "S-1-5-113"
|
||||
SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
|
||||
SID_OTHER_ORGANIZATION = "S-1-5-1000"
|
||||
SID_ALL_APP_PACKAGES = "S-1-15-2-1"
|
||||
SID_ML_UNTRUSTED = "S-1-16-0"
|
||||
SID_ML_LOW = "S-1-16-4096"
|
||||
SID_ML_MEDIUM = "S-1-16-8192"
|
||||
SID_ML_MEDIUM_PLUS = "S-1-16-8448"
|
||||
SID_ML_HIGH = "S-1-16-12288"
|
||||
SID_ML_SYSTEM = "S-1-16-16384"
|
||||
SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
|
||||
SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
|
||||
SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
|
||||
SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
|
||||
SID_KEY_TRUST_IDENTITY = "S-1-18-4"
|
||||
SID_KEY_PROPERTY_MFA = "S-1-18-5"
|
||||
SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
|
||||
|
||||
|
||||
class SID(object):
|
||||
"""
|
||||
A Windows security identifier. Represents a single principal, such a
|
||||
user or a group, as a sequence of numbers consisting of the revision,
|
||||
identifier authority, and a variable-length list of subauthorities.
|
||||
|
||||
See [MS-DTYP]: 2.4.2
|
||||
"""
|
||||
def __init__(self, revision, identifier_authority, subauthorities):
|
||||
#: Revision, should always be 1.
|
||||
self.revision = revision
|
||||
#: An integer representing the identifier authority.
|
||||
self.identifier_authority = identifier_authority
|
||||
#: A list of integers representing all subauthorities.
|
||||
self.subauthorities = subauthorities
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation, as specified in [MS-DTYP]: 2.4.2.1
|
||||
"""
|
||||
if self.identifier_authority >= 2**32:
|
||||
id_auth = '%#x' % (self.identifier_authority,)
|
||||
else:
|
||||
id_auth = self.identifier_authority
|
||||
auths = [self.revision, id_auth] + self.subauthorities
|
||||
return 'S-' + '-'.join(str(subauth) for subauth in auths)
|
||||
|
||||
def __repr__(self):
|
||||
return 'SID(%r)' % (str(self),)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data, return_tail=False):
|
||||
revision, subauth_count = struct.unpack('<BB', data[:2])
|
||||
identifier_authority = struct.unpack('>Q', '\x00\x00' + data[2:8])[0]
|
||||
subauth_data = data[8:]
|
||||
subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
|
||||
for i in range(subauth_count)]
|
||||
sid = cls(revision, identifier_authority, subauthorities)
|
||||
if return_tail:
|
||||
return sid, subauth_data[4 * subauth_count :]
|
||||
return sid
|
||||
|
||||
|
||||
class ACE(object):
|
||||
"""
|
||||
Represents a single access control entry.
|
||||
|
||||
See [MS-DTYP]: 2.4.4
|
||||
"""
|
||||
HEADER_FORMAT = '<BBH'
|
||||
|
||||
def __init__(self, type_, flags, mask, sid, additional_data):
|
||||
#: An integer representing the type of the ACE. One of the
|
||||
#: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
|
||||
#: from [MS-DTYP] 2.4.4.1.
|
||||
self.type = type_
|
||||
#: An integer bitmask with ACE flags, corresponds to the
|
||||
#: ``AceFlags`` field.
|
||||
self.flags = flags
|
||||
#: An integer representing the ``ACCESS_MASK`` as specified in
|
||||
#: [MS-DTYP] 2.4.3.
|
||||
self.mask = mask
|
||||
#: The :class:`SID` of a trustee.
|
||||
self.sid = sid
|
||||
#: A dictionary of additional fields present in the ACE, depending
|
||||
#: on the type. The following fields can be present:
|
||||
#:
|
||||
#: * ``flags``
|
||||
#: * ``object_type``
|
||||
#: * ``inherited_object_type``
|
||||
#: * ``application_data``
|
||||
#: * ``attribute_data``
|
||||
self.additional_data = additional_data
|
||||
|
||||
def __repr__(self):
|
||||
return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
|
||||
self.type, self.flags, self.mask, self.sid,
|
||||
)
|
||||
|
||||
@property
|
||||
def isInheritOnly(self):
|
||||
"""Convenience property which indicates if this ACE is inherit
|
||||
only, meaning that it doesn't apply to the object itself."""
|
||||
return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header = data[:header_size]
|
||||
type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
body = data[header_size:size]
|
||||
additional_data = {}
|
||||
|
||||
# In all ACE types, the mask immediately follows the header.
|
||||
mask = struct.unpack('<I', body[:4])[0]
|
||||
body = body[4:]
|
||||
|
||||
# All OBJECT-type ACEs contain additional flags, and two GUIDs as
|
||||
# the following fields.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['flags'] = struct.unpack('<I', body[:4])[0]
|
||||
additional_data['object_type'] = body[4:20]
|
||||
additional_data['inherited_object_type'] = body[20:36]
|
||||
body = body[36:]
|
||||
|
||||
# Then the SID in all types.
|
||||
sid, body = SID.from_bytes(body, return_tail=True)
|
||||
|
||||
# CALLBACK-type ACEs (and for some obscure reason,
|
||||
# SYSTEM_AUDIT_OBJECT) have a final tail of application data.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['application_data'] = body
|
||||
|
||||
# SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
|
||||
if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
|
||||
additional_data['attribute_data'] = body
|
||||
|
||||
return cls(type_, flags, mask, sid, additional_data)
|
||||
|
||||
|
||||
class ACL(object):
|
||||
"""
|
||||
Access control list, encapsulating a sequence of access control
|
||||
entries.
|
||||
|
||||
See [MS-DTYP]: 2.4.5
|
||||
"""
|
||||
HEADER_FORMAT = '<BBHHH'
|
||||
|
||||
def __init__(self, revision, aces):
|
||||
#: Integer value of the revision.
|
||||
self.revision = revision
|
||||
#: List of :class:`ACE` instances.
|
||||
self.aces = aces
|
||||
|
||||
def __repr__(self):
|
||||
return "ACL(%r)" % (self.aces,)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
revision = None
|
||||
aces = []
|
||||
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header, remaining = data[:header_size], data[header_size:]
|
||||
revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
for i in range(count):
|
||||
ace_size = struct.unpack('<H', remaining[2:4])[0]
|
||||
ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
|
||||
aces.append(ACE.from_bytes(ace_data))
|
||||
|
||||
return cls(revision, aces)
|
||||
|
||||
|
||||
class SecurityDescriptor(object):
|
||||
"""
|
||||
Represents a security descriptor.
|
||||
|
||||
See [MS-DTYP]: 2.4.6
|
||||
"""
|
||||
|
||||
HEADER_FORMAT = '<BBHIIII'
|
||||
|
||||
def __init__(self, flags, owner, group, dacl, sacl):
|
||||
#: Integer bitmask of control flags. Corresponds to the
|
||||
#: ``Control`` field in [MS-DTYP] 2.4.6.
|
||||
self.flags = flags
|
||||
#: Instance of :class:`SID` representing the owner user.
|
||||
self.owner = owner
|
||||
#: Instance of :class:`SID` representing the owner group.
|
||||
self.group = group
|
||||
#: Instance of :class:`ACL` representing the discretionary access
|
||||
#: control list, which specifies access restrictions of an object.
|
||||
self.dacl = dacl
|
||||
#: Instance of :class:`ACL` representing the system access control
|
||||
#: list, which specifies audit logging of an object.
|
||||
self.sacl = sacl
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
owner = None
|
||||
group = None
|
||||
dacl = None
|
||||
sacl = None
|
||||
|
||||
header = data[:struct.calcsize(cls.HEADER_FORMAT)]
|
||||
(revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
|
||||
dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert revision == 1
|
||||
assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
|
||||
for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
|
||||
assert 0 <= offset < len(data)
|
||||
|
||||
if owner_offset:
|
||||
owner = SID.from_bytes(data[owner_offset:])
|
||||
if group_offset:
|
||||
group = SID.from_bytes(data[group_offset:])
|
||||
if dacl_offset:
|
||||
dacl = ACL.from_bytes(data[dacl_offset:])
|
||||
if sacl_offset:
|
||||
sacl = ACL.from_bytes(data[sacl_offset:])
|
||||
|
||||
return cls(flags, owner, group, dacl, sacl)
|
||||
@@ -1,136 +1,136 @@
|
||||
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
||||
|
||||
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
||||
|
||||
|
||||
class UnsupportedSecurityProvider(Exception): pass
|
||||
class BadSecurityBlobError(Exception): pass
|
||||
|
||||
|
||||
def generateNegotiateSecurityBlob(ntlm_data):
|
||||
mech_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
mech_types = MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
mech_types.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.4.1.311.2.2.10'))
|
||||
|
||||
n = NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
n.setComponentByName('mechTypes', mech_types)
|
||||
n.setComponentByName('mechToken', mech_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenInit', n)
|
||||
|
||||
ct = ContextToken()
|
||||
ct.setComponentByName('thisMech', univ.ObjectIdentifier('1.3.6.1.5.5.2'))
|
||||
ct.setComponentByName('innerContextToken', nt)
|
||||
|
||||
return encoder.encode(ct)
|
||||
|
||||
|
||||
def generateAuthSecurityBlob(ntlm_data):
|
||||
response_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
|
||||
n = NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||
n.setComponentByName('responseToken', response_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenTarg', n)
|
||||
|
||||
return encoder.encode(nt)
|
||||
|
||||
|
||||
def decodeChallengeSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
token = nt.getComponentByName('responseToken')
|
||||
if not token:
|
||||
raise BadSecurityBlobError('NTLMSSP_CHALLENGE security blob does not contain responseToken field')
|
||||
|
||||
provider_oid = nt.getComponentByName('supportedMech')
|
||||
if provider_oid and str(provider_oid) != '1.3.6.1.4.1.311.2.2.10': # This OID is defined in [MS-NLMP]: 1.9
|
||||
raise UnsupportedSecurityProvider('Security provider "%s" is not supported by pysmb' % str(provider_oid))
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result), str(token)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
def decodeAuthResponseSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
#
|
||||
# GSS-API ASN.1 (RFC2478 section 3.2.1)
|
||||
#
|
||||
|
||||
RESULT_ACCEPT_COMPLETED = 0
|
||||
RESULT_ACCEPT_INCOMPLETE = 1
|
||||
RESULT_REJECT = 2
|
||||
|
||||
class NegResultEnumerated(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'accept_completed', 0 ),
|
||||
( 'accept_incomplete', 1 ),
|
||||
( 'reject', 2 )
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2)
|
||||
|
||||
|
||||
class MechTypeList(univ.SequenceOf):
|
||||
componentType = univ.ObjectIdentifier()
|
||||
|
||||
|
||||
class ContextFlags(univ.BitString):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'delegFlag', 0 ),
|
||||
( 'mutualFlag', 1 ),
|
||||
( 'replayFlag', 2 ),
|
||||
( 'sequenceFlag', 3 ),
|
||||
( 'anonFlag', 4 ),
|
||||
( 'confFlag', 5 ),
|
||||
( 'integFlag', 6 )
|
||||
)
|
||||
|
||||
|
||||
class NegTokenInit(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('mechTypes', MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('reqFlags', ContextFlags().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('mechToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegTokenTarg(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('negResult', NegResultEnumerated().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('supportedMech', univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('responseToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegotiationToken(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.NamedType('negTokenTarg', NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||
)
|
||||
|
||||
|
||||
class ContextToken(univ.Sequence):
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('thisMech', univ.ObjectIdentifier()),
|
||||
namedtype.NamedType('innerContextToken', NegotiationToken())
|
||||
)
|
||||
|
||||
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
|
||||
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
||||
|
||||
|
||||
class UnsupportedSecurityProvider(Exception): pass
|
||||
class BadSecurityBlobError(Exception): pass
|
||||
|
||||
|
||||
def generateNegotiateSecurityBlob(ntlm_data):
|
||||
mech_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
mech_types = MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
mech_types.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.4.1.311.2.2.10'))
|
||||
|
||||
n = NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
n.setComponentByName('mechTypes', mech_types)
|
||||
n.setComponentByName('mechToken', mech_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenInit', n)
|
||||
|
||||
ct = ContextToken()
|
||||
ct.setComponentByName('thisMech', univ.ObjectIdentifier('1.3.6.1.5.5.2'))
|
||||
ct.setComponentByName('innerContextToken', nt)
|
||||
|
||||
return encoder.encode(ct)
|
||||
|
||||
|
||||
def generateAuthSecurityBlob(ntlm_data):
|
||||
response_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
|
||||
n = NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||
n.setComponentByName('responseToken', response_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenTarg', n)
|
||||
|
||||
return encoder.encode(nt)
|
||||
|
||||
|
||||
def decodeChallengeSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
token = nt.getComponentByName('responseToken')
|
||||
if not token:
|
||||
raise BadSecurityBlobError('NTLMSSP_CHALLENGE security blob does not contain responseToken field')
|
||||
|
||||
provider_oid = nt.getComponentByName('supportedMech')
|
||||
if provider_oid and str(provider_oid) != '1.3.6.1.4.1.311.2.2.10': # This OID is defined in [MS-NLMP]: 1.9
|
||||
raise UnsupportedSecurityProvider('Security provider "%s" is not supported by pysmb' % str(provider_oid))
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result), str(token)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
def decodeAuthResponseSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
#
|
||||
# GSS-API ASN.1 (RFC2478 section 3.2.1)
|
||||
#
|
||||
|
||||
RESULT_ACCEPT_COMPLETED = 0
|
||||
RESULT_ACCEPT_INCOMPLETE = 1
|
||||
RESULT_REJECT = 2
|
||||
|
||||
class NegResultEnumerated(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'accept_completed', 0 ),
|
||||
( 'accept_incomplete', 1 ),
|
||||
( 'reject', 2 )
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2)
|
||||
|
||||
|
||||
class MechTypeList(univ.SequenceOf):
|
||||
componentType = univ.ObjectIdentifier()
|
||||
|
||||
|
||||
class ContextFlags(univ.BitString):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'delegFlag', 0 ),
|
||||
( 'mutualFlag', 1 ),
|
||||
( 'replayFlag', 2 ),
|
||||
( 'sequenceFlag', 3 ),
|
||||
( 'anonFlag', 4 ),
|
||||
( 'confFlag', 5 ),
|
||||
( 'integFlag', 6 )
|
||||
)
|
||||
|
||||
|
||||
class NegTokenInit(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('mechTypes', MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('reqFlags', ContextFlags().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('mechToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegTokenTarg(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('negResult', NegResultEnumerated().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('supportedMech', univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('responseToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegotiationToken(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.NamedType('negTokenTarg', NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||
)
|
||||
|
||||
|
||||
class ContextToken(univ.Sequence):
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('thisMech', univ.ObjectIdentifier()),
|
||||
namedtype.NamedType('innerContextToken', NegotiationToken())
|
||||
)
|
||||
|
||||
@@ -1,101 +1,115 @@
|
||||
|
||||
# Bitmask for Flags field in SMB2 message header
|
||||
SMB2_FLAGS_SERVER_TO_REDIR = 0x01
|
||||
SMB2_FLAGS_ASYNC_COMMAND = 0x02
|
||||
SMB2_FLAGS_RELATED_OPERATIONS = 0x04
|
||||
SMB2_FLAGS_SIGNED = 0x08
|
||||
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000
|
||||
|
||||
# Values for Command field in SMB2 message header
|
||||
SMB2_COM_NEGOTIATE = 0x0000
|
||||
SMB2_COM_SESSION_SETUP = 0x0001
|
||||
SMB2_COM_LOGOFF = 0x0002
|
||||
SMB2_COM_TREE_CONNECT = 0x0003
|
||||
SMB2_COM_TREE_DISCONNECT = 0x0004
|
||||
SMB2_COM_CREATE = 0x0005
|
||||
SMB2_COM_CLOSE = 0x0006
|
||||
SMB2_COM_FLUSH = 0x0007
|
||||
SMB2_COM_READ = 0x0008
|
||||
SMB2_COM_WRITE = 0x0009
|
||||
SMB2_COM_LOCK = 0x000A
|
||||
SMB2_COM_IOCTL = 0x000B
|
||||
SMB2_COM_CANCEL = 0x000C
|
||||
SMB2_COM_ECHO = 0x000D
|
||||
SMB2_COM_QUERY_DIRECTORY = 0x000E
|
||||
SMB2_COM_CHANGE_NOTIFY = 0x000F
|
||||
SMB2_COM_QUERY_INFO = 0x0010
|
||||
SMB2_COM_SET_INFO = 0x0011
|
||||
SMB2_COM_OPLOCK_BREAK = 0x0012
|
||||
|
||||
SMB2_COMMAND_NAMES = {
|
||||
0x0000: 'SMB2_COM_NEGOTIATE',
|
||||
0x0001: 'SMB2_COM_SESSION_SETUP',
|
||||
0x0002: 'SMB2_COM_LOGOFF',
|
||||
0x0003: 'SMB2_COM_TREE_CONNECT',
|
||||
0x0004: 'SMB2_COM_TREE_DISCONNECT',
|
||||
0x0005: 'SMB2_COM_CREATE',
|
||||
0x0006: 'SMB2_COM_CLOSE',
|
||||
0x0007: 'SMB2_COM_FLUSH',
|
||||
0x0008: 'SMB2_COM_READ',
|
||||
0x0009: 'SMB2_COM_WRITE',
|
||||
0x000A: 'SMB2_COM_LOCK',
|
||||
0x000B: 'SMB2_COM_IOCTL',
|
||||
0x000C: 'SMB2_COM_CANCEL',
|
||||
0x000D: 'SMB2_COM_ECHO',
|
||||
0x000E: 'SMB2_COM_QUERY_DIRECTORY',
|
||||
0x000F: 'SMB2_COM_CHANGE_NOTIFY',
|
||||
0x0010: 'SMB2_COM_QUERY_INFO',
|
||||
0x0011: 'SMB2_COM_SET_INFO',
|
||||
0x0012: 'SMB2_COM_OPLOCK_BREAK',
|
||||
}
|
||||
|
||||
# Values for dialect_revision field in SMB2NegotiateResponse class
|
||||
SMB2_DIALECT_2 = 0x0202
|
||||
SMB2_DIALECT_21 = 0x0210
|
||||
SMB2_DIALECT_2ALL = 0x02FF
|
||||
|
||||
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
||||
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002
|
||||
|
||||
# Values for ShareType field in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_TYPE_DISK = 0x01
|
||||
SMB2_SHARE_TYPE_PIPE = 0x02
|
||||
SMB2_SHARE_TYPE_PRINTER = 0x03
|
||||
|
||||
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_CAP_DFS = 0x0008
|
||||
|
||||
# Values for OpLockLevel field in SMB2CreateRequest class
|
||||
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
||||
SMB2_OPLOCK_LEVEL_II = 0x01
|
||||
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
|
||||
SMB2_OPLOCK_LEVEL_BATCH = 0x09
|
||||
SMB2_OPLOCK_LEVEL_LEASE = 0xFF
|
||||
|
||||
# Values for FileAttributes field in SMB2CreateRequest class
|
||||
# The values are defined in [MS-FSCC] 2.6
|
||||
SMB2_FILE_ATTRIBUTE_ARCHIVE = 0x0020
|
||||
SMB2_FILE_ATTRIBUTE_COMPRESSED = 0x0800
|
||||
SMB2_FILE_ATTRIBUTE_DIRECTORY = 0x0010
|
||||
SMB2_FILE_ATTRIBUTE_ENCRYPTED = 0x4000
|
||||
SMB2_FILE_ATTRIBUTE_HIDDEN = 0x0002
|
||||
SMB2_FILE_ATTRIBUTE_NORMAL = 0x0080
|
||||
SMB2_FILE_ATTRIBUTE_NOTINDEXED = 0x2000
|
||||
SMB2_FILE_ATTRIBUTE_OFFLINE = 0x1000
|
||||
SMB2_FILE_ATTRIBUTE_READONLY = 0x0001
|
||||
SMB2_FILE_ATTRIBUTE_SPARSE = 0x0200
|
||||
SMB2_FILE_ATTRIBUTE_SYSTEM = 0x0004
|
||||
SMB2_FILE_ATTRIBUTE_TEMPORARY = 0x0100
|
||||
|
||||
# Values for CreateAction field in SMB2CreateResponse class
|
||||
SMB2_FILE_SUPERCEDED = 0x00
|
||||
SMB2_FILE_OPENED = 0x01
|
||||
SMB2_FILE_CREATED = 0x02
|
||||
SMB2_FILE_OVERWRITTEN = 0x03
|
||||
|
||||
# Values for InfoType field in SMB2QueryInfoRequest class
|
||||
SMB2_INFO_FILE = 0x01
|
||||
SMB2_INFO_FILESYSTEM = 0x02
|
||||
SMB2_INFO_SECURITY = 0x03
|
||||
SMB2_INFO_QUOTA = 0x04
|
||||
|
||||
# Bitmask for Flags field in SMB2 message header
|
||||
SMB2_FLAGS_SERVER_TO_REDIR = 0x01
|
||||
SMB2_FLAGS_ASYNC_COMMAND = 0x02
|
||||
SMB2_FLAGS_RELATED_OPERATIONS = 0x04
|
||||
SMB2_FLAGS_SIGNED = 0x08
|
||||
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000
|
||||
|
||||
# Values for Command field in SMB2 message header
|
||||
SMB2_COM_NEGOTIATE = 0x0000
|
||||
SMB2_COM_SESSION_SETUP = 0x0001
|
||||
SMB2_COM_LOGOFF = 0x0002
|
||||
SMB2_COM_TREE_CONNECT = 0x0003
|
||||
SMB2_COM_TREE_DISCONNECT = 0x0004
|
||||
SMB2_COM_CREATE = 0x0005
|
||||
SMB2_COM_CLOSE = 0x0006
|
||||
SMB2_COM_FLUSH = 0x0007
|
||||
SMB2_COM_READ = 0x0008
|
||||
SMB2_COM_WRITE = 0x0009
|
||||
SMB2_COM_LOCK = 0x000A
|
||||
SMB2_COM_IOCTL = 0x000B
|
||||
SMB2_COM_CANCEL = 0x000C
|
||||
SMB2_COM_ECHO = 0x000D
|
||||
SMB2_COM_QUERY_DIRECTORY = 0x000E
|
||||
SMB2_COM_CHANGE_NOTIFY = 0x000F
|
||||
SMB2_COM_QUERY_INFO = 0x0010
|
||||
SMB2_COM_SET_INFO = 0x0011
|
||||
SMB2_COM_OPLOCK_BREAK = 0x0012
|
||||
|
||||
SMB2_COMMAND_NAMES = {
|
||||
0x0000: 'SMB2_COM_NEGOTIATE',
|
||||
0x0001: 'SMB2_COM_SESSION_SETUP',
|
||||
0x0002: 'SMB2_COM_LOGOFF',
|
||||
0x0003: 'SMB2_COM_TREE_CONNECT',
|
||||
0x0004: 'SMB2_COM_TREE_DISCONNECT',
|
||||
0x0005: 'SMB2_COM_CREATE',
|
||||
0x0006: 'SMB2_COM_CLOSE',
|
||||
0x0007: 'SMB2_COM_FLUSH',
|
||||
0x0008: 'SMB2_COM_READ',
|
||||
0x0009: 'SMB2_COM_WRITE',
|
||||
0x000A: 'SMB2_COM_LOCK',
|
||||
0x000B: 'SMB2_COM_IOCTL',
|
||||
0x000C: 'SMB2_COM_CANCEL',
|
||||
0x000D: 'SMB2_COM_ECHO',
|
||||
0x000E: 'SMB2_COM_QUERY_DIRECTORY',
|
||||
0x000F: 'SMB2_COM_CHANGE_NOTIFY',
|
||||
0x0010: 'SMB2_COM_QUERY_INFO',
|
||||
0x0011: 'SMB2_COM_SET_INFO',
|
||||
0x0012: 'SMB2_COM_OPLOCK_BREAK',
|
||||
}
|
||||
|
||||
# Values for dialect_revision field in SMB2NegotiateResponse class
|
||||
SMB2_DIALECT_2 = 0x0202 # 2.0.2 - First SMB2 version
|
||||
SMB2_DIALECT_21 = 0x0210 # 2.1 - Windows 7
|
||||
SMB2_DIALET_30 = 0x0300 # 3.0 - Windows 8
|
||||
SMB2_DIALECT_302 = 0x0302 # 3.0.2 - Windows 8.1
|
||||
SMB2_DIALECT_311 = 0x0311 # 3.1.1 - Windows 10
|
||||
SMB2_DIALECT_2ALL = 0x02FF # Wildcard (for negotiation only)
|
||||
|
||||
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
||||
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002
|
||||
|
||||
# Values for ShareType field in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_TYPE_DISK = 0x01
|
||||
SMB2_SHARE_TYPE_PIPE = 0x02
|
||||
SMB2_SHARE_TYPE_PRINTER = 0x03
|
||||
|
||||
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_CAP_DFS = 0x0008
|
||||
|
||||
|
||||
# SMB 2.1 / 3 Capabilities flags
|
||||
SMB2_GLOBAL_CAP_DFS = 0x01
|
||||
SMB2_GLOBAL_CAP_LEASING = 0x02
|
||||
SMB2_GLOBAL_CAP_LARGE_MTU = 0x04
|
||||
SMB2_GLOBAL_CAP_MULTI_CHANNEL = 0x08
|
||||
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES = 0x10
|
||||
SMB2_GLOBAL_CAP_DIRECTORY_LEASING = 0x20
|
||||
SMB2_GLOBAL_CAP_ENCRYPTION = 0x40
|
||||
|
||||
|
||||
# Values for OpLockLevel field in SMB2CreateRequest class
|
||||
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
||||
SMB2_OPLOCK_LEVEL_II = 0x01
|
||||
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
|
||||
SMB2_OPLOCK_LEVEL_BATCH = 0x09
|
||||
SMB2_OPLOCK_LEVEL_LEASE = 0xFF
|
||||
|
||||
# Values for FileAttributes field in SMB2CreateRequest class
|
||||
# The values are defined in [MS-FSCC] 2.6
|
||||
SMB2_FILE_ATTRIBUTE_ARCHIVE = 0x0020
|
||||
SMB2_FILE_ATTRIBUTE_COMPRESSED = 0x0800
|
||||
SMB2_FILE_ATTRIBUTE_DIRECTORY = 0x0010
|
||||
SMB2_FILE_ATTRIBUTE_ENCRYPTED = 0x4000
|
||||
SMB2_FILE_ATTRIBUTE_HIDDEN = 0x0002
|
||||
SMB2_FILE_ATTRIBUTE_NORMAL = 0x0080
|
||||
SMB2_FILE_ATTRIBUTE_NOTINDEXED = 0x2000
|
||||
SMB2_FILE_ATTRIBUTE_OFFLINE = 0x1000
|
||||
SMB2_FILE_ATTRIBUTE_READONLY = 0x0001
|
||||
SMB2_FILE_ATTRIBUTE_SPARSE = 0x0200
|
||||
SMB2_FILE_ATTRIBUTE_SYSTEM = 0x0004
|
||||
SMB2_FILE_ATTRIBUTE_TEMPORARY = 0x0100
|
||||
|
||||
# Values for CreateAction field in SMB2CreateResponse class
|
||||
SMB2_FILE_SUPERCEDED = 0x00
|
||||
SMB2_FILE_OPENED = 0x01
|
||||
SMB2_FILE_CREATED = 0x02
|
||||
SMB2_FILE_OVERWRITTEN = 0x03
|
||||
|
||||
# Values for InfoType field in SMB2QueryInfoRequest class
|
||||
SMB2_INFO_FILE = 0x01
|
||||
SMB2_INFO_FILESYSTEM = 0x02
|
||||
SMB2_INFO_SECURITY = 0x03
|
||||
SMB2_INFO_QUOTA = 0x04
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,239 +1,257 @@
|
||||
|
||||
# Values for Command field in SMB message header
|
||||
SMB_COM_CREATE_DIRECTORY = 0x00
|
||||
SMB_COM_DELETE_DIRECTORY = 0x01
|
||||
SMB_COM_CLOSE = 0x04
|
||||
SMB_COM_DELETE = 0x06
|
||||
SMB_COM_RENAME = 0x07
|
||||
SMB_COM_TRANSACTION = 0x25
|
||||
SMB_COM_ECHO = 0x2B
|
||||
SMB_COM_OPEN_ANDX = 0x2D
|
||||
SMB_COM_READ_ANDX = 0x2E
|
||||
SMB_COM_WRITE_ANDX = 0x2F
|
||||
SMB_COM_TRANSACTION2 = 0x32
|
||||
SMB_COM_NEGOTIATE = 0x72
|
||||
SMB_COM_SESSION_SETUP_ANDX = 0x73
|
||||
SMB_COM_TREE_CONNECT_ANDX = 0x75
|
||||
SMB_COM_NT_TRANSACT = 0xA0
|
||||
SMB_COM_NT_CREATE_ANDX = 0xA2
|
||||
|
||||
SMB_COMMAND_NAMES = {
|
||||
0x00: 'SMB_COM_CREATE_DIRECTORY',
|
||||
0x01: 'SMB_COM_DELETE_DIRECTORY',
|
||||
0x04: 'SMB_COM_CLOSE',
|
||||
0x06: 'SMB_COM_DELETE',
|
||||
0x25: 'SMB_COM_TRANSACTION',
|
||||
0x2B: 'SMB_COM_ECHO',
|
||||
0x2D: 'SMB_COM_OPEN_ANDX',
|
||||
0x2E: 'SMB_COM_READ_ANDX',
|
||||
0x2F: 'SMB_COM_WRITE_ANDX',
|
||||
0x32: 'SMB_COM_TRANSACTION2',
|
||||
0x72: 'SMB_COM_NEGOTIATE',
|
||||
0x73: 'SMB_COM_SESSION_SETUP_ANDX',
|
||||
0x75: 'SMB_COM_TREE_CONNECT_ANDX',
|
||||
0xA0: 'SMB_COM_NT_TRANSACT',
|
||||
0xA2: 'SMB_COM_NT_CREATE_ANDX',
|
||||
}
|
||||
|
||||
# Bitmask for Flags field in SMB message header
|
||||
SMB_FLAGS_LOCK_AND_READ_OK = 0x01 # LANMAN1.0
|
||||
SMB_FLAGS_BUF_AVAIL = 0x02 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CASE_INSENSITIVE = 0x08 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CANONICALIZED_PATHS = 0x10 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPLOCK = 0x20 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPBATCH = 0x40 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_REPLY = 0x80 # LANMAN1.0
|
||||
|
||||
# Bitmask for Flags2 field in SMB message header
|
||||
SMB_FLAGS2_LONG_NAMES = 0x0001 # LANMAN2.0
|
||||
SMB_FLAGS2_EAS = 0x0002 # LANMAN1.2
|
||||
SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 # NT LANMAN
|
||||
SMB_FLAGS2_IS_LONG_NAME = 0x0040 # NT LANMAN
|
||||
SMB_FLAGS2_DFS = 0x1000 # NT LANMAN
|
||||
SMB_FLAGS2_REPARSE_PATH = 0x0400 #
|
||||
SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 #
|
||||
SMB_FLAGS2_PAGING_IO = 0x2000 # NT LANMAN
|
||||
SMB_FLAGS2_NT_STATUS = 0x4000 # NT LANMAN
|
||||
SMB_FLAGS2_UNICODE = 0x8000 # NT LANMAN
|
||||
|
||||
# Bitmask for Capabilities field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
# [MS-SMB]: 2.2.4.5.2.1 (Capabilities field)
|
||||
CAP_RAW_MODE = 0x01
|
||||
CAP_MPX_MODE = 0x02
|
||||
CAP_UNICODE = 0x04
|
||||
CAP_LARGE_FILES = 0x08
|
||||
CAP_NT_SMBS = 0x10
|
||||
CAP_RPC_REMOTE_APIS = 0x20
|
||||
CAP_STATUS32 = 0x40
|
||||
CAP_LEVEL_II_OPLOCKS = 0x80
|
||||
CAP_LOCK_AND_READ = 0x0100
|
||||
CAP_NT_FIND = 0x0200
|
||||
CAP_DFS = 0x1000
|
||||
CAP_INFOLEVEL_PASSTHRU = 0x2000
|
||||
CAP_LARGE_READX = 0x4000
|
||||
CAP_LARGE_WRITEX = 0x8000
|
||||
CAP_LWIO = 0x010000
|
||||
CAP_UNIX = 0x800000
|
||||
CAP_COMPRESSED = 0x02000000
|
||||
CAP_DYNAMIC_REAUTH = 0x20000000
|
||||
CAP_PERSISTENT_HANDLES = 0x40000000
|
||||
CAP_EXTENDED_SECURITY = 0x80000000
|
||||
|
||||
# Value for Action field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
SMB_SETUP_GUEST = 0x0001
|
||||
SMB_SETUP_USE_LANMAN_KEY = 0X0002
|
||||
|
||||
# Bitmask for SecurityMode field in SMB_COM_NEGOTIATE response
|
||||
NEGOTIATE_USER_SECURITY = 0x01
|
||||
NEGOTIATE_ENCRYPT_PASSWORDS = 0x02
|
||||
NEGOTIATE_SECURITY_SIGNATURES_ENABLE = 0x04
|
||||
NEGOTIATE_SECURITY_SIGNATURES_REQUIRE = 0x08
|
||||
|
||||
# Available constants for Service field in SMB_COM_TREE_CONNECT_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.55.1 (Service field)
|
||||
SERVICE_PRINTER = 'LPT1:'
|
||||
SERVICE_NAMED_PIPE = 'IPC'
|
||||
SERVICE_COMM = 'COMM'
|
||||
SERVICE_ANY = '?????'
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
NT_CREATE_REQUEST_OPLOCK = 0x02
|
||||
NT_CREATE_REQUEST_OPBATCH = 0x04
|
||||
NT_CREATE_OPEN_TARGET_DIR = 0x08
|
||||
NT_CREATE_REQUEST_EXTENDED_RESPONSE = 0x10 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Bitmask for DesiredAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# Also used for MaximalAccess field in SMB2TreeConnectResponse class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13.1.1
|
||||
FILE_READ_DATA = 0x01
|
||||
FILE_WRITE_DATA = 0X02
|
||||
FILE_APPEND_DATA = 0x04
|
||||
FILE_READ_EA = 0x08
|
||||
FILE_WRITE_EA = 0x10
|
||||
FILE_EXECUTE = 0x20
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
FILE_WRITE_ATTRIBUTES = 0x0100
|
||||
DELETE = 0x010000
|
||||
READ_CONTROL = 0x020000
|
||||
WRITE_DAC = 0x040000
|
||||
WRITE_OWNER = 0x080000
|
||||
SYNCHRONIZE = 0x100000
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
GENERIC_ALL = 0x10000000
|
||||
GENERIC_EXECUTE = 0x20000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
GENERIC_READ = 0x80000000L
|
||||
|
||||
# SMB_EXT_FILE_ATTR bitmask ([MS-CIFS]: 2.2.1.2.3)
|
||||
# Includes extensions defined in [MS-SMB] 2.2.1.2.1
|
||||
# Bitmask for FileAttributes field in SMB_COM_NT_CREATE_ANDX request ([MS-CIFS]: 2.2.4.64.1)
|
||||
# Also used for FileAttributes field in SMB2CreateRequest class ([MS-SMB2]: 2.2.13)
|
||||
ATTR_READONLY = 0x01
|
||||
ATTR_HIDDEN = 0x02
|
||||
ATTR_SYSTEM = 0x04
|
||||
ATTR_DIRECTORY = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
ATTR_NORMAL = 0x80
|
||||
ATTR_TEMPORARY = 0x0100
|
||||
ATTR_SPARSE = 0x0200
|
||||
ATTR_REPARSE_POINT = 0x0400
|
||||
ATTR_COMPRESSED = 0x0800
|
||||
ATTR_OFFLINE = 0x1000
|
||||
ATTR_NOT_CONTENT_INDEXED = 0x2000
|
||||
ATTR_ENCRYPTED = 0x4000
|
||||
POSIX_SEMANTICS = 0x01000000
|
||||
BACKUP_SEMANTICS = 0x02000000
|
||||
DELETE_ON_CLOSE = 0x04000000
|
||||
SEQUENTIAL_SCAN = 0x08000000
|
||||
RANDOM_ACCESS = 0x10000000
|
||||
NO_BUFFERING = 0x20000000
|
||||
WRITE_THROUGH = 0x80000000
|
||||
|
||||
# Bitmask for ShareAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SHARE_NONE = 0x00
|
||||
FILE_SHARE_READ = 0x01
|
||||
FILE_SHARE_WRITE = 0x02
|
||||
FILE_SHARE_DELETE = 0x04
|
||||
|
||||
# Values for CreateDisposition field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SUPERSEDE = 0x00
|
||||
FILE_OPEN = 0x01
|
||||
FILE_CREATE = 0x02
|
||||
FILE_OPEN_IF = 0x03
|
||||
FILE_OVERWRITE = 0x04
|
||||
FILE_OVERWRITE_IF = 0x05
|
||||
|
||||
# Bitmask for CreateOptions field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_DIRECTORY_FILE = 0x01
|
||||
FILE_WRITE_THROUGH = 0x02
|
||||
FILE_SEQUENTIAL_ONLY = 0x04
|
||||
FILE_NO_INTERMEDIATE_BUFFERING = 0x08
|
||||
FILE_SYNCHRONOUS_IO_ALERT = 0x10
|
||||
FILE_SYNCHRONOUS_IO_NONALERT = 0x20
|
||||
FILE_NON_DIRECTORY_FILE = 0x40
|
||||
FILE_CREATE_TREE_CONNECTION = 0x80
|
||||
FILE_COMPLETE_IF_OPLOCKED = 0x0100
|
||||
FILE_NO_EA_KNOWLEDGE = 0x0200
|
||||
FILE_OPEN_FOR_RECOVERY = 0x0400
|
||||
FILE_RANDOM_ACCESS = 0x0800
|
||||
FILE_DELETE_ON_CLOSE = 0x1000
|
||||
FILE_OPEN_BY_FILE_ID = 0x2000
|
||||
FILE_OPEN_FOR_BACKUP_INTENT = 0x4000
|
||||
FILE_NO_COMPRESSION = 0x8000
|
||||
FILE_RESERVE_OPFILTER = 0x100000
|
||||
FILE_OPEN_NO_RECALL = 0x400000
|
||||
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x800000
|
||||
|
||||
# Values for ImpersonationLevel field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# For interpretations about these values, refer to [MS-WSO] and [MSDN-IMPERS]
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
SEC_ANONYMOUS = 0x00
|
||||
SEC_IDENTIFY = 0x01
|
||||
SEC_IMPERSONATE = 0x02
|
||||
SEC_DELEGATION = 0x03 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Values for SecurityFlags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
SMB_SECURITY_CONTEXT_TRACKING = 0x01
|
||||
SMB_SECURITY_EFFECTIVE_ONLY = 0x02
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_TRANSACTION2 request
|
||||
# [MS-CIFS]: 2.2.4.46.1
|
||||
DISCONNECT_TID = 0x01
|
||||
NO_RESPONSE = 0x02
|
||||
|
||||
# Bitmask for basic file attributes
|
||||
# [MS-CIFS]: 2.2.1.2.4
|
||||
SMB_FILE_ATTRIBUTE_NORMAL = 0x00
|
||||
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
||||
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
||||
SMB_FILE_ATTRIBUTE_VOLUME = 0x08
|
||||
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
||||
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
||||
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
||||
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
||||
SMB_SEARCH_ATTRIBUTE_DIRECTORY = 0x1000
|
||||
SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
|
||||
|
||||
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
||||
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
||||
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
||||
|
||||
# Values for Command field in SMB message header
|
||||
SMB_COM_CREATE_DIRECTORY = 0x00
|
||||
SMB_COM_DELETE_DIRECTORY = 0x01
|
||||
SMB_COM_CLOSE = 0x04
|
||||
SMB_COM_DELETE = 0x06
|
||||
SMB_COM_RENAME = 0x07
|
||||
SMB_COM_TRANSACTION = 0x25
|
||||
SMB_COM_ECHO = 0x2B
|
||||
SMB_COM_OPEN_ANDX = 0x2D
|
||||
SMB_COM_READ_ANDX = 0x2E
|
||||
SMB_COM_WRITE_ANDX = 0x2F
|
||||
SMB_COM_TRANSACTION2 = 0x32
|
||||
SMB_COM_NEGOTIATE = 0x72
|
||||
SMB_COM_SESSION_SETUP_ANDX = 0x73
|
||||
SMB_COM_TREE_CONNECT_ANDX = 0x75
|
||||
SMB_COM_NT_TRANSACT = 0xA0
|
||||
SMB_COM_NT_CREATE_ANDX = 0xA2
|
||||
|
||||
SMB_COMMAND_NAMES = {
|
||||
0x00: 'SMB_COM_CREATE_DIRECTORY',
|
||||
0x01: 'SMB_COM_DELETE_DIRECTORY',
|
||||
0x04: 'SMB_COM_CLOSE',
|
||||
0x06: 'SMB_COM_DELETE',
|
||||
0x25: 'SMB_COM_TRANSACTION',
|
||||
0x2B: 'SMB_COM_ECHO',
|
||||
0x2D: 'SMB_COM_OPEN_ANDX',
|
||||
0x2E: 'SMB_COM_READ_ANDX',
|
||||
0x2F: 'SMB_COM_WRITE_ANDX',
|
||||
0x32: 'SMB_COM_TRANSACTION2',
|
||||
0x72: 'SMB_COM_NEGOTIATE',
|
||||
0x73: 'SMB_COM_SESSION_SETUP_ANDX',
|
||||
0x75: 'SMB_COM_TREE_CONNECT_ANDX',
|
||||
0xA0: 'SMB_COM_NT_TRANSACT',
|
||||
0xA2: 'SMB_COM_NT_CREATE_ANDX',
|
||||
}
|
||||
|
||||
# Bitmask for Flags field in SMB message header
|
||||
SMB_FLAGS_LOCK_AND_READ_OK = 0x01 # LANMAN1.0
|
||||
SMB_FLAGS_BUF_AVAIL = 0x02 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CASE_INSENSITIVE = 0x08 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CANONICALIZED_PATHS = 0x10 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPLOCK = 0x20 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPBATCH = 0x40 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_REPLY = 0x80 # LANMAN1.0
|
||||
|
||||
# Bitmask for Flags2 field in SMB message header
|
||||
SMB_FLAGS2_LONG_NAMES = 0x0001 # LANMAN2.0
|
||||
SMB_FLAGS2_EAS = 0x0002 # LANMAN1.2
|
||||
SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 # NT LANMAN
|
||||
SMB_FLAGS2_IS_LONG_NAME = 0x0040 # NT LANMAN
|
||||
SMB_FLAGS2_DFS = 0x1000 # NT LANMAN
|
||||
SMB_FLAGS2_REPARSE_PATH = 0x0400 #
|
||||
SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 #
|
||||
SMB_FLAGS2_PAGING_IO = 0x2000 # NT LANMAN
|
||||
SMB_FLAGS2_NT_STATUS = 0x4000 # NT LANMAN
|
||||
SMB_FLAGS2_UNICODE = 0x8000 # NT LANMAN
|
||||
|
||||
# Bitmask for Capabilities field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
# [MS-SMB]: 2.2.4.5.2.1 (Capabilities field)
|
||||
CAP_RAW_MODE = 0x01
|
||||
CAP_MPX_MODE = 0x02
|
||||
CAP_UNICODE = 0x04
|
||||
CAP_LARGE_FILES = 0x08
|
||||
CAP_NT_SMBS = 0x10
|
||||
CAP_RPC_REMOTE_APIS = 0x20
|
||||
CAP_STATUS32 = 0x40
|
||||
CAP_LEVEL_II_OPLOCKS = 0x80
|
||||
CAP_LOCK_AND_READ = 0x0100
|
||||
CAP_NT_FIND = 0x0200
|
||||
CAP_DFS = 0x1000
|
||||
CAP_INFOLEVEL_PASSTHRU = 0x2000
|
||||
CAP_LARGE_READX = 0x4000
|
||||
CAP_LARGE_WRITEX = 0x8000
|
||||
CAP_LWIO = 0x010000
|
||||
CAP_UNIX = 0x800000
|
||||
CAP_COMPRESSED = 0x02000000
|
||||
CAP_DYNAMIC_REAUTH = 0x20000000
|
||||
CAP_PERSISTENT_HANDLES = 0x40000000
|
||||
CAP_EXTENDED_SECURITY = 0x80000000
|
||||
|
||||
# Value for Action field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
SMB_SETUP_GUEST = 0x0001
|
||||
SMB_SETUP_USE_LANMAN_KEY = 0X0002
|
||||
|
||||
# Bitmask for SecurityMode field in SMB_COM_NEGOTIATE response
|
||||
NEGOTIATE_USER_SECURITY = 0x01
|
||||
NEGOTIATE_ENCRYPT_PASSWORDS = 0x02
|
||||
NEGOTIATE_SECURITY_SIGNATURES_ENABLE = 0x04
|
||||
NEGOTIATE_SECURITY_SIGNATURES_REQUIRE = 0x08
|
||||
|
||||
# Available constants for Service field in SMB_COM_TREE_CONNECT_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.55.1 (Service field)
|
||||
SERVICE_PRINTER = 'LPT1:'
|
||||
SERVICE_NAMED_PIPE = 'IPC'
|
||||
SERVICE_COMM = 'COMM'
|
||||
SERVICE_ANY = '?????'
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
NT_CREATE_REQUEST_OPLOCK = 0x02
|
||||
NT_CREATE_REQUEST_OPBATCH = 0x04
|
||||
NT_CREATE_OPEN_TARGET_DIR = 0x08
|
||||
NT_CREATE_REQUEST_EXTENDED_RESPONSE = 0x10 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Bitmask for DesiredAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# Also used for MaximalAccess field in SMB2TreeConnectResponse class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13.1.1
|
||||
FILE_READ_DATA = 0x01
|
||||
FILE_WRITE_DATA = 0X02
|
||||
FILE_APPEND_DATA = 0x04
|
||||
FILE_READ_EA = 0x08
|
||||
FILE_WRITE_EA = 0x10
|
||||
FILE_EXECUTE = 0x20
|
||||
FILE_DELETE_CHILD = 0x40
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
FILE_WRITE_ATTRIBUTES = 0x0100
|
||||
DELETE = 0x010000
|
||||
READ_CONTROL = 0x020000
|
||||
WRITE_DAC = 0x040000
|
||||
WRITE_OWNER = 0x080000
|
||||
SYNCHRONIZE = 0x100000
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
GENERIC_ALL = 0x10000000
|
||||
GENERIC_EXECUTE = 0x20000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
GENERIC_READ = 0x80000000L
|
||||
|
||||
# SMB_EXT_FILE_ATTR bitmask ([MS-CIFS]: 2.2.1.2.3)
|
||||
# Includes extensions defined in [MS-SMB] 2.2.1.2.1
|
||||
# Bitmask for FileAttributes field in SMB_COM_NT_CREATE_ANDX request ([MS-CIFS]: 2.2.4.64.1)
|
||||
# Also used for FileAttributes field in SMB2CreateRequest class ([MS-SMB2]: 2.2.13)
|
||||
ATTR_READONLY = 0x01
|
||||
ATTR_HIDDEN = 0x02
|
||||
ATTR_SYSTEM = 0x04
|
||||
ATTR_DIRECTORY = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
ATTR_NORMAL = 0x80
|
||||
ATTR_TEMPORARY = 0x0100
|
||||
ATTR_SPARSE = 0x0200
|
||||
ATTR_REPARSE_POINT = 0x0400
|
||||
ATTR_COMPRESSED = 0x0800
|
||||
ATTR_OFFLINE = 0x1000
|
||||
ATTR_NOT_CONTENT_INDEXED = 0x2000
|
||||
ATTR_ENCRYPTED = 0x4000
|
||||
POSIX_SEMANTICS = 0x01000000
|
||||
BACKUP_SEMANTICS = 0x02000000
|
||||
DELETE_ON_CLOSE = 0x04000000
|
||||
SEQUENTIAL_SCAN = 0x08000000
|
||||
RANDOM_ACCESS = 0x10000000
|
||||
NO_BUFFERING = 0x20000000
|
||||
WRITE_THROUGH = 0x80000000
|
||||
|
||||
# Bitmask for ShareAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SHARE_NONE = 0x00
|
||||
FILE_SHARE_READ = 0x01
|
||||
FILE_SHARE_WRITE = 0x02
|
||||
FILE_SHARE_DELETE = 0x04
|
||||
|
||||
# Values for CreateDisposition field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SUPERSEDE = 0x00
|
||||
FILE_OPEN = 0x01
|
||||
FILE_CREATE = 0x02
|
||||
FILE_OPEN_IF = 0x03
|
||||
FILE_OVERWRITE = 0x04
|
||||
FILE_OVERWRITE_IF = 0x05
|
||||
|
||||
# Bitmask for CreateOptions field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_DIRECTORY_FILE = 0x01
|
||||
FILE_WRITE_THROUGH = 0x02
|
||||
FILE_SEQUENTIAL_ONLY = 0x04
|
||||
FILE_NO_INTERMEDIATE_BUFFERING = 0x08
|
||||
FILE_SYNCHRONOUS_IO_ALERT = 0x10
|
||||
FILE_SYNCHRONOUS_IO_NONALERT = 0x20
|
||||
FILE_NON_DIRECTORY_FILE = 0x40
|
||||
FILE_CREATE_TREE_CONNECTION = 0x80
|
||||
FILE_COMPLETE_IF_OPLOCKED = 0x0100
|
||||
FILE_NO_EA_KNOWLEDGE = 0x0200
|
||||
FILE_OPEN_FOR_RECOVERY = 0x0400
|
||||
FILE_RANDOM_ACCESS = 0x0800
|
||||
FILE_DELETE_ON_CLOSE = 0x1000
|
||||
FILE_OPEN_BY_FILE_ID = 0x2000
|
||||
FILE_OPEN_FOR_BACKUP_INTENT = 0x4000
|
||||
FILE_NO_COMPRESSION = 0x8000
|
||||
FILE_RESERVE_OPFILTER = 0x100000
|
||||
FILE_OPEN_NO_RECALL = 0x400000
|
||||
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x800000
|
||||
|
||||
# Values for ImpersonationLevel field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# For interpretations about these values, refer to [MS-WSO] and [MSDN-IMPERS]
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
SEC_ANONYMOUS = 0x00
|
||||
SEC_IDENTIFY = 0x01
|
||||
SEC_IMPERSONATE = 0x02
|
||||
SEC_DELEGATION = 0x03 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Values for SecurityFlags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
SMB_SECURITY_CONTEXT_TRACKING = 0x01
|
||||
SMB_SECURITY_EFFECTIVE_ONLY = 0x02
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_TRANSACTION2 request
|
||||
# [MS-CIFS]: 2.2.4.46.1
|
||||
DISCONNECT_TID = 0x01
|
||||
NO_RESPONSE = 0x02
|
||||
|
||||
# Bitmask for basic file attributes
|
||||
# [MS-CIFS]: 2.2.1.2.4
|
||||
SMB_FILE_ATTRIBUTE_NORMAL = 0x00
|
||||
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
||||
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
||||
SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations
|
||||
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
||||
# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files for
|
||||
# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs.
|
||||
SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000
|
||||
# Do not use the following values for listPath() operations as they are not supported for SMB2
|
||||
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
||||
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
||||
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
||||
SMB_SEARCH_ATTRIBUTE_DIRECTORY = 0x1000
|
||||
SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
|
||||
|
||||
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
||||
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
||||
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
||||
|
||||
# Bitmask for security information fields, specified as
|
||||
# AdditionalInformation in SMB2
|
||||
# [MS-SMB]: 2.2.7.4
|
||||
# [MS-SMB2]: 2.2.37
|
||||
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||
DACL_SECURITY_INFORMATION = 0x00000004
|
||||
SACL_SECURITY_INFORMATION = 0x00000008
|
||||
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
|
||||
md4.py and U32.py
|
||||
Both modules downloaded from http://www.oocities.org/rozmanov/python/md4.html.
|
||||
Licensed under LGPL
|
||||
|
||||
pyDes.py 2.0.0
|
||||
Downloaded from http://twhiteman.netfirms.com/des.html
|
||||
Licensed under public domain
|
||||
|
||||
sha256.py
|
||||
Downloaded from http://xbmc-addons.googlecode.com/svn-history/r1686/trunk/scripts/OpenSubtitles_OSD/resources/lib/sha256.py
|
||||
Licensed under MIT
|
||||
|
||||
md4.py and U32.py
|
||||
Both modules downloaded from http://www.oocities.org/rozmanov/python/md4.html.
|
||||
Licensed under LGPL
|
||||
|
||||
pyDes.py 2.0.0
|
||||
Downloaded from http://twhiteman.netfirms.com/des.html
|
||||
Licensed under public domain
|
||||
|
||||
sha256.py
|
||||
Downloaded from http://xbmc-addons.googlecode.com/svn-history/r1686/trunk/scripts/OpenSubtitles_OSD/resources/lib/sha256.py
|
||||
Licensed under MIT
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
def convertFILETIMEtoEpoch(t):
|
||||
return (t - 116444736000000000L) / 10000000.0;
|
||||
|
||||
def convertFILETIMEtoEpoch(t):
|
||||
return (t - 116444736000000000L) / 10000000.0;
|
||||
|
||||
@@ -1,254 +1,254 @@
|
||||
# md4.py implements md4 hash class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# based on md4.c from "the Python Cryptography Toolkit, version 1.0.0
|
||||
# Copyright (C) 1995, A.M. Kuchling"
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
# MD4 validation data
|
||||
|
||||
md4_test= [
|
||||
('', 0x31d6cfe0d16ae931b73c59d7e0c089c0L),
|
||||
("a", 0xbde52cb31de33e46245e05fbdbd6fb24L),
|
||||
("abc", 0xa448017aaf21d8525fc10ae87aa6729dL),
|
||||
("message digest", 0xd9130a8164549fe818874806e1c7014bL),
|
||||
("abcdefghijklmnopqrstuvwxyz", 0xd79e1c308aa5bbcdeea8ed63df412da9L),
|
||||
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
0x043f8582f241db351ce627e153e7f0e4L),
|
||||
("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
0xe33b4ddc9c38f2199c3e7b164fcc0536L),
|
||||
]
|
||||
|
||||
#====================================================================
|
||||
from U32 import U32
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
class MD4:
|
||||
A = None
|
||||
B = None
|
||||
C = None
|
||||
D = None
|
||||
count, len1, len2 = None, None, None
|
||||
buf = []
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __init__(self):
|
||||
|
||||
|
||||
self.A = U32(0x67452301L)
|
||||
self.B = U32(0xefcdab89L)
|
||||
self.C = U32(0x98badcfeL)
|
||||
self.D = U32(0x10325476L)
|
||||
self.count, self.len1, self.len2 = U32(0L), U32(0L), U32(0L)
|
||||
self.buf = [0x00] * 64
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __repr__(self):
|
||||
r = 'A = %s, \nB = %s, \nC = %s, \nD = %s.\n' % (self.A.__repr__(), self.B.__repr__(), self.C.__repr__(), self.D.__repr__())
|
||||
r = r + 'count = %s, \nlen1 = %s, \nlen2 = %s.\n' % (self.count.__repr__(), self.len1.__repr__(), self.len2.__repr__())
|
||||
for i in range(4):
|
||||
for j in range(16):
|
||||
r = r + '%4s ' % hex(self.buf[i+j])
|
||||
r = r + '\n'
|
||||
|
||||
return r
|
||||
#-----------------------------------------------------
|
||||
def make_copy(self):
|
||||
|
||||
dest = new()
|
||||
|
||||
dest.len1 = self.len1
|
||||
dest.len2 = self.len2
|
||||
dest.A = self.A
|
||||
dest.B = self.B
|
||||
dest.C = self.C
|
||||
dest.D = self.D
|
||||
dest.count = self.count
|
||||
for i in range(self.count):
|
||||
dest.buf[i] = self.buf[i]
|
||||
|
||||
return dest
|
||||
|
||||
#-----------------------------------------------------
|
||||
def update(self, str):
|
||||
|
||||
buf = []
|
||||
for i in str: buf.append(ord(i))
|
||||
ilen = U32(len(buf))
|
||||
|
||||
# check if the first length is out of range
|
||||
# as the length is measured in bits then multiplay it by 8
|
||||
if (long(self.len1 + (ilen << 3)) < long(self.len1)):
|
||||
self.len2 = self.len2 + U32(1)
|
||||
|
||||
self.len1 = self.len1 + (ilen << 3)
|
||||
self.len2 = self.len2 + (ilen >> 29)
|
||||
|
||||
L = U32(0)
|
||||
bufpos = 0
|
||||
while (long(ilen) > 0):
|
||||
if (64 - long(self.count)) < long(ilen): L = U32(64 - long(self.count))
|
||||
else: L = ilen
|
||||
for i in range(int(L)): self.buf[i + int(self.count)] = buf[i + bufpos]
|
||||
self.count = self.count + L
|
||||
ilen = ilen - L
|
||||
bufpos = bufpos + int(L)
|
||||
|
||||
if (long(self.count) == 64L):
|
||||
self.count = U32(0L)
|
||||
X = []
|
||||
i = 0
|
||||
for j in range(16):
|
||||
X.append(U32(self.buf[i]) + (U32(self.buf[i+1]) << 8) + \
|
||||
(U32(self.buf[i+2]) << 16) + (U32(self.buf[i+3]) << 24))
|
||||
i = i + 4
|
||||
|
||||
A = self.A
|
||||
B = self.B
|
||||
C = self.C
|
||||
D = self.D
|
||||
|
||||
A = f1(A,B,C,D, 0, 3, X)
|
||||
D = f1(D,A,B,C, 1, 7, X)
|
||||
C = f1(C,D,A,B, 2,11, X)
|
||||
B = f1(B,C,D,A, 3,19, X)
|
||||
A = f1(A,B,C,D, 4, 3, X)
|
||||
D = f1(D,A,B,C, 5, 7, X)
|
||||
C = f1(C,D,A,B, 6,11, X)
|
||||
B = f1(B,C,D,A, 7,19, X)
|
||||
A = f1(A,B,C,D, 8, 3, X)
|
||||
D = f1(D,A,B,C, 9, 7, X)
|
||||
C = f1(C,D,A,B,10,11, X)
|
||||
B = f1(B,C,D,A,11,19, X)
|
||||
A = f1(A,B,C,D,12, 3, X)
|
||||
D = f1(D,A,B,C,13, 7, X)
|
||||
C = f1(C,D,A,B,14,11, X)
|
||||
B = f1(B,C,D,A,15,19, X)
|
||||
|
||||
A = f2(A,B,C,D, 0, 3, X)
|
||||
D = f2(D,A,B,C, 4, 5, X)
|
||||
C = f2(C,D,A,B, 8, 9, X)
|
||||
B = f2(B,C,D,A,12,13, X)
|
||||
A = f2(A,B,C,D, 1, 3, X)
|
||||
D = f2(D,A,B,C, 5, 5, X)
|
||||
C = f2(C,D,A,B, 9, 9, X)
|
||||
B = f2(B,C,D,A,13,13, X)
|
||||
A = f2(A,B,C,D, 2, 3, X)
|
||||
D = f2(D,A,B,C, 6, 5, X)
|
||||
C = f2(C,D,A,B,10, 9, X)
|
||||
B = f2(B,C,D,A,14,13, X)
|
||||
A = f2(A,B,C,D, 3, 3, X)
|
||||
D = f2(D,A,B,C, 7, 5, X)
|
||||
C = f2(C,D,A,B,11, 9, X)
|
||||
B = f2(B,C,D,A,15,13, X)
|
||||
|
||||
A = f3(A,B,C,D, 0, 3, X)
|
||||
D = f3(D,A,B,C, 8, 9, X)
|
||||
C = f3(C,D,A,B, 4,11, X)
|
||||
B = f3(B,C,D,A,12,15, X)
|
||||
A = f3(A,B,C,D, 2, 3, X)
|
||||
D = f3(D,A,B,C,10, 9, X)
|
||||
C = f3(C,D,A,B, 6,11, X)
|
||||
B = f3(B,C,D,A,14,15, X)
|
||||
A = f3(A,B,C,D, 1, 3, X)
|
||||
D = f3(D,A,B,C, 9, 9, X)
|
||||
C = f3(C,D,A,B, 5,11, X)
|
||||
B = f3(B,C,D,A,13,15, X)
|
||||
A = f3(A,B,C,D, 3, 3, X)
|
||||
D = f3(D,A,B,C,11, 9, X)
|
||||
C = f3(C,D,A,B, 7,11, X)
|
||||
B = f3(B,C,D,A,15,15, X)
|
||||
|
||||
self.A = self.A + A
|
||||
self.B = self.B + B
|
||||
self.C = self.C + C
|
||||
self.D = self.D + D
|
||||
|
||||
#-----------------------------------------------------
|
||||
def digest(self):
|
||||
|
||||
res = [0x00] * 16
|
||||
s = [0x00] * 8
|
||||
padding = [0x00] * 64
|
||||
padding[0] = 0x80
|
||||
padlen, oldlen1, oldlen2 = U32(0), U32(0), U32(0)
|
||||
|
||||
temp = self.make_copy()
|
||||
|
||||
oldlen1 = temp.len1
|
||||
oldlen2 = temp.len2
|
||||
if (56 <= long(self.count)): padlen = U32(56 - long(self.count) + 64)
|
||||
else: padlen = U32(56 - long(self.count))
|
||||
|
||||
temp.update(int_array2str(padding[:int(padlen)]))
|
||||
|
||||
s[0]= (oldlen1) & U32(0xFF)
|
||||
s[1]=((oldlen1) >> 8) & U32(0xFF)
|
||||
s[2]=((oldlen1) >> 16) & U32(0xFF)
|
||||
s[3]=((oldlen1) >> 24) & U32(0xFF)
|
||||
s[4]= (oldlen2) & U32(0xFF)
|
||||
s[5]=((oldlen2) >> 8) & U32(0xFF)
|
||||
s[6]=((oldlen2) >> 16) & U32(0xFF)
|
||||
s[7]=((oldlen2) >> 24) & U32(0xFF)
|
||||
temp.update(int_array2str(s))
|
||||
|
||||
res[ 0]= temp.A & U32(0xFF)
|
||||
res[ 1]=(temp.A >> 8) & U32(0xFF)
|
||||
res[ 2]=(temp.A >> 16) & U32(0xFF)
|
||||
res[ 3]=(temp.A >> 24) & U32(0xFF)
|
||||
res[ 4]= temp.B & U32(0xFF)
|
||||
res[ 5]=(temp.B >> 8) & U32(0xFF)
|
||||
res[ 6]=(temp.B >> 16) & U32(0xFF)
|
||||
res[ 7]=(temp.B >> 24) & U32(0xFF)
|
||||
res[ 8]= temp.C & U32(0xFF)
|
||||
res[ 9]=(temp.C >> 8) & U32(0xFF)
|
||||
res[10]=(temp.C >> 16) & U32(0xFF)
|
||||
res[11]=(temp.C >> 24) & U32(0xFF)
|
||||
res[12]= temp.D & U32(0xFF)
|
||||
res[13]=(temp.D >> 8) & U32(0xFF)
|
||||
res[14]=(temp.D >> 16) & U32(0xFF)
|
||||
res[15]=(temp.D >> 24) & U32(0xFF)
|
||||
|
||||
return int_array2str(res)
|
||||
|
||||
#====================================================================
|
||||
# helpers
|
||||
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
|
||||
def G(x, y, z): return (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
|
||||
def H(x, y, z): return ((x) ^ (y) ^ (z))
|
||||
|
||||
def ROL(x, n): return (((x) << n) | ((x) >> (32-n)))
|
||||
|
||||
def f1(a, b, c, d, k, s, X): return ROL(a + F(b, c, d) + X[k], s)
|
||||
def f2(a, b, c, d, k, s, X): return ROL(a + G(b, c, d) + X[k] + U32(0x5a827999L), s)
|
||||
def f3(a, b, c, d, k, s, X): return ROL(a + H(b, c, d) + X[k] + U32(0x6ed9eba1L), s)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# helper function
|
||||
def int_array2str(array):
|
||||
str = ''
|
||||
for i in array:
|
||||
str = str + chr(i)
|
||||
return str
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# To be able to use md4.new() instead of md4.MD4()
|
||||
new = MD4
|
||||
# md4.py implements md4 hash class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# based on md4.c from "the Python Cryptography Toolkit, version 1.0.0
|
||||
# Copyright (C) 1995, A.M. Kuchling"
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
# MD4 validation data
|
||||
|
||||
md4_test= [
|
||||
('', 0x31d6cfe0d16ae931b73c59d7e0c089c0L),
|
||||
("a", 0xbde52cb31de33e46245e05fbdbd6fb24L),
|
||||
("abc", 0xa448017aaf21d8525fc10ae87aa6729dL),
|
||||
("message digest", 0xd9130a8164549fe818874806e1c7014bL),
|
||||
("abcdefghijklmnopqrstuvwxyz", 0xd79e1c308aa5bbcdeea8ed63df412da9L),
|
||||
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
0x043f8582f241db351ce627e153e7f0e4L),
|
||||
("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
0xe33b4ddc9c38f2199c3e7b164fcc0536L),
|
||||
]
|
||||
|
||||
#====================================================================
|
||||
from U32 import U32
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
class MD4:
|
||||
A = None
|
||||
B = None
|
||||
C = None
|
||||
D = None
|
||||
count, len1, len2 = None, None, None
|
||||
buf = []
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __init__(self):
|
||||
|
||||
|
||||
self.A = U32(0x67452301L)
|
||||
self.B = U32(0xefcdab89L)
|
||||
self.C = U32(0x98badcfeL)
|
||||
self.D = U32(0x10325476L)
|
||||
self.count, self.len1, self.len2 = U32(0L), U32(0L), U32(0L)
|
||||
self.buf = [0x00] * 64
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __repr__(self):
|
||||
r = 'A = %s, \nB = %s, \nC = %s, \nD = %s.\n' % (self.A.__repr__(), self.B.__repr__(), self.C.__repr__(), self.D.__repr__())
|
||||
r = r + 'count = %s, \nlen1 = %s, \nlen2 = %s.\n' % (self.count.__repr__(), self.len1.__repr__(), self.len2.__repr__())
|
||||
for i in range(4):
|
||||
for j in range(16):
|
||||
r = r + '%4s ' % hex(self.buf[i+j])
|
||||
r = r + '\n'
|
||||
|
||||
return r
|
||||
#-----------------------------------------------------
|
||||
def make_copy(self):
|
||||
|
||||
dest = new()
|
||||
|
||||
dest.len1 = self.len1
|
||||
dest.len2 = self.len2
|
||||
dest.A = self.A
|
||||
dest.B = self.B
|
||||
dest.C = self.C
|
||||
dest.D = self.D
|
||||
dest.count = self.count
|
||||
for i in range(self.count):
|
||||
dest.buf[i] = self.buf[i]
|
||||
|
||||
return dest
|
||||
|
||||
#-----------------------------------------------------
|
||||
def update(self, str):
|
||||
|
||||
buf = []
|
||||
for i in str: buf.append(ord(i))
|
||||
ilen = U32(len(buf))
|
||||
|
||||
# check if the first length is out of range
|
||||
# as the length is measured in bits then multiplay it by 8
|
||||
if (long(self.len1 + (ilen << 3)) < long(self.len1)):
|
||||
self.len2 = self.len2 + U32(1)
|
||||
|
||||
self.len1 = self.len1 + (ilen << 3)
|
||||
self.len2 = self.len2 + (ilen >> 29)
|
||||
|
||||
L = U32(0)
|
||||
bufpos = 0
|
||||
while (long(ilen) > 0):
|
||||
if (64 - long(self.count)) < long(ilen): L = U32(64 - long(self.count))
|
||||
else: L = ilen
|
||||
for i in range(int(L)): self.buf[i + int(self.count)] = buf[i + bufpos]
|
||||
self.count = self.count + L
|
||||
ilen = ilen - L
|
||||
bufpos = bufpos + int(L)
|
||||
|
||||
if (long(self.count) == 64L):
|
||||
self.count = U32(0L)
|
||||
X = []
|
||||
i = 0
|
||||
for j in range(16):
|
||||
X.append(U32(self.buf[i]) + (U32(self.buf[i+1]) << 8) + \
|
||||
(U32(self.buf[i+2]) << 16) + (U32(self.buf[i+3]) << 24))
|
||||
i = i + 4
|
||||
|
||||
A = self.A
|
||||
B = self.B
|
||||
C = self.C
|
||||
D = self.D
|
||||
|
||||
A = f1(A,B,C,D, 0, 3, X)
|
||||
D = f1(D,A,B,C, 1, 7, X)
|
||||
C = f1(C,D,A,B, 2,11, X)
|
||||
B = f1(B,C,D,A, 3,19, X)
|
||||
A = f1(A,B,C,D, 4, 3, X)
|
||||
D = f1(D,A,B,C, 5, 7, X)
|
||||
C = f1(C,D,A,B, 6,11, X)
|
||||
B = f1(B,C,D,A, 7,19, X)
|
||||
A = f1(A,B,C,D, 8, 3, X)
|
||||
D = f1(D,A,B,C, 9, 7, X)
|
||||
C = f1(C,D,A,B,10,11, X)
|
||||
B = f1(B,C,D,A,11,19, X)
|
||||
A = f1(A,B,C,D,12, 3, X)
|
||||
D = f1(D,A,B,C,13, 7, X)
|
||||
C = f1(C,D,A,B,14,11, X)
|
||||
B = f1(B,C,D,A,15,19, X)
|
||||
|
||||
A = f2(A,B,C,D, 0, 3, X)
|
||||
D = f2(D,A,B,C, 4, 5, X)
|
||||
C = f2(C,D,A,B, 8, 9, X)
|
||||
B = f2(B,C,D,A,12,13, X)
|
||||
A = f2(A,B,C,D, 1, 3, X)
|
||||
D = f2(D,A,B,C, 5, 5, X)
|
||||
C = f2(C,D,A,B, 9, 9, X)
|
||||
B = f2(B,C,D,A,13,13, X)
|
||||
A = f2(A,B,C,D, 2, 3, X)
|
||||
D = f2(D,A,B,C, 6, 5, X)
|
||||
C = f2(C,D,A,B,10, 9, X)
|
||||
B = f2(B,C,D,A,14,13, X)
|
||||
A = f2(A,B,C,D, 3, 3, X)
|
||||
D = f2(D,A,B,C, 7, 5, X)
|
||||
C = f2(C,D,A,B,11, 9, X)
|
||||
B = f2(B,C,D,A,15,13, X)
|
||||
|
||||
A = f3(A,B,C,D, 0, 3, X)
|
||||
D = f3(D,A,B,C, 8, 9, X)
|
||||
C = f3(C,D,A,B, 4,11, X)
|
||||
B = f3(B,C,D,A,12,15, X)
|
||||
A = f3(A,B,C,D, 2, 3, X)
|
||||
D = f3(D,A,B,C,10, 9, X)
|
||||
C = f3(C,D,A,B, 6,11, X)
|
||||
B = f3(B,C,D,A,14,15, X)
|
||||
A = f3(A,B,C,D, 1, 3, X)
|
||||
D = f3(D,A,B,C, 9, 9, X)
|
||||
C = f3(C,D,A,B, 5,11, X)
|
||||
B = f3(B,C,D,A,13,15, X)
|
||||
A = f3(A,B,C,D, 3, 3, X)
|
||||
D = f3(D,A,B,C,11, 9, X)
|
||||
C = f3(C,D,A,B, 7,11, X)
|
||||
B = f3(B,C,D,A,15,15, X)
|
||||
|
||||
self.A = self.A + A
|
||||
self.B = self.B + B
|
||||
self.C = self.C + C
|
||||
self.D = self.D + D
|
||||
|
||||
#-----------------------------------------------------
|
||||
def digest(self):
|
||||
|
||||
res = [0x00] * 16
|
||||
s = [0x00] * 8
|
||||
padding = [0x00] * 64
|
||||
padding[0] = 0x80
|
||||
padlen, oldlen1, oldlen2 = U32(0), U32(0), U32(0)
|
||||
|
||||
temp = self.make_copy()
|
||||
|
||||
oldlen1 = temp.len1
|
||||
oldlen2 = temp.len2
|
||||
if (56 <= long(self.count)): padlen = U32(56 - long(self.count) + 64)
|
||||
else: padlen = U32(56 - long(self.count))
|
||||
|
||||
temp.update(int_array2str(padding[:int(padlen)]))
|
||||
|
||||
s[0]= (oldlen1) & U32(0xFF)
|
||||
s[1]=((oldlen1) >> 8) & U32(0xFF)
|
||||
s[2]=((oldlen1) >> 16) & U32(0xFF)
|
||||
s[3]=((oldlen1) >> 24) & U32(0xFF)
|
||||
s[4]= (oldlen2) & U32(0xFF)
|
||||
s[5]=((oldlen2) >> 8) & U32(0xFF)
|
||||
s[6]=((oldlen2) >> 16) & U32(0xFF)
|
||||
s[7]=((oldlen2) >> 24) & U32(0xFF)
|
||||
temp.update(int_array2str(s))
|
||||
|
||||
res[ 0]= temp.A & U32(0xFF)
|
||||
res[ 1]=(temp.A >> 8) & U32(0xFF)
|
||||
res[ 2]=(temp.A >> 16) & U32(0xFF)
|
||||
res[ 3]=(temp.A >> 24) & U32(0xFF)
|
||||
res[ 4]= temp.B & U32(0xFF)
|
||||
res[ 5]=(temp.B >> 8) & U32(0xFF)
|
||||
res[ 6]=(temp.B >> 16) & U32(0xFF)
|
||||
res[ 7]=(temp.B >> 24) & U32(0xFF)
|
||||
res[ 8]= temp.C & U32(0xFF)
|
||||
res[ 9]=(temp.C >> 8) & U32(0xFF)
|
||||
res[10]=(temp.C >> 16) & U32(0xFF)
|
||||
res[11]=(temp.C >> 24) & U32(0xFF)
|
||||
res[12]= temp.D & U32(0xFF)
|
||||
res[13]=(temp.D >> 8) & U32(0xFF)
|
||||
res[14]=(temp.D >> 16) & U32(0xFF)
|
||||
res[15]=(temp.D >> 24) & U32(0xFF)
|
||||
|
||||
return int_array2str(res)
|
||||
|
||||
#====================================================================
|
||||
# helpers
|
||||
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
|
||||
def G(x, y, z): return (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
|
||||
def H(x, y, z): return ((x) ^ (y) ^ (z))
|
||||
|
||||
def ROL(x, n): return (((x) << n) | ((x) >> (32-n)))
|
||||
|
||||
def f1(a, b, c, d, k, s, X): return ROL(a + F(b, c, d) + X[k], s)
|
||||
def f2(a, b, c, d, k, s, X): return ROL(a + G(b, c, d) + X[k] + U32(0x5a827999L), s)
|
||||
def f3(a, b, c, d, k, s, X): return ROL(a + H(b, c, d) + X[k] + U32(0x6ed9eba1L), s)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# helper function
|
||||
def int_array2str(array):
|
||||
str = ''
|
||||
for i in array:
|
||||
str = str + chr(i)
|
||||
return str
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# To be able to use md4.new() instead of md4.MD4()
|
||||
new = MD4
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,112 +1,110 @@
|
||||
#!/usr/bin/python
|
||||
__author__ = 'Thomas Dixon'
|
||||
__license__ = 'MIT'
|
||||
|
||||
import copy
|
||||
import struct
|
||||
import sys
|
||||
|
||||
digest_size = 32
|
||||
blocksize = 1
|
||||
|
||||
def new(m=None):
|
||||
return sha256(m)
|
||||
|
||||
class sha256(object):
|
||||
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
|
||||
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
|
||||
_output_size = 8
|
||||
|
||||
blocksize = 1
|
||||
block_size = 64
|
||||
digest_size = 32
|
||||
|
||||
def __init__(self, m=None):
|
||||
self._buffer = ''
|
||||
self._counter = 0
|
||||
|
||||
if m is not None:
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__)
|
||||
self.update(m)
|
||||
|
||||
def _rotr(self, x, y):
|
||||
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
|
||||
|
||||
def _sha256_process(self, c):
|
||||
w = [0]*64
|
||||
w[0:15] = struct.unpack('!16L', c)
|
||||
|
||||
for i in range(16, 64):
|
||||
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
|
||||
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
|
||||
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
|
||||
|
||||
a,b,c,d,e,f,g,h = self._h
|
||||
|
||||
for i in range(64):
|
||||
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
|
||||
maj = (a & b) ^ (a & c) ^ (b & c)
|
||||
t2 = s0 + maj
|
||||
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
|
||||
ch = (e & f) ^ ((~e) & g)
|
||||
t1 = h + s1 + ch + self._k[i] + w[i]
|
||||
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = (d + t1) & 0xFFFFFFFF
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = (t1 + t2) & 0xFFFFFFFF
|
||||
|
||||
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
|
||||
|
||||
def update(self, m):
|
||||
if not m:
|
||||
return
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__)
|
||||
|
||||
self._buffer += m
|
||||
self._counter += len(m)
|
||||
|
||||
while len(self._buffer) >= 64:
|
||||
self._sha256_process(self._buffer[:64])
|
||||
self._buffer = self._buffer[64:]
|
||||
|
||||
def digest(self):
|
||||
mdi = self._counter & 0x3F
|
||||
length = struct.pack('!Q', self._counter<<3)
|
||||
|
||||
if mdi < 56:
|
||||
padlen = 55-mdi
|
||||
else:
|
||||
padlen = 119-mdi
|
||||
|
||||
r = self.copy()
|
||||
r.update('\x80'+('\x00'*padlen)+length)
|
||||
return ''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
|
||||
|
||||
def hexdigest(self):
|
||||
return self.digest().encode('hex')
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
#!/usr/bin/python
|
||||
__author__ = 'Thomas Dixon'
|
||||
__license__ = 'MIT'
|
||||
|
||||
import copy, struct, sys
|
||||
|
||||
digest_size = 32
|
||||
blocksize = 1
|
||||
|
||||
def new(m=None):
|
||||
return sha256(m)
|
||||
|
||||
class sha256(object):
|
||||
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
|
||||
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
|
||||
_output_size = 8
|
||||
|
||||
blocksize = 1
|
||||
block_size = 64
|
||||
digest_size = 32
|
||||
|
||||
def __init__(self, m=None):
|
||||
self._buffer = ''
|
||||
self._counter = 0
|
||||
|
||||
if m is not None:
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__)
|
||||
self.update(m)
|
||||
|
||||
def _rotr(self, x, y):
|
||||
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
|
||||
|
||||
def _sha256_process(self, c):
|
||||
w = [0]*64
|
||||
w[0:15] = struct.unpack('!16L', c)
|
||||
|
||||
for i in range(16, 64):
|
||||
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
|
||||
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
|
||||
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
|
||||
|
||||
a,b,c,d,e,f,g,h = self._h
|
||||
|
||||
for i in range(64):
|
||||
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
|
||||
maj = (a & b) ^ (a & c) ^ (b & c)
|
||||
t2 = s0 + maj
|
||||
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
|
||||
ch = (e & f) ^ ((~e) & g)
|
||||
t1 = h + s1 + ch + self._k[i] + w[i]
|
||||
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = (d + t1) & 0xFFFFFFFF
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = (t1 + t2) & 0xFFFFFFFF
|
||||
|
||||
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
|
||||
|
||||
def update(self, m):
|
||||
if not m:
|
||||
return
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__)
|
||||
|
||||
self._buffer += m
|
||||
self._counter += len(m)
|
||||
|
||||
while len(self._buffer) >= 64:
|
||||
self._sha256_process(self._buffer[:64])
|
||||
self._buffer = self._buffer[64:]
|
||||
|
||||
def digest(self):
|
||||
mdi = self._counter & 0x3F
|
||||
length = struct.pack('!Q', self._counter<<3)
|
||||
|
||||
if mdi < 56:
|
||||
padlen = 55-mdi
|
||||
else:
|
||||
padlen = 119-mdi
|
||||
|
||||
r = self.copy()
|
||||
r.update('\x80'+('\x00'*padlen)+length)
|
||||
return ''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
|
||||
|
||||
def hexdigest(self):
|
||||
return self.digest().encode('hex')
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@@ -1106,6 +1106,8 @@ def play_torrent(item, xlistitem, mediaurl):
|
||||
url_stat = False
|
||||
torrents_path = ''
|
||||
videolibrary_path = config.get_videolibrary_path() #Calculamos el path absoluto a partir de la Videoteca
|
||||
if videolibrary_path.lower().startswith("smb://"): #Si es una conexión SMB, usamos userdata local
|
||||
videolibrary_path = config.get_data_path() #Calculamos el path absoluto a partir de Userdata
|
||||
if not filetools.exists(videolibrary_path): #Si no existe el path, pasamos al modo clásico
|
||||
videolibrary_path = False
|
||||
else:
|
||||
@@ -1139,7 +1141,7 @@ def play_torrent(item, xlistitem, mediaurl):
|
||||
folder = movies #películas
|
||||
else:
|
||||
folder = series #o series
|
||||
item.url = filetools.join(videolibrary_path, folder, item.url) #dirección del .torrent local en la Videoteca
|
||||
item.url = filetools.join(config.get_videolibrary_path(), folder, item.url) #dirección del .torrent local en la Videoteca
|
||||
if filetools.copy(item.url, torrents_path, silent=True): #se copia a la carpeta generíca para evitar problemas de encode
|
||||
item.url = torrents_path
|
||||
if "torrentin" in torrent_options[seleccion][1]: #Si es Torrentin, hay que añadir un prefijo
|
||||
|
||||
@@ -329,6 +329,7 @@ def mark_content_as_watched_on_alfa(path):
|
||||
from channels import videolibrary
|
||||
from core import videolibrarytools
|
||||
from core import scrapertools
|
||||
from core import filetools
|
||||
import re
|
||||
"""
|
||||
marca toda la serie o película como vista o no vista en la Videoteca de Alfa basado en su estado en la Videoteca de Kodi
|
||||
@@ -375,7 +376,8 @@ def mark_content_as_watched_on_alfa(path):
|
||||
nfo_name = scrapertools.find_single_match(path2, '\]\/(.*?)$') #Construyo el nombre del .nfo
|
||||
path1 = path1.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
||||
path2 = path2.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
||||
|
||||
path2 = filetools.remove_smb_credential(path2) #Si el archivo está en un servidor SMB, quiamos las credenciales
|
||||
|
||||
#Ejecutmos la sentencia SQL
|
||||
sql = 'select strFileName, playCount from %s where (strPath like "%s" or strPath like "%s")' % (contentType, path1, path2)
|
||||
nun_records = 0
|
||||
@@ -486,7 +488,8 @@ def update(folder_content=config.get_setting("folder_tvshows"), folder=""):
|
||||
else:
|
||||
update_path = filetools.join(videolibrarypath, folder_content, folder) + "/"
|
||||
|
||||
payload["params"] = {"directory": update_path}
|
||||
if not update_path.startswith("smb://"):
|
||||
payload["params"] = {"directory": update_path}
|
||||
|
||||
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
|
||||
xbmc.sleep(500)
|
||||
|
||||
Reference in New Issue
Block a user