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