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:
Alfa
2018-11-28 11:24:40 -05:00
committed by GitHub
40 changed files with 9936 additions and 8741 deletions

View File

@@ -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

View File

@@ -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
},

View File

@@ -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",

View File

@@ -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:

View File

@@ -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",

View File

@@ -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&ntilde;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&ntilde;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('&#038;', '&').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('&#038;', '&').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('&#038;', '&').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:

View File

@@ -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",

View File

@@ -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}|(<!--.*?-->)|&nbsp;", "", 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}|(<!--.*?-->)|&nbsp;", "", 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:

View File

@@ -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:

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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)

View File

@@ -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())
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +1,3 @@
def convertFILETIMEtoEpoch(t):
return (t - 116444736000000000L) / 10000000.0;
def convertFILETIMEtoEpoch(t):
return (t - 116444736000000000L) / 10000000.0;

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)