mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 20:02:29 +01:00
Merge branch 'master' into vpc
This commit is contained in:
commit
911ed25fbc
@ -23,7 +23,6 @@ import java.util.Set;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.api.ApiConstants;
|
||||
import com.cloud.api.BaseCmd.CommandType;
|
||||
import com.cloud.api.BaseListTaggedResourcesCmd;
|
||||
import com.cloud.api.IdentityMapper;
|
||||
import com.cloud.api.Implementation;
|
||||
|
||||
@ -870,7 +870,7 @@ public class EC2Engine {
|
||||
public boolean associateAddress( EC2AssociateAddress request ) {
|
||||
try {
|
||||
CloudStackIpAddress cloudIp = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null).get(0);
|
||||
CloudStackUserVm cloudVm = getApi().listVirtualMachines(null, null, null, null, null, null, request.getInstanceId(), null, null, null, null, null, null, null, null).get(0);
|
||||
CloudStackUserVm cloudVm = getApi().listVirtualMachines(null, null, true, null, null, null, null, request.getInstanceId(), null, null, null, null, null, null, null, null).get(0);
|
||||
|
||||
CloudStackInfoResponse resp = getApi().enableStaticNat(cloudIp.getId(), cloudVm.getId());
|
||||
if (resp != null) {
|
||||
@ -1783,7 +1783,7 @@ public class EC2Engine {
|
||||
throws Exception {
|
||||
|
||||
String instId = instanceId != null ? instanceId : null;
|
||||
List<CloudStackUserVm> vms = getApi().listVirtualMachines(null, null, null, null, null, null,
|
||||
List<CloudStackUserVm> vms = getApi().listVirtualMachines(null, null, true, null, null, null, null,
|
||||
instId, null, null, null, null, null, null, null, null);
|
||||
|
||||
if(vms != null && vms.size() > 0) {
|
||||
@ -1911,8 +1911,8 @@ public class EC2Engine {
|
||||
public EC2DescribeSecurityGroupsResponse listSecurityGroups( String[] interestedGroups ) throws Exception {
|
||||
try {
|
||||
EC2DescribeSecurityGroupsResponse groupSet = new EC2DescribeSecurityGroupsResponse();
|
||||
|
||||
List<CloudStackSecurityGroup> groups = getApi().listSecurityGroups(null, null, null, null, null, null);
|
||||
|
||||
List<CloudStackSecurityGroup> groups = getApi().listSecurityGroups(null, null, null, true, null, null, null);
|
||||
if (groups != null && groups.size() > 0)
|
||||
for (CloudStackSecurityGroup group : groups) {
|
||||
boolean matched = false;
|
||||
|
||||
@ -311,13 +311,14 @@ public class CloudStackApi {
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public List<CloudStackUserVm> listVirtualMachines(String account, String accountId, Boolean forVirtualNetwork, String groupId, String hostId,
|
||||
public List<CloudStackUserVm> listVirtualMachines(String account, String accountId, Boolean listAll, Boolean forVirtualNetwork, String groupId, String hostId,
|
||||
String hypervisor, String id, Boolean isRecursive, String keyWord, String name, String networkId, String podId, String state, String storageId,
|
||||
String zoneId) throws Exception {
|
||||
CloudStackCommand cmd = new CloudStackCommand(ApiConstants.LIST_VIRTUAL_MACHINES);
|
||||
if (cmd != null) {
|
||||
if (account != null) cmd.setParam(ApiConstants.ACCOUNT, account);
|
||||
if (accountId != null) cmd.setParam(ApiConstants.ACCOUNT_ID, accountId);
|
||||
if (listAll != null) cmd.setParam(ApiConstants.LIST_ALL, listAll.toString());
|
||||
if (forVirtualNetwork != null) cmd.setParam(ApiConstants.FOR_VIRTUAL_NETWORK, forVirtualNetwork.toString());
|
||||
if (groupId != null) cmd.setParam(ApiConstants.GROUP_ID, groupId);
|
||||
if (hostId != null) cmd.setParam(ApiConstants.HOST_ID, hostId);
|
||||
@ -1087,13 +1088,14 @@ public class CloudStackApi {
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public List<CloudStackSecurityGroup> listSecurityGroups(String account, String domainId, String id, String keyWord, String securityGroupName,
|
||||
String virtualMachineId) throws Exception {
|
||||
public List<CloudStackSecurityGroup> listSecurityGroups(String account, String domainId, String id, Boolean listAll, String keyWord,
|
||||
String securityGroupName, String virtualMachineId) throws Exception {
|
||||
CloudStackCommand cmd = new CloudStackCommand(ApiConstants.LIST_SECURITY_GROUPS);
|
||||
if (cmd != null) {
|
||||
if (account != null) cmd.setParam(ApiConstants.ACCOUNT, account);
|
||||
if (domainId != null) cmd.setParam(ApiConstants.DOMAIN_ID, domainId);
|
||||
if (id != null) cmd.setParam(ApiConstants.ID, id);
|
||||
if (listAll != null) cmd.setParam(ApiConstants.LIST_ALL, listAll.toString());
|
||||
if (keyWord != null) cmd.setParam(ApiConstants.KEYWORD, keyWord);
|
||||
if (securityGroupName != null) cmd.setParam(ApiConstants.SECURITY_GROUP_NAME, securityGroupName);
|
||||
if (virtualMachineId != null) cmd.setParam(ApiConstants.VIRTUAL_MACHINE_ID, virtualMachineId);
|
||||
|
||||
@ -242,6 +242,7 @@ public class ApiConstants {
|
||||
public static final String LINMIN_USERNAME = "linminusername";
|
||||
public static final String LIST_ACCOUNTS = "listAccounts";
|
||||
public static final String LIST_ACCOUNTS_RESPONSE = "listaccountsresponse";
|
||||
public static final String LIST_ALL = "listall";
|
||||
public static final String LIST_CAPABILITIES = "listCapabilities";
|
||||
public static final String LIST_CAPABILITIES_RESPONSE = "listcapabilitiesresponse";
|
||||
public static final String LIST_DISK_OFFERINGS = "listDiskOfferings";
|
||||
|
||||
12
build/replace.properties
Normal file
12
build/replace.properties
Normal file
@ -0,0 +1,12 @@
|
||||
DBUSER=cloud
|
||||
DBPW=cloud
|
||||
DBROOTPW=
|
||||
MSLOG=vmops.log
|
||||
APISERVERLOG=api.log
|
||||
DBHOST=localhost
|
||||
AGENTLOGDIR=logs
|
||||
AGENTLOG=logs/agent.log
|
||||
MSMNTDIR=/mnt
|
||||
COMPONENTS-SPEC=components-premium.xml
|
||||
AWSAPILOG=awsapi.log
|
||||
REMOTEHOST=localhost
|
||||
@ -2133,6 +2133,15 @@ public class ApiResponseHelper implements ResponseGenerator {
|
||||
populateAccount(isoResponse, owner.getId());
|
||||
populateDomain(isoResponse, owner.getDomainId());
|
||||
|
||||
//set tag information
|
||||
List<? extends ResourceTag> tags = ApiDBUtils.listByResourceTypeAndId(TaggedResourceType.ISO, iso.getId());
|
||||
List<ResourceTagResponse> tagResponses = new ArrayList<ResourceTagResponse>();
|
||||
for (ResourceTag tag : tags) {
|
||||
ResourceTagResponse tagResponse = createResourceTagResponse(tag, true);
|
||||
tagResponses.add(tagResponse);
|
||||
}
|
||||
isoResponse.setTags(tagResponses);
|
||||
|
||||
isoResponse.setObjectName("iso");
|
||||
isoResponses.add(isoResponse);
|
||||
return isoResponses;
|
||||
@ -2281,6 +2290,16 @@ public class ApiResponseHelper implements ResponseGenerator {
|
||||
isoResponse.setSize(isoSize);
|
||||
}
|
||||
|
||||
//set tag information
|
||||
List<? extends ResourceTag> tags = ApiDBUtils.listByResourceTypeAndId(TaggedResourceType.ISO, iso.getId());
|
||||
|
||||
List<ResourceTagResponse> tagResponses = new ArrayList<ResourceTagResponse>();
|
||||
for (ResourceTag tag : tags) {
|
||||
ResourceTagResponse tagResponse = createResourceTagResponse(tag, true);
|
||||
tagResponses.add(tagResponse);
|
||||
}
|
||||
isoResponse.setTags(tagResponses);
|
||||
|
||||
isoResponse.setObjectName("iso");
|
||||
isoResponses.add(isoResponse);
|
||||
return isoResponses;
|
||||
|
||||
@ -1296,12 +1296,15 @@ public class ManagementServerImpl implements ManagementServer {
|
||||
boolean showDomr = ((templateFilter != TemplateFilter.selfexecutable) && (templateFilter != TemplateFilter.featured));
|
||||
HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor());
|
||||
|
||||
return listTemplates(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, showDomr,
|
||||
return listTemplates(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null,
|
||||
cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, showDomr,
|
||||
cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags);
|
||||
}
|
||||
|
||||
private Set<Pair<Long, Long>> listTemplates(Long templateId, String name, String keyword, TemplateFilter templateFilter, boolean isIso, Boolean bootable, Long pageSize, Long startIndex,
|
||||
Long zoneId, HypervisorType hyperType, boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags) {
|
||||
private Set<Pair<Long, Long>> listTemplates(Long templateId, String name, String keyword, TemplateFilter templateFilter,
|
||||
boolean isIso, Boolean bootable, Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType,
|
||||
boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller,
|
||||
ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags) {
|
||||
|
||||
VMTemplateVO template = null;
|
||||
if (templateId != null) {
|
||||
@ -1339,11 +1342,12 @@ public class ManagementServerImpl implements ManagementServer {
|
||||
if (template == null) {
|
||||
templateZonePairSet = _templateDao.searchSwiftTemplates(name, keyword, templateFilter, isIso,
|
||||
hypers, bootable, domain, pageSize, startIndex, zoneId, hyperType, onlyReady, showDomr,
|
||||
permittedAccounts, caller);
|
||||
permittedAccounts, caller, tags);
|
||||
Set<Pair<Long, Long>> templateZonePairSet2 = new HashSet<Pair<Long, Long>>();
|
||||
templateZonePairSet2 = _templateDao.searchTemplates(name, keyword, templateFilter, isIso, hypers,
|
||||
bootable, domain, pageSize, startIndex, zoneId, hyperType, onlyReady, showDomr,
|
||||
permittedAccounts, caller, listProjectResourcesCriteria);
|
||||
permittedAccounts, caller, listProjectResourcesCriteria, tags);
|
||||
|
||||
for (Pair<Long, Long> tmpltPair : templateZonePairSet2) {
|
||||
if (!templateZonePairSet.contains(new Pair<Long, Long>(tmpltPair.first(), -1L))) {
|
||||
templateZonePairSet.add(tmpltPair);
|
||||
@ -1362,7 +1366,7 @@ public class ManagementServerImpl implements ManagementServer {
|
||||
if (template == null) {
|
||||
templateZonePairSet = _templateDao.searchTemplates(name, keyword, templateFilter, isIso, hypers,
|
||||
bootable, domain, pageSize, startIndex, zoneId, hyperType, onlyReady, showDomr,
|
||||
permittedAccounts, caller, listProjectResourcesCriteria);
|
||||
permittedAccounts, caller, listProjectResourcesCriteria, tags);
|
||||
} else {
|
||||
// if template is not public, perform permission check here
|
||||
if (!template.isPublicTemplate() && caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
|
||||
|
||||
@ -3772,7 +3772,7 @@ public class StorageManagerImpl implements StorageManager, Manager, ClusterManag
|
||||
sb.join("vmSearch", vmSearch, sb.entity().getInstanceId(), vmSearch.entity().getId(), JoinBuilder.JoinType.LEFTOUTER);
|
||||
|
||||
if (tags != null && !tags.isEmpty()) {
|
||||
SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
|
||||
SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
|
||||
for (int count=0; count < tags.size(); count++) {
|
||||
tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
|
||||
tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package com.cloud.storage.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.cloud.domain.DomainVO;
|
||||
@ -40,24 +41,28 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long> {
|
||||
|
||||
//public void update(VMTemplateVO template);
|
||||
|
||||
|
||||
public List<VMTemplateVO> listAllSystemVMTemplates();
|
||||
|
||||
public List<VMTemplateVO> listDefaultBuiltinTemplates();
|
||||
public String getRoutingTemplateUniqueName();
|
||||
public List<VMTemplateVO> findIsosByIdAndPath(Long domainId, Long accountId, String path);
|
||||
public List<VMTemplateVO> listReadyTemplates();
|
||||
public List<VMTemplateVO> listByAccountId(long accountId);
|
||||
public Set<Pair<Long, Long>> searchTemplates(String name, String keyword, TemplateFilter templateFilter, boolean isIso, List<HypervisorType> hypers, Boolean bootable,
|
||||
DomainVO domain, Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria);
|
||||
public List<VMTemplateVO> listDefaultBuiltinTemplates();
|
||||
public String getRoutingTemplateUniqueName();
|
||||
public List<VMTemplateVO> findIsosByIdAndPath(Long domainId, Long accountId, String path);
|
||||
public List<VMTemplateVO> listReadyTemplates();
|
||||
public List<VMTemplateVO> listByAccountId(long accountId);
|
||||
public Set<Pair<Long, Long>> searchTemplates(String name, String keyword, TemplateFilter templateFilter, boolean isIso,
|
||||
List<HypervisorType> hypers, Boolean bootable, DomainVO domain, Long pageSize, Long startIndex, Long zoneId,
|
||||
HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller,
|
||||
ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags);
|
||||
|
||||
public Set<Pair<Long, Long>> searchSwiftTemplates(String name, String keyword, TemplateFilter templateFilter, boolean isIso, List<HypervisorType> hypers, Boolean bootable, DomainVO domain,
|
||||
Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller);
|
||||
public Set<Pair<Long, Long>> searchSwiftTemplates(String name, String keyword, TemplateFilter templateFilter,
|
||||
boolean isIso, List<HypervisorType> hypers, Boolean bootable, DomainVO domain, Long pageSize, Long startIndex,
|
||||
Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller, Map<String, String> tags);
|
||||
|
||||
public long addTemplateToZone(VMTemplateVO tmplt, long zoneId);
|
||||
public List<VMTemplateVO> listAllInZone(long dataCenterId);
|
||||
|
||||
public List<VMTemplateVO> listByHypervisorType(List<HypervisorType> hyperTypes);
|
||||
public List<VMTemplateVO> publicIsoSearch(Boolean bootable, boolean listRemoved);
|
||||
public List<VMTemplateVO> publicIsoSearch(Boolean bootable, boolean listRemoved, Map<String, String> tags);
|
||||
VMTemplateVO findSystemVMTemplate(long zoneId);
|
||||
VMTemplateVO findSystemVMTemplate(long zoneId, HypervisorType hType);
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ import javax.naming.ConfigurationException;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.api.BaseCmd;
|
||||
import com.cloud.configuration.Resource;
|
||||
import com.cloud.configuration.dao.ConfigurationDao;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.domain.DomainVO;
|
||||
@ -50,6 +49,7 @@ import com.cloud.storage.Storage.TemplateType;
|
||||
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
|
||||
import com.cloud.storage.VMTemplateVO;
|
||||
import com.cloud.storage.VMTemplateZoneVO;
|
||||
import com.cloud.tags.ResourceTagVO;
|
||||
import com.cloud.tags.dao.ResourceTagsDaoImpl;
|
||||
import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
|
||||
import com.cloud.user.Account;
|
||||
@ -139,8 +139,32 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VMTemplateVO> publicIsoSearch(Boolean bootable, boolean listRemoved){
|
||||
SearchCriteria<VMTemplateVO> sc = PublicIsoSearch.create();
|
||||
public List<VMTemplateVO> publicIsoSearch(Boolean bootable, boolean listRemoved, Map<String, String> tags){
|
||||
|
||||
SearchBuilder<VMTemplateVO> sb = null;
|
||||
if (tags == null || tags.isEmpty()) {
|
||||
sb = PublicIsoSearch;
|
||||
} else {
|
||||
sb = createSearchBuilder();
|
||||
sb.and("public", sb.entity().isPublicTemplate(), SearchCriteria.Op.EQ);
|
||||
sb.and("format", sb.entity().getFormat(), SearchCriteria.Op.EQ);
|
||||
sb.and("type", sb.entity().getTemplateType(), SearchCriteria.Op.EQ);
|
||||
sb.and("bootable", sb.entity().isBootable(), SearchCriteria.Op.EQ);
|
||||
sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.EQ);
|
||||
|
||||
SearchBuilder<ResourceTagVO> tagSearch = _tagsDao.createSearchBuilder();
|
||||
for (int count=0; count < tags.size(); count++) {
|
||||
tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
|
||||
tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
|
||||
tagSearch.cp();
|
||||
}
|
||||
tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
sb.groupBy(sb.entity().getId());
|
||||
sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
|
||||
}
|
||||
|
||||
SearchCriteria<VMTemplateVO> sc = sb.create();
|
||||
|
||||
sc.setParameters("public", 1);
|
||||
sc.setParameters("format", "ISO");
|
||||
sc.setParameters("type", TemplateType.PERHOST.toString());
|
||||
@ -152,6 +176,16 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
sc.setParameters("removed", (Object)null);
|
||||
}
|
||||
|
||||
if (tags != null && !tags.isEmpty()) {
|
||||
int count = 0;
|
||||
sc.setJoinParameters("tagSearch", "resourceType", TaggedResourceType.ISO.toString());
|
||||
for (String key : tags.keySet()) {
|
||||
sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
|
||||
sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@ -264,7 +298,6 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
PublicIsoSearch.and("type", PublicIsoSearch.entity().getTemplateType(), SearchCriteria.Op.EQ);
|
||||
PublicIsoSearch.and("bootable", PublicIsoSearch.entity().isBootable(), SearchCriteria.Op.EQ);
|
||||
PublicIsoSearch.and("removed", PublicIsoSearch.entity().getRemoved(), SearchCriteria.Op.EQ);
|
||||
|
||||
|
||||
tmpltTypeHyperSearch = createSearchBuilder();
|
||||
tmpltTypeHyperSearch.and("templateType", tmpltTypeHyperSearch.entity().getTemplateType(), SearchCriteria.Op.EQ);
|
||||
@ -320,7 +353,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
|
||||
@Override
|
||||
public Set<Pair<Long, Long>> searchSwiftTemplates(String name, String keyword, TemplateFilter templateFilter, boolean isIso, List<HypervisorType> hypers, Boolean bootable, DomainVO domain,
|
||||
Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller) {
|
||||
Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr, List<Account> permittedAccounts, Account caller, Map<String, String> tags) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (!permittedAccounts.isEmpty()) {
|
||||
@ -436,8 +469,12 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
return templateZonePairList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Pair<Long, Long>> searchTemplates(String name, String keyword, TemplateFilter templateFilter, boolean isIso, List<HypervisorType> hypers, Boolean bootable, DomainVO domain, Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr,List<Account> permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria) {
|
||||
|
||||
@Override
|
||||
public Set<Pair<Long, Long>> searchTemplates(String name, String keyword, TemplateFilter templateFilter,
|
||||
boolean isIso, List<HypervisorType> hypers, Boolean bootable, DomainVO domain, Long pageSize, Long startIndex,
|
||||
Long zoneId, HypervisorType hyperType, boolean onlyReady, boolean showDomr,List<Account> permittedAccounts,
|
||||
Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (!permittedAccounts.isEmpty()) {
|
||||
for (Account permittedAccount : permittedAccounts) {
|
||||
@ -468,6 +505,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
String guestOSJoin = "";
|
||||
StringBuilder templateHostRefJoin = new StringBuilder();
|
||||
String dataCenterJoin = "", lpjoin = "";
|
||||
String tagsJoin = "";
|
||||
|
||||
if (isIso && !hyperType.equals(HypervisorType.None)) {
|
||||
guestOSJoin = " INNER JOIN guest_os guestOS on (guestOS.id = t.guest_os_id) INNER JOIN guest_os_hypervisor goh on ( goh.guest_os_id = guestOS.id) ";
|
||||
@ -480,11 +518,16 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
if ((templateFilter == TemplateFilter.featured) || (templateFilter == TemplateFilter.community)) {
|
||||
dataCenterJoin = " INNER JOIN data_center dc on (h.data_center_id = dc.id)";
|
||||
}
|
||||
|
||||
if (templateFilter == TemplateFilter.sharedexecutable){
|
||||
lpjoin = " INNER JOIN launch_permission lp ON t.id = lp.template_id ";
|
||||
}
|
||||
|
||||
if (tags != null && !tags.isEmpty()) {
|
||||
tagsJoin = " INNER JOIN resource_tags r ON t.id = r.resource_id ";
|
||||
}
|
||||
|
||||
sql += guestOSJoin + templateHostRefJoin + dataCenterJoin + lpjoin;
|
||||
sql += guestOSJoin + templateHostRefJoin + dataCenterJoin + lpjoin + tagsJoin;
|
||||
String whereClause = "";
|
||||
|
||||
//All joins have to be made before we start setting the condition settings
|
||||
@ -497,7 +540,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
if (listProjectResourcesCriteria == ListProjectResourcesCriteria.SkipProjectResources) {
|
||||
whereClause += " AND a.type != " + Account.ACCOUNT_TYPE_PROJECT;
|
||||
}
|
||||
}else
|
||||
} else
|
||||
if (listProjectResourcesCriteria == ListProjectResourcesCriteria.SkipProjectResources) {
|
||||
whereClause += " WHERE a.type != " + Account.ACCOUNT_TYPE_PROJECT;
|
||||
}
|
||||
@ -580,6 +623,19 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
} else if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN && !isIso) {
|
||||
return templateZonePairList;
|
||||
}
|
||||
|
||||
if (tags != null && !tags.isEmpty()) {
|
||||
whereClause += " AND (";
|
||||
boolean first = true;
|
||||
for (String key : tags.keySet()) {
|
||||
if (!first) {
|
||||
whereClause += " OR ";
|
||||
}
|
||||
whereClause += "(r.key=\"" + key + "\" and r.value=\"" + tags.get(key) + "\")";
|
||||
first = false;
|
||||
}
|
||||
whereClause += ")";
|
||||
}
|
||||
|
||||
if (whereClause.equals("")) {
|
||||
whereClause += " WHERE ";
|
||||
@ -587,7 +643,8 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
whereClause += " AND ";
|
||||
}
|
||||
|
||||
sql += whereClause + getExtrasWhere(templateFilter, name, keyword, isIso, bootable, hyperType, zoneId, onlyReady, showDomr) + groupByClause + getOrderByLimit(pageSize, startIndex);
|
||||
sql += whereClause + getExtrasWhere(templateFilter, name, keyword, isIso, bootable, hyperType, zoneId,
|
||||
onlyReady, showDomr) + groupByClause + getOrderByLimit(pageSize, startIndex);
|
||||
|
||||
pstmt = txn.prepareStatement(sql);
|
||||
rs = pstmt.executeQuery();
|
||||
@ -600,7 +657,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
|
||||
if(isIso && templateZonePairList.size() < (pageSize != null ? pageSize : 500)
|
||||
&& templateFilter != TemplateFilter.community
|
||||
&& !(templateFilter == TemplateFilter.self && !BaseCmd.isRootAdmin(caller.getType())) ){ //evaluates to true If root admin and filter=self
|
||||
List<VMTemplateVO> publicIsos = publicIsoSearch(bootable, false);
|
||||
List<VMTemplateVO> publicIsos = publicIsoSearch(bootable, false, tags);
|
||||
for( int i=0; i < publicIsos.size(); i++){
|
||||
if (keyword != null && publicIsos.get(i).getName().contains(keyword)) {
|
||||
templateZonePairList.add(new Pair<Long,Long>(publicIsos.get(i).getId(), null));
|
||||
|
||||
@ -323,9 +323,13 @@ public class TaggedResourceManagerImpl implements TaggedResourceService, Manager
|
||||
|
||||
sb.and("key", sb.entity().getKey(), SearchCriteria.Op.EQ);
|
||||
sb.and("value", sb.entity().getValue(), SearchCriteria.Op.EQ);
|
||||
|
||||
if (resourceId != null) {
|
||||
sb.and().op("resourceId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
|
||||
sb.or("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.EQ);
|
||||
sb.cp();
|
||||
}
|
||||
|
||||
sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
sb.and("customer", sb.entity().getCustomer(), SearchCriteria.Op.EQ);
|
||||
|
||||
|
||||
@ -2318,9 +2318,8 @@ CREATE TABLE `cloud`.`resource_tags` (
|
||||
CONSTRAINT `fk_tags__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`),
|
||||
CONSTRAINT `fk_tags__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`),
|
||||
UNIQUE `i_tags__resource_id__resource_type__key`(`resource_id`, `resource_type`, `key`),
|
||||
CONSTRAINT `uc_resource_tags__uuid` UNIQUE (`uuid`),
|
||||
CONSTRAINT `uc_resource_tags__resource_uuid` UNIQUE (`resource_uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
CONSTRAINT `uc_resource_tags__uuid` UNIQUE (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
SET foreign_key_checks = 1;
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
# Copyright (C) 2003-2009 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
|
||||
is a module for python 2.3 or greater that implements the SSH2 protocol for
|
||||
secure (encrypted and authenticated) connections to remote machines. Unlike
|
||||
SSL (aka TLS), the SSH2 protocol does not require heirarchical certificates
|
||||
signed by a powerful central authority. You may know SSH2 as the protocol that
|
||||
replaced C{telnet} and C{rsh} for secure access to remote shells, but the
|
||||
protocol also includes the ability to open arbitrary channels to remote
|
||||
services across an encrypted tunnel. (This is how C{sftp} works, for example.)
|
||||
|
||||
The high-level client API starts with creation of an L{SSHClient} object.
|
||||
For more direct control, pass a socket (or socket-like object) to a
|
||||
L{Transport}, and use L{start_server <Transport.start_server>} or
|
||||
L{start_client <Transport.start_client>} to negoatite
|
||||
with the remote host as either a server or client. As a client, you are
|
||||
responsible for authenticating using a password or private key, and checking
|
||||
the server's host key. I{(Key signature and verification is done by paramiko,
|
||||
but you will need to provide private keys and check that the content of a
|
||||
public key matches what you expected to see.)} As a server, you are
|
||||
responsible for deciding which users, passwords, and keys to allow, and what
|
||||
kind of channels to allow.
|
||||
|
||||
Once you have finished, either side may request flow-controlled L{Channel}s to
|
||||
the other side, which are python objects that act like sockets, but send and
|
||||
receive data over the encrypted session.
|
||||
|
||||
Paramiko is written entirely in python (no C or platform-dependent code) and is
|
||||
released under the GNU Lesser General Public License (LGPL).
|
||||
|
||||
Website: U{http://www.lag.net/paramiko/}
|
||||
|
||||
@version: 1.7.6 (Fanny)
|
||||
@author: Robey Pointer
|
||||
@contact: robeypointer@gmail.com
|
||||
@license: GNU Lesser General Public License (LGPL)
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (2, 2):
|
||||
raise RuntimeError('You need python 2.2 for this module.')
|
||||
|
||||
|
||||
__author__ = "Robey Pointer <robeypointer@gmail.com>"
|
||||
__date__ = "1 Nov 2009"
|
||||
__version__ = "1.7.6 (Fanny)"
|
||||
__version_info__ = (1, 7, 6)
|
||||
__license__ = "GNU Lesser General Public License (LGPL)"
|
||||
|
||||
|
||||
from transport import randpool, SecurityOptions, Transport
|
||||
from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy
|
||||
from auth_handler import AuthHandler
|
||||
from channel import Channel, ChannelFile
|
||||
from ssh_exception import SSHException, PasswordRequiredException, \
|
||||
BadAuthenticationType, ChannelException, BadHostKeyException, \
|
||||
AuthenticationException
|
||||
from server import ServerInterface, SubsystemHandler, InteractiveQuery
|
||||
from rsakey import RSAKey
|
||||
from dsskey import DSSKey
|
||||
from sftp import SFTPError, BaseSFTP
|
||||
from sftp_client import SFTP, SFTPClient
|
||||
from sftp_server import SFTPServer
|
||||
from sftp_attr import SFTPAttributes
|
||||
from sftp_handle import SFTPHandle
|
||||
from sftp_si import SFTPServerInterface
|
||||
from sftp_file import SFTPFile
|
||||
from message import Message
|
||||
from packet import Packetizer
|
||||
from file import BufferedFile
|
||||
from agent import Agent, AgentKey
|
||||
from pkey import PKey
|
||||
from hostkeys import HostKeys
|
||||
from config import SSHConfig
|
||||
|
||||
# fix module names for epydoc
|
||||
for c in locals().values():
|
||||
if issubclass(type(c), type) or type(c).__name__ == 'classobj':
|
||||
# classobj for exceptions :/
|
||||
c.__module__ = __name__
|
||||
del c
|
||||
|
||||
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
||||
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
|
||||
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE
|
||||
|
||||
from sftp import SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, \
|
||||
SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED
|
||||
|
||||
__all__ = [ 'Transport',
|
||||
'SSHClient',
|
||||
'MissingHostKeyPolicy',
|
||||
'AutoAddPolicy',
|
||||
'RejectPolicy',
|
||||
'WarningPolicy',
|
||||
'SecurityOptions',
|
||||
'SubsystemHandler',
|
||||
'Channel',
|
||||
'PKey',
|
||||
'RSAKey',
|
||||
'DSSKey',
|
||||
'Message',
|
||||
'SSHException',
|
||||
'AuthenticationException',
|
||||
'PasswordRequiredException',
|
||||
'BadAuthenticationType',
|
||||
'ChannelException',
|
||||
'BadHostKeyException',
|
||||
'SFTP',
|
||||
'SFTPFile',
|
||||
'SFTPHandle',
|
||||
'SFTPClient',
|
||||
'SFTPServer',
|
||||
'SFTPError',
|
||||
'SFTPAttributes',
|
||||
'SFTPServerInterface',
|
||||
'ServerInterface',
|
||||
'BufferedFile',
|
||||
'Agent',
|
||||
'AgentKey',
|
||||
'HostKeys',
|
||||
'SSHConfig',
|
||||
'util' ]
|
||||
@ -1,148 +0,0 @@
|
||||
# Copyright (C) 2003-2007 John Rochester <john@jrochester.org>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
SSH Agent interface for Unix clients.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
from paramiko.pkey import PKey
|
||||
|
||||
|
||||
SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \
|
||||
SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15)
|
||||
|
||||
|
||||
class Agent:
|
||||
"""
|
||||
Client interface for using private keys from an SSH agent running on the
|
||||
local machine. If an SSH agent is running, this class can be used to
|
||||
connect to it and retreive L{PKey} objects which can be used when
|
||||
attempting to authenticate to remote SSH servers.
|
||||
|
||||
Because the SSH agent protocol uses environment variables and unix-domain
|
||||
sockets, this probably doesn't work on Windows. It does work on most
|
||||
posix platforms though (Linux and MacOS X, for example).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Open a session with the local machine's SSH agent, if one is running.
|
||||
If no agent is running, initialization will succeed, but L{get_keys}
|
||||
will return an empty tuple.
|
||||
|
||||
@raise SSHException: if an SSH agent is found, but speaks an
|
||||
incompatible protocol
|
||||
"""
|
||||
self.keys = ()
|
||||
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
conn.connect(os.environ['SSH_AUTH_SOCK'])
|
||||
except:
|
||||
# probably a dangling env var: the ssh agent is gone
|
||||
return
|
||||
self.conn = conn
|
||||
elif sys.platform == 'win32':
|
||||
import win_pageant
|
||||
if win_pageant.can_talk_to_agent():
|
||||
self.conn = win_pageant.PageantConnection()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# no agent support
|
||||
return
|
||||
|
||||
ptype, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES))
|
||||
if ptype != SSH2_AGENT_IDENTITIES_ANSWER:
|
||||
raise SSHException('could not get keys from ssh-agent')
|
||||
keys = []
|
||||
for i in range(result.get_int()):
|
||||
keys.append(AgentKey(self, result.get_string()))
|
||||
result.get_string()
|
||||
self.keys = tuple(keys)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the SSH agent connection.
|
||||
"""
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
self.keys = ()
|
||||
|
||||
def get_keys(self):
|
||||
"""
|
||||
Return the list of keys available through the SSH agent, if any. If
|
||||
no SSH agent was running (or it couldn't be contacted), an empty list
|
||||
will be returned.
|
||||
|
||||
@return: a list of keys available on the SSH agent
|
||||
@rtype: tuple of L{AgentKey}
|
||||
"""
|
||||
return self.keys
|
||||
|
||||
def _send_message(self, msg):
|
||||
msg = str(msg)
|
||||
self.conn.send(struct.pack('>I', len(msg)) + msg)
|
||||
l = self._read_all(4)
|
||||
msg = Message(self._read_all(struct.unpack('>I', l)[0]))
|
||||
return ord(msg.get_byte()), msg
|
||||
|
||||
def _read_all(self, wanted):
|
||||
result = self.conn.recv(wanted)
|
||||
while len(result) < wanted:
|
||||
if len(result) == 0:
|
||||
raise SSHException('lost ssh-agent')
|
||||
extra = self.conn.recv(wanted - len(result))
|
||||
if len(extra) == 0:
|
||||
raise SSHException('lost ssh-agent')
|
||||
result += extra
|
||||
return result
|
||||
|
||||
|
||||
class AgentKey(PKey):
|
||||
"""
|
||||
Private key held in a local SSH agent. This type of key can be used for
|
||||
authenticating to a remote server (signing). Most other key operations
|
||||
work as expected.
|
||||
"""
|
||||
|
||||
def __init__(self, agent, blob):
|
||||
self.agent = agent
|
||||
self.blob = blob
|
||||
self.name = Message(blob).get_string()
|
||||
|
||||
def __str__(self):
|
||||
return self.blob
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def sign_ssh_data(self, randpool, data):
|
||||
msg = Message()
|
||||
msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
|
||||
msg.add_string(self.blob)
|
||||
msg.add_string(data)
|
||||
msg.add_int(0)
|
||||
ptype, result = self.agent._send_message(msg)
|
||||
if ptype != SSH2_AGENT_SIGN_RESPONSE:
|
||||
raise SSHException('key cannot be used for signing')
|
||||
return result.get_string()
|
||||
@ -1,423 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{AuthHandler}
|
||||
"""
|
||||
|
||||
import threading
|
||||
import weakref
|
||||
|
||||
# this helps freezing utils
|
||||
import encodings.utf_8
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.ssh_exception import SSHException, AuthenticationException, \
|
||||
BadAuthenticationType, PartialAuthentication
|
||||
from paramiko.server import InteractiveQuery
|
||||
|
||||
|
||||
class AuthHandler (object):
|
||||
"""
|
||||
Internal class to handle the mechanics of authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = weakref.proxy(transport)
|
||||
self.username = None
|
||||
self.authenticated = False
|
||||
self.auth_event = None
|
||||
self.auth_method = ''
|
||||
self.password = None
|
||||
self.private_key = None
|
||||
self.interactive_handler = None
|
||||
self.submethods = None
|
||||
# for server mode:
|
||||
self.auth_username = None
|
||||
self.auth_fail_count = 0
|
||||
|
||||
def is_authenticated(self):
|
||||
return self.authenticated
|
||||
|
||||
def get_username(self):
|
||||
if self.transport.server_mode:
|
||||
return self.auth_username
|
||||
else:
|
||||
return self.username
|
||||
|
||||
def auth_none(self, username, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = 'none'
|
||||
self.username = username
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_publickey(self, username, key, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = 'publickey'
|
||||
self.username = username
|
||||
self.private_key = key
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_password(self, username, password, event):
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = 'password'
|
||||
self.username = username
|
||||
self.password = password
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def auth_interactive(self, username, handler, event, submethods=''):
|
||||
"""
|
||||
response_list = handler(title, instructions, prompt_list)
|
||||
"""
|
||||
self.transport.lock.acquire()
|
||||
try:
|
||||
self.auth_event = event
|
||||
self.auth_method = 'keyboard-interactive'
|
||||
self.username = username
|
||||
self.interactive_handler = handler
|
||||
self.submethods = submethods
|
||||
self._request_auth()
|
||||
finally:
|
||||
self.transport.lock.release()
|
||||
|
||||
def abort(self):
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _request_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_SERVICE_REQUEST))
|
||||
m.add_string('ssh-userauth')
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _disconnect_service_not_available(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_DISCONNECT))
|
||||
m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
|
||||
m.add_string('Service not available')
|
||||
m.add_string('en')
|
||||
self.transport._send_message(m)
|
||||
self.transport.close()
|
||||
|
||||
def _disconnect_no_more_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_DISCONNECT))
|
||||
m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
|
||||
m.add_string('No more auth methods available')
|
||||
m.add_string('en')
|
||||
self.transport._send_message(m)
|
||||
self.transport.close()
|
||||
|
||||
def _get_session_blob(self, key, service, username):
|
||||
m = Message()
|
||||
m.add_string(self.transport.session_id)
|
||||
m.add_byte(chr(MSG_USERAUTH_REQUEST))
|
||||
m.add_string(username)
|
||||
m.add_string(service)
|
||||
m.add_string('publickey')
|
||||
m.add_boolean(1)
|
||||
m.add_string(key.get_name())
|
||||
m.add_string(str(key))
|
||||
return str(m)
|
||||
|
||||
def wait_for_response(self, event):
|
||||
while True:
|
||||
event.wait(0.1)
|
||||
if not self.transport.is_active():
|
||||
e = self.transport.get_exception()
|
||||
if (e is None) or issubclass(e.__class__, EOFError):
|
||||
e = AuthenticationException('Authentication failed.')
|
||||
raise e
|
||||
if event.isSet():
|
||||
break
|
||||
if not self.is_authenticated():
|
||||
e = self.transport.get_exception()
|
||||
if e is None:
|
||||
e = AuthenticationException('Authentication failed.')
|
||||
# this is horrible. python Exception isn't yet descended from
|
||||
# object, so type(e) won't work. :(
|
||||
if issubclass(e.__class__, PartialAuthentication):
|
||||
return e.allowed_types
|
||||
raise e
|
||||
return []
|
||||
|
||||
def _parse_service_request(self, m):
|
||||
service = m.get_string()
|
||||
if self.transport.server_mode and (service == 'ssh-userauth'):
|
||||
# accepted
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_SERVICE_ACCEPT))
|
||||
m.add_string(service)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
# dunno this one
|
||||
self._disconnect_service_not_available()
|
||||
|
||||
def _parse_service_accept(self, m):
|
||||
service = m.get_string()
|
||||
if service == 'ssh-userauth':
|
||||
self.transport._log(DEBUG, 'userauth is OK')
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_REQUEST))
|
||||
m.add_string(self.username)
|
||||
m.add_string('ssh-connection')
|
||||
m.add_string(self.auth_method)
|
||||
if self.auth_method == 'password':
|
||||
m.add_boolean(False)
|
||||
password = self.password
|
||||
if isinstance(password, unicode):
|
||||
password = password.encode('UTF-8')
|
||||
m.add_string(password)
|
||||
elif self.auth_method == 'publickey':
|
||||
m.add_boolean(True)
|
||||
m.add_string(self.private_key.get_name())
|
||||
m.add_string(str(self.private_key))
|
||||
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
|
||||
sig = self.private_key.sign_ssh_data(self.transport.randpool, blob)
|
||||
m.add_string(str(sig))
|
||||
elif self.auth_method == 'keyboard-interactive':
|
||||
m.add_string('')
|
||||
m.add_string(self.submethods)
|
||||
elif self.auth_method == 'none':
|
||||
pass
|
||||
else:
|
||||
raise SSHException('Unknown auth method "%s"' % self.auth_method)
|
||||
self.transport._send_message(m)
|
||||
else:
|
||||
self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
|
||||
|
||||
def _send_auth_result(self, username, method, result):
|
||||
# okay, send result
|
||||
m = Message()
|
||||
if result == AUTH_SUCCESSFUL:
|
||||
self.transport._log(INFO, 'Auth granted (%s).' % method)
|
||||
m.add_byte(chr(MSG_USERAUTH_SUCCESS))
|
||||
self.authenticated = True
|
||||
else:
|
||||
self.transport._log(INFO, 'Auth rejected (%s).' % method)
|
||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
||||
m.add_string(self.transport.server_object.get_allowed_auths(username))
|
||||
if result == AUTH_PARTIALLY_SUCCESSFUL:
|
||||
m.add_boolean(1)
|
||||
else:
|
||||
m.add_boolean(0)
|
||||
self.auth_fail_count += 1
|
||||
self.transport._send_message(m)
|
||||
if self.auth_fail_count >= 10:
|
||||
self._disconnect_no_more_auth()
|
||||
if result == AUTH_SUCCESSFUL:
|
||||
self.transport._auth_trigger()
|
||||
|
||||
def _interactive_query(self, q):
|
||||
# make interactive query instead of response
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_INFO_REQUEST))
|
||||
m.add_string(q.name)
|
||||
m.add_string(q.instructions)
|
||||
m.add_string('')
|
||||
m.add_int(len(q.prompts))
|
||||
for p in q.prompts:
|
||||
m.add_string(p[0])
|
||||
m.add_boolean(p[1])
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _parse_userauth_request(self, m):
|
||||
if not self.transport.server_mode:
|
||||
# er, uh... what?
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
||||
m.add_string('none')
|
||||
m.add_boolean(0)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
if self.authenticated:
|
||||
# ignore
|
||||
return
|
||||
username = m.get_string()
|
||||
service = m.get_string()
|
||||
method = m.get_string()
|
||||
self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
|
||||
if service != 'ssh-connection':
|
||||
self._disconnect_service_not_available()
|
||||
return
|
||||
if (self.auth_username is not None) and (self.auth_username != username):
|
||||
self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight')
|
||||
self._disconnect_no_more_auth()
|
||||
return
|
||||
self.auth_username = username
|
||||
|
||||
if method == 'none':
|
||||
result = self.transport.server_object.check_auth_none(username)
|
||||
elif method == 'password':
|
||||
changereq = m.get_boolean()
|
||||
password = m.get_string()
|
||||
try:
|
||||
password = password.decode('UTF-8')
|
||||
except UnicodeError:
|
||||
# some clients/servers expect non-utf-8 passwords!
|
||||
# in this case, just return the raw byte string.
|
||||
pass
|
||||
if changereq:
|
||||
# always treated as failure, since we don't support changing passwords, but collect
|
||||
# the list of valid auth types from the callback anyway
|
||||
self.transport._log(DEBUG, 'Auth request to change passwords (rejected)')
|
||||
newpassword = m.get_string()
|
||||
try:
|
||||
newpassword = newpassword.decode('UTF-8', 'replace')
|
||||
except UnicodeError:
|
||||
pass
|
||||
result = AUTH_FAILED
|
||||
else:
|
||||
result = self.transport.server_object.check_auth_password(username, password)
|
||||
elif method == 'publickey':
|
||||
sig_attached = m.get_boolean()
|
||||
keytype = m.get_string()
|
||||
keyblob = m.get_string()
|
||||
try:
|
||||
key = self.transport._key_info[keytype](Message(keyblob))
|
||||
except SSHException, e:
|
||||
self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e))
|
||||
key = None
|
||||
except:
|
||||
self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key')
|
||||
key = None
|
||||
if key is None:
|
||||
self._disconnect_no_more_auth()
|
||||
return
|
||||
# first check if this key is okay... if not, we can skip the verify
|
||||
result = self.transport.server_object.check_auth_publickey(username, key)
|
||||
if result != AUTH_FAILED:
|
||||
# key is okay, verify it
|
||||
if not sig_attached:
|
||||
# client wants to know if this key is acceptable, before it
|
||||
# signs anything... send special "ok" message
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_PK_OK))
|
||||
m.add_string(keytype)
|
||||
m.add_string(keyblob)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
sig = Message(m.get_string())
|
||||
blob = self._get_session_blob(key, service, username)
|
||||
if not key.verify_ssh_sig(blob, sig):
|
||||
self.transport._log(INFO, 'Auth rejected: invalid signature')
|
||||
result = AUTH_FAILED
|
||||
elif method == 'keyboard-interactive':
|
||||
lang = m.get_string()
|
||||
submethods = m.get_string()
|
||||
result = self.transport.server_object.check_auth_interactive(username, submethods)
|
||||
if isinstance(result, InteractiveQuery):
|
||||
# make interactive query instead of response
|
||||
self._interactive_query(result)
|
||||
return
|
||||
else:
|
||||
result = self.transport.server_object.check_auth_none(username)
|
||||
# okay, send result
|
||||
self._send_auth_result(username, method, result)
|
||||
|
||||
def _parse_userauth_success(self, m):
|
||||
self.transport._log(INFO, 'Authentication (%s) successful!' % self.auth_method)
|
||||
self.authenticated = True
|
||||
self.transport._auth_trigger()
|
||||
if self.auth_event != None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_failure(self, m):
|
||||
authlist = m.get_list()
|
||||
partial = m.get_boolean()
|
||||
if partial:
|
||||
self.transport._log(INFO, 'Authentication continues...')
|
||||
self.transport._log(DEBUG, 'Methods: ' + str(authlist))
|
||||
self.transport.saved_exception = PartialAuthentication(authlist)
|
||||
elif self.auth_method not in authlist:
|
||||
self.transport._log(DEBUG, 'Authentication type (%s) not permitted.' % self.auth_method)
|
||||
self.transport._log(DEBUG, 'Allowed methods: ' + str(authlist))
|
||||
self.transport.saved_exception = BadAuthenticationType('Bad authentication type', authlist)
|
||||
else:
|
||||
self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)
|
||||
self.authenticated = False
|
||||
self.username = None
|
||||
if self.auth_event != None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_banner(self, m):
|
||||
banner = m.get_string()
|
||||
lang = m.get_string()
|
||||
self.transport._log(INFO, 'Auth banner: ' + banner)
|
||||
# who cares.
|
||||
|
||||
def _parse_userauth_info_request(self, m):
|
||||
if self.auth_method != 'keyboard-interactive':
|
||||
raise SSHException('Illegal info request from server')
|
||||
title = m.get_string()
|
||||
instructions = m.get_string()
|
||||
m.get_string() # lang
|
||||
prompts = m.get_int()
|
||||
prompt_list = []
|
||||
for i in range(prompts):
|
||||
prompt_list.append((m.get_string(), m.get_boolean()))
|
||||
response_list = self.interactive_handler(title, instructions, prompt_list)
|
||||
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
|
||||
m.add_int(len(response_list))
|
||||
for r in response_list:
|
||||
m.add_string(r)
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _parse_userauth_info_response(self, m):
|
||||
if not self.transport.server_mode:
|
||||
raise SSHException('Illegal info response from server')
|
||||
n = m.get_int()
|
||||
responses = []
|
||||
for i in range(n):
|
||||
responses.append(m.get_string())
|
||||
result = self.transport.server_object.check_auth_interactive_response(responses)
|
||||
if isinstance(type(result), InteractiveQuery):
|
||||
# make interactive query instead of response
|
||||
self._interactive_query(result)
|
||||
return
|
||||
self._send_auth_result(self.auth_username, 'keyboard-interactive', result)
|
||||
|
||||
|
||||
_handler_table = {
|
||||
MSG_SERVICE_REQUEST: _parse_service_request,
|
||||
MSG_SERVICE_ACCEPT: _parse_service_accept,
|
||||
MSG_USERAUTH_REQUEST: _parse_userauth_request,
|
||||
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
|
||||
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
|
||||
MSG_USERAUTH_BANNER: _parse_userauth_banner,
|
||||
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
|
||||
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
|
||||
}
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
|
||||
import util
|
||||
|
||||
|
||||
class BERException (Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BER(object):
|
||||
"""
|
||||
Robey's tiny little attempt at a BER decoder.
|
||||
"""
|
||||
|
||||
def __init__(self, content=''):
|
||||
self.content = content
|
||||
self.idx = 0
|
||||
|
||||
def __str__(self):
|
||||
return self.content
|
||||
|
||||
def __repr__(self):
|
||||
return 'BER(\'' + repr(self.content) + '\')'
|
||||
|
||||
def decode(self):
|
||||
return self.decode_next()
|
||||
|
||||
def decode_next(self):
|
||||
if self.idx >= len(self.content):
|
||||
return None
|
||||
ident = ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if (ident & 31) == 31:
|
||||
# identifier > 30
|
||||
ident = 0
|
||||
while self.idx < len(self.content):
|
||||
t = ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
ident = (ident << 7) | (t & 0x7f)
|
||||
if not (t & 0x80):
|
||||
break
|
||||
if self.idx >= len(self.content):
|
||||
return None
|
||||
# now fetch length
|
||||
size = ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if size & 0x80:
|
||||
# more complimicated...
|
||||
# FIXME: theoretically should handle indefinite-length (0x80)
|
||||
t = size & 0x7f
|
||||
if self.idx + t > len(self.content):
|
||||
return None
|
||||
size = util.inflate_long(self.content[self.idx : self.idx + t], True)
|
||||
self.idx += t
|
||||
if self.idx + size > len(self.content):
|
||||
# can't fit
|
||||
return None
|
||||
data = self.content[self.idx : self.idx + size]
|
||||
self.idx += size
|
||||
# now switch on id
|
||||
if ident == 0x30:
|
||||
# sequence
|
||||
return self.decode_sequence(data)
|
||||
elif ident == 2:
|
||||
# int
|
||||
return util.inflate_long(data)
|
||||
else:
|
||||
# 1: boolean (00 false, otherwise true)
|
||||
raise BERException('Unknown ber encoding type %d (robey is lazy)' % ident)
|
||||
|
||||
def decode_sequence(data):
|
||||
out = []
|
||||
b = BER(data)
|
||||
while True:
|
||||
x = b.decode_next()
|
||||
if x is None:
|
||||
break
|
||||
out.append(x)
|
||||
return out
|
||||
decode_sequence = staticmethod(decode_sequence)
|
||||
|
||||
def encode_tlv(self, ident, val):
|
||||
# no need to support ident > 31 here
|
||||
self.content += chr(ident)
|
||||
if len(val) > 0x7f:
|
||||
lenstr = util.deflate_long(len(val))
|
||||
self.content += chr(0x80 + len(lenstr)) + lenstr
|
||||
else:
|
||||
self.content += chr(len(val))
|
||||
self.content += val
|
||||
|
||||
def encode(self, x):
|
||||
if type(x) is bool:
|
||||
if x:
|
||||
self.encode_tlv(1, '\xff')
|
||||
else:
|
||||
self.encode_tlv(1, '\x00')
|
||||
elif (type(x) is int) or (type(x) is long):
|
||||
self.encode_tlv(2, util.deflate_long(x))
|
||||
elif type(x) is str:
|
||||
self.encode_tlv(4, x)
|
||||
elif (type(x) is list) or (type(x) is tuple):
|
||||
self.encode_tlv(0x30, self.encode_sequence(x))
|
||||
else:
|
||||
raise BERException('Unknown type for encoding: %s' % repr(type(x)))
|
||||
|
||||
def encode_sequence(data):
|
||||
b = BER()
|
||||
for item in data:
|
||||
b.encode(item)
|
||||
return str(b)
|
||||
encode_sequence = staticmethod(encode_sequence)
|
||||
@ -1,197 +0,0 @@
|
||||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Attempt to generalize the "feeder" part of a Channel: an object which can be
|
||||
read from and closed, but is reading from a buffer fed by another thread. The
|
||||
read operations are blocking and can have a timeout set.
|
||||
"""
|
||||
|
||||
import array
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
class PipeTimeout (IOError):
|
||||
"""
|
||||
Indicates that a timeout was reached on a read from a L{BufferedPipe}.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BufferedPipe (object):
|
||||
"""
|
||||
A buffer that obeys normal read (with timeout) & close semantics for a
|
||||
file or socket, but is fed data from another thread. This is used by
|
||||
L{Channel}.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._lock = threading.Lock()
|
||||
self._cv = threading.Condition(self._lock)
|
||||
self._event = None
|
||||
self._buffer = array.array('B')
|
||||
self._closed = False
|
||||
|
||||
def set_event(self, event):
|
||||
"""
|
||||
Set an event on this buffer. When data is ready to be read (or the
|
||||
buffer has been closed), the event will be set. When no data is
|
||||
ready, the event will be cleared.
|
||||
|
||||
@param event: the event to set/clear
|
||||
@type event: Event
|
||||
"""
|
||||
self._event = event
|
||||
if len(self._buffer) > 0:
|
||||
event.set()
|
||||
else:
|
||||
event.clear()
|
||||
|
||||
def feed(self, data):
|
||||
"""
|
||||
Feed new data into this pipe. This method is assumed to be called
|
||||
from a separate thread, so synchronization is done.
|
||||
|
||||
@param data: the data to add
|
||||
@type data: str
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if self._event is not None:
|
||||
self._event.set()
|
||||
self._buffer.fromstring(data)
|
||||
self._cv.notifyAll()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def read_ready(self):
|
||||
"""
|
||||
Returns true if data is buffered and ready to be read from this
|
||||
feeder. A C{False} result does not mean that the feeder has closed;
|
||||
it means you may need to wait before more data arrives.
|
||||
|
||||
@return: C{True} if a L{read} call would immediately return at least
|
||||
one byte; C{False} otherwise.
|
||||
@rtype: bool
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if len(self._buffer) == 0:
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def read(self, nbytes, timeout=None):
|
||||
"""
|
||||
Read data from the pipe. The return value is a string representing
|
||||
the data received. The maximum amount of data to be received at once
|
||||
is specified by C{nbytes}. If a string of length zero is returned,
|
||||
the pipe has been closed.
|
||||
|
||||
The optional C{timeout} argument can be a nonnegative float expressing
|
||||
seconds, or C{None} for no timeout. If a float is given, a
|
||||
C{PipeTimeout} will be raised if the timeout period value has
|
||||
elapsed before any data arrives.
|
||||
|
||||
@param nbytes: maximum number of bytes to read
|
||||
@type nbytes: int
|
||||
@param timeout: maximum seconds to wait (or C{None}, the default, to
|
||||
wait forever)
|
||||
@type timeout: float
|
||||
@return: data
|
||||
@rtype: str
|
||||
|
||||
@raise PipeTimeout: if a timeout was specified and no data was ready
|
||||
before that timeout
|
||||
"""
|
||||
out = ''
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if len(self._buffer) == 0:
|
||||
if self._closed:
|
||||
return out
|
||||
# should we block?
|
||||
if timeout == 0.0:
|
||||
raise PipeTimeout()
|
||||
# loop here in case we get woken up but a different thread has
|
||||
# grabbed everything in the buffer.
|
||||
while (len(self._buffer) == 0) and not self._closed:
|
||||
then = time.time()
|
||||
self._cv.wait(timeout)
|
||||
if timeout is not None:
|
||||
timeout -= time.time() - then
|
||||
if timeout <= 0.0:
|
||||
raise PipeTimeout()
|
||||
|
||||
# something's in the buffer and we have the lock!
|
||||
if len(self._buffer) <= nbytes:
|
||||
out = self._buffer.tostring()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
else:
|
||||
out = self._buffer[:nbytes].tostring()
|
||||
del self._buffer[:nbytes]
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return out
|
||||
|
||||
def empty(self):
|
||||
"""
|
||||
Clear out the buffer and return all data that was in it.
|
||||
|
||||
@return: any data that was in the buffer prior to clearing it out
|
||||
@rtype: str
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
out = self._buffer.tostring()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
return out
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close this pipe object. Future calls to L{read} after the buffer
|
||||
has been emptied will return immediately with an empty string.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._closed = True
|
||||
self._cv.notifyAll()
|
||||
if self._event is not None:
|
||||
self._event.set()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Return the number of bytes buffered.
|
||||
|
||||
@return: number of bytes bufferes
|
||||
@rtype: int
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return len(self._buffer)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,483 +0,0 @@
|
||||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SSHClient}.
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import getpass
|
||||
import os
|
||||
import socket
|
||||
import warnings
|
||||
|
||||
from paramiko.agent import Agent
|
||||
from paramiko.common import *
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.hostkeys import HostKeys
|
||||
from paramiko.resource import ResourceManager
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.ssh_exception import SSHException, BadHostKeyException
|
||||
from paramiko.transport import Transport
|
||||
|
||||
|
||||
SSH_PORT = 22
|
||||
|
||||
class MissingHostKeyPolicy (object):
|
||||
"""
|
||||
Interface for defining the policy that L{SSHClient} should use when the
|
||||
SSH server's hostname is not in either the system host keys or the
|
||||
application's keys. Pre-made classes implement policies for automatically
|
||||
adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
|
||||
and for automatically rejecting the key (L{RejectPolicy}).
|
||||
|
||||
This function may be used to ask the user to verify the key, for example.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
"""
|
||||
Called when an L{SSHClient} receives a server key for a server that
|
||||
isn't in either the system or local L{HostKeys} object. To accept
|
||||
the key, simply return. To reject, raised an exception (which will
|
||||
be passed to the calling application).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AutoAddPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
if client._host_keys_filename is not None:
|
||||
client.save_host_keys(client._host_keys_filename)
|
||||
client._log(DEBUG, 'Adding %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
||||
|
||||
class RejectPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically rejecting the unknown hostname & key. This is
|
||||
used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
raise SSHException('Unknown server %s' % hostname)
|
||||
|
||||
|
||||
class WarningPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for logging a python-style warning for an unknown host key, but
|
||||
accepting it. This is used by L{SSHClient}.
|
||||
"""
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
warnings.warn('Unknown %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
||||
|
||||
class SSHClient (object):
|
||||
"""
|
||||
A high-level representation of a session with an SSH server. This class
|
||||
wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
|
||||
aspects of authenticating and opening channels. A typical use case is::
|
||||
|
||||
client = SSHClient()
|
||||
client.load_system_host_keys()
|
||||
client.connect('ssh.example.com')
|
||||
stdin, stdout, stderr = client.exec_command('ls -l')
|
||||
|
||||
You may pass in explicit overrides for authentication and server host key
|
||||
checking. The default mechanism is to try to use local key files or an
|
||||
SSH agent (if one is running).
|
||||
|
||||
@since: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new SSHClient.
|
||||
"""
|
||||
self._system_host_keys = HostKeys()
|
||||
self._host_keys = HostKeys()
|
||||
self._host_keys_filename = None
|
||||
self._log_channel = None
|
||||
self._policy = RejectPolicy()
|
||||
self._transport = None
|
||||
|
||||
def load_system_host_keys(self, filename=None):
|
||||
"""
|
||||
Load host keys from a system (read-only) file. Host keys read with
|
||||
this method will not be saved back by L{save_host_keys}.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts).
|
||||
|
||||
If C{filename} is left as C{None}, an attempt will be made to read
|
||||
keys from the user's local "known hosts" file, as used by OpenSSH,
|
||||
and no exception will be raised if the file can't be read. This is
|
||||
probably only useful on posix.
|
||||
|
||||
@param filename: the filename to read, or C{None}
|
||||
@type filename: str
|
||||
|
||||
@raise IOError: if a filename was provided and the file could not be
|
||||
read
|
||||
"""
|
||||
if filename is None:
|
||||
# try the user's .ssh key file, and mask exceptions
|
||||
filename = os.path.expanduser('~/.ssh/known_hosts')
|
||||
try:
|
||||
self._system_host_keys.load(filename)
|
||||
except IOError:
|
||||
pass
|
||||
return
|
||||
self._system_host_keys.load(filename)
|
||||
|
||||
def load_host_keys(self, filename):
|
||||
"""
|
||||
Load host keys from a local host-key file. Host keys read with this
|
||||
method will be checked I{after} keys loaded via L{load_system_host_keys},
|
||||
but will be saved back by L{save_host_keys} (so they can be modified).
|
||||
The missing host key policy L{AutoAddPolicy} adds keys to this set and
|
||||
saves them, when connecting to a previously-unknown server.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts). When automatically saving, the last hostname is used.
|
||||
|
||||
@param filename: the filename to read
|
||||
@type filename: str
|
||||
|
||||
@raise IOError: if the filename could not be read
|
||||
"""
|
||||
self._host_keys_filename = filename
|
||||
self._host_keys.load(filename)
|
||||
|
||||
def save_host_keys(self, filename):
|
||||
"""
|
||||
Save the host keys back to a file. Only the host keys loaded with
|
||||
L{load_host_keys} (plus any added directly) will be saved -- not any
|
||||
host keys loaded with L{load_system_host_keys}.
|
||||
|
||||
@param filename: the filename to save to
|
||||
@type filename: str
|
||||
|
||||
@raise IOError: if the file could not be written
|
||||
"""
|
||||
f = open(filename, 'w')
|
||||
f.write('# SSH host keys collected by paramiko\n')
|
||||
for hostname, keys in self._host_keys.iteritems():
|
||||
for keytype, key in keys.iteritems():
|
||||
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
|
||||
f.close()
|
||||
|
||||
def get_host_keys(self):
|
||||
"""
|
||||
Get the local L{HostKeys} object. This can be used to examine the
|
||||
local host keys or change them.
|
||||
|
||||
@return: the local host keys
|
||||
@rtype: L{HostKeys}
|
||||
"""
|
||||
return self._host_keys
|
||||
|
||||
def set_log_channel(self, name):
|
||||
"""
|
||||
Set the channel for logging. The default is C{"paramiko.transport"}
|
||||
but it can be set to anything you want.
|
||||
|
||||
@param name: new channel name for logging
|
||||
@type name: str
|
||||
"""
|
||||
self._log_channel = name
|
||||
|
||||
def set_missing_host_key_policy(self, policy):
|
||||
"""
|
||||
Set the policy to use when connecting to a server that doesn't have a
|
||||
host key in either the system or local L{HostKeys} objects. The
|
||||
default policy is to reject all unknown servers (using L{RejectPolicy}).
|
||||
You may substitute L{AutoAddPolicy} or write your own policy class.
|
||||
|
||||
@param policy: the policy to use when receiving a host key from a
|
||||
previously-unknown server
|
||||
@type policy: L{MissingHostKeyPolicy}
|
||||
"""
|
||||
self._policy = policy
|
||||
|
||||
def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
|
||||
key_filename=None, timeout=None, allow_agent=True, look_for_keys=True):
|
||||
"""
|
||||
Connect to an SSH server and authenticate to it. The server's host key
|
||||
is checked against the system host keys (see L{load_system_host_keys})
|
||||
and any local host keys (L{load_host_keys}). If the server's hostname
|
||||
is not found in either set of host keys, the missing host key policy
|
||||
is used (see L{set_missing_host_key_policy}). The default policy is
|
||||
to reject the key and raise an L{SSHException}.
|
||||
|
||||
Authentication is attempted in the following order of priority:
|
||||
|
||||
- The C{pkey} or C{key_filename} passed in (if any)
|
||||
- Any key we can find through an SSH agent
|
||||
- Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
|
||||
- Plain username/password auth, if a password was given
|
||||
|
||||
If a private key requires a password to unlock it, and a password is
|
||||
passed in, that password will be used to attempt to unlock the key.
|
||||
|
||||
@param hostname: the server to connect to
|
||||
@type hostname: str
|
||||
@param port: the server port to connect to
|
||||
@type port: int
|
||||
@param username: the username to authenticate as (defaults to the
|
||||
current local username)
|
||||
@type username: str
|
||||
@param password: a password to use for authentication or for unlocking
|
||||
a private key
|
||||
@type password: str
|
||||
@param pkey: an optional private key to use for authentication
|
||||
@type pkey: L{PKey}
|
||||
@param key_filename: the filename, or list of filenames, of optional
|
||||
private key(s) to try for authentication
|
||||
@type key_filename: str or list(str)
|
||||
@param timeout: an optional timeout (in seconds) for the TCP connect
|
||||
@type timeout: float
|
||||
@param allow_agent: set to False to disable connecting to the SSH agent
|
||||
@type allow_agent: bool
|
||||
@param look_for_keys: set to False to disable searching for discoverable
|
||||
private key files in C{~/.ssh/}
|
||||
@type look_for_keys: bool
|
||||
|
||||
@raise BadHostKeyException: if the server's host key could not be
|
||||
verified
|
||||
@raise AuthenticationException: if authentication failed
|
||||
@raise SSHException: if there was any other error connecting or
|
||||
establishing an SSH session
|
||||
@raise socket.error: if a socket error occurred while connecting
|
||||
"""
|
||||
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
|
||||
if socktype == socket.SOCK_STREAM:
|
||||
af = family
|
||||
addr = sockaddr
|
||||
break
|
||||
else:
|
||||
raise SSHException('No suitable address family for %s' % hostname)
|
||||
sock = socket.socket(af, socket.SOCK_STREAM)
|
||||
if timeout is not None:
|
||||
try:
|
||||
sock.settimeout(timeout)
|
||||
except:
|
||||
pass
|
||||
sock.connect(addr)
|
||||
t = self._transport = Transport(sock)
|
||||
|
||||
if self._log_channel is not None:
|
||||
t.set_log_channel(self._log_channel)
|
||||
t.start_client()
|
||||
ResourceManager.register(self, t)
|
||||
|
||||
server_key = t.get_remote_server_key()
|
||||
keytype = server_key.get_name()
|
||||
|
||||
if port == SSH_PORT:
|
||||
server_hostkey_name = hostname
|
||||
else:
|
||||
server_hostkey_name = "[%s]:%d" % (hostname, port)
|
||||
our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None)
|
||||
if our_server_key is None:
|
||||
our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None)
|
||||
if our_server_key is None:
|
||||
# will raise exception if the key is rejected; let that fall out
|
||||
self._policy.missing_host_key(self, server_hostkey_name, server_key)
|
||||
# if the callback returns, assume the key is ok
|
||||
our_server_key = server_key
|
||||
|
||||
if server_key != our_server_key:
|
||||
raise BadHostKeyException(hostname, server_key, our_server_key)
|
||||
|
||||
if username is None:
|
||||
username = getpass.getuser()
|
||||
|
||||
if key_filename is None:
|
||||
key_filenames = []
|
||||
elif isinstance(key_filename, (str, unicode)):
|
||||
key_filenames = [ key_filename ]
|
||||
else:
|
||||
key_filenames = key_filename
|
||||
self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close this SSHClient and its underlying L{Transport}.
|
||||
"""
|
||||
if self._transport is None:
|
||||
return
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
|
||||
def exec_command(self, command, bufsize=-1):
|
||||
"""
|
||||
Execute a command on the SSH server. A new L{Channel} is opened and
|
||||
the requested command is executed. The command's input and output
|
||||
streams are returned as python C{file}-like objects representing
|
||||
stdin, stdout, and stderr.
|
||||
|
||||
@param command: the command to execute
|
||||
@type command: str
|
||||
@param bufsize: interpreted the same way as by the built-in C{file()} function in python
|
||||
@type bufsize: int
|
||||
@return: the stdin, stdout, and stderr of the executing command
|
||||
@rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
|
||||
|
||||
@raise SSHException: if the server fails to execute the command
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
chan.exec_command(command)
|
||||
stdin = chan.makefile('wb', bufsize)
|
||||
stdout = chan.makefile('rb', bufsize)
|
||||
stderr = chan.makefile_stderr('rb', bufsize)
|
||||
return stdin, stdout, stderr
|
||||
|
||||
def invoke_shell(self, term='vt100', width=80, height=24):
|
||||
"""
|
||||
Start an interactive shell session on the SSH server. A new L{Channel}
|
||||
is opened and connected to a pseudo-terminal using the requested
|
||||
terminal type and size.
|
||||
|
||||
@param term: the terminal type to emulate (for example, C{"vt100"})
|
||||
@type term: str
|
||||
@param width: the width (in characters) of the terminal window
|
||||
@type width: int
|
||||
@param height: the height (in characters) of the terminal window
|
||||
@type height: int
|
||||
@return: a new channel connected to the remote shell
|
||||
@rtype: L{Channel}
|
||||
|
||||
@raise SSHException: if the server fails to invoke a shell
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
chan.get_pty(term, width, height)
|
||||
chan.invoke_shell()
|
||||
return chan
|
||||
|
||||
def open_sftp(self):
|
||||
"""
|
||||
Open an SFTP session on the SSH server.
|
||||
|
||||
@return: a new SFTP session object
|
||||
@rtype: L{SFTPClient}
|
||||
"""
|
||||
return self._transport.open_sftp_client()
|
||||
|
||||
def get_transport(self):
|
||||
"""
|
||||
Return the underlying L{Transport} object for this SSH connection.
|
||||
This can be used to perform lower-level tasks, like opening specific
|
||||
kinds of channels.
|
||||
|
||||
@return: the Transport for this connection
|
||||
@rtype: L{Transport}
|
||||
"""
|
||||
return self._transport
|
||||
|
||||
def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
|
||||
"""
|
||||
Try, in order:
|
||||
|
||||
- The key passed in, if one was passed in.
|
||||
- Any key we can find through an SSH agent (if allowed).
|
||||
- Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
|
||||
- Plain username/password auth, if a password was given.
|
||||
|
||||
(The password might be needed to unlock a private key.)
|
||||
"""
|
||||
saved_exception = None
|
||||
|
||||
if pkey is not None:
|
||||
try:
|
||||
self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
|
||||
self._transport.auth_publickey(username, pkey)
|
||||
return
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
|
||||
for key_filename in key_filenames:
|
||||
for pkey_class in (RSAKey, DSSKey):
|
||||
try:
|
||||
key = pkey_class.from_private_key_file(key_filename, password)
|
||||
self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
|
||||
self._transport.auth_publickey(username, key)
|
||||
return
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
|
||||
if allow_agent:
|
||||
for key in Agent().get_keys():
|
||||
try:
|
||||
self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
|
||||
self._transport.auth_publickey(username, key)
|
||||
return
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
|
||||
keyfiles = []
|
||||
rsa_key = os.path.expanduser('~/.ssh/id_rsa')
|
||||
dsa_key = os.path.expanduser('~/.ssh/id_dsa')
|
||||
if os.path.isfile(rsa_key):
|
||||
keyfiles.append((RSAKey, rsa_key))
|
||||
if os.path.isfile(dsa_key):
|
||||
keyfiles.append((DSSKey, dsa_key))
|
||||
# look in ~/ssh/ for windows users:
|
||||
rsa_key = os.path.expanduser('~/ssh/id_rsa')
|
||||
dsa_key = os.path.expanduser('~/ssh/id_dsa')
|
||||
if os.path.isfile(rsa_key):
|
||||
keyfiles.append((RSAKey, rsa_key))
|
||||
if os.path.isfile(dsa_key):
|
||||
keyfiles.append((DSSKey, dsa_key))
|
||||
|
||||
if not look_for_keys:
|
||||
keyfiles = []
|
||||
|
||||
for pkey_class, filename in keyfiles:
|
||||
try:
|
||||
key = pkey_class.from_private_key_file(filename, password)
|
||||
self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
|
||||
self._transport.auth_publickey(username, key)
|
||||
return
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
except IOError, e:
|
||||
saved_exception = e
|
||||
|
||||
if password is not None:
|
||||
try:
|
||||
self._transport.auth_password(username, password)
|
||||
return
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
|
||||
# if we got an auth-failed exception earlier, re-raise it
|
||||
if saved_exception is not None:
|
||||
raise saved_exception
|
||||
raise SSHException('No authentication methods available')
|
||||
|
||||
def _log(self, level, msg):
|
||||
self._transport._log(level, msg)
|
||||
|
||||
@ -1,122 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Common constants and global variables.
|
||||
"""
|
||||
|
||||
MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \
|
||||
MSG_SERVICE_ACCEPT = range(1, 7)
|
||||
MSG_KEXINIT, MSG_NEWKEYS = range(20, 22)
|
||||
MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
|
||||
MSG_USERAUTH_BANNER = range(50, 54)
|
||||
MSG_USERAUTH_PK_OK = 60
|
||||
MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62)
|
||||
MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
|
||||
MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
|
||||
MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
|
||||
MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \
|
||||
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101)
|
||||
|
||||
|
||||
MSG_NAMES = {
|
||||
MSG_DISCONNECT: 'disconnect',
|
||||
MSG_IGNORE: 'ignore',
|
||||
MSG_UNIMPLEMENTED: 'unimplemented',
|
||||
MSG_DEBUG: 'debug',
|
||||
MSG_SERVICE_REQUEST: 'service-request',
|
||||
MSG_SERVICE_ACCEPT: 'service-accept',
|
||||
MSG_KEXINIT: 'kexinit',
|
||||
MSG_NEWKEYS: 'newkeys',
|
||||
30: 'kex30',
|
||||
31: 'kex31',
|
||||
32: 'kex32',
|
||||
33: 'kex33',
|
||||
34: 'kex34',
|
||||
MSG_USERAUTH_REQUEST: 'userauth-request',
|
||||
MSG_USERAUTH_FAILURE: 'userauth-failure',
|
||||
MSG_USERAUTH_SUCCESS: 'userauth-success',
|
||||
MSG_USERAUTH_BANNER: 'userauth--banner',
|
||||
MSG_USERAUTH_PK_OK: 'userauth-60(pk-ok/info-request)',
|
||||
MSG_USERAUTH_INFO_RESPONSE: 'userauth-info-response',
|
||||
MSG_GLOBAL_REQUEST: 'global-request',
|
||||
MSG_REQUEST_SUCCESS: 'request-success',
|
||||
MSG_REQUEST_FAILURE: 'request-failure',
|
||||
MSG_CHANNEL_OPEN: 'channel-open',
|
||||
MSG_CHANNEL_OPEN_SUCCESS: 'channel-open-success',
|
||||
MSG_CHANNEL_OPEN_FAILURE: 'channel-open-failure',
|
||||
MSG_CHANNEL_WINDOW_ADJUST: 'channel-window-adjust',
|
||||
MSG_CHANNEL_DATA: 'channel-data',
|
||||
MSG_CHANNEL_EXTENDED_DATA: 'channel-extended-data',
|
||||
MSG_CHANNEL_EOF: 'channel-eof',
|
||||
MSG_CHANNEL_CLOSE: 'channel-close',
|
||||
MSG_CHANNEL_REQUEST: 'channel-request',
|
||||
MSG_CHANNEL_SUCCESS: 'channel-success',
|
||||
MSG_CHANNEL_FAILURE: 'channel-failure'
|
||||
}
|
||||
|
||||
|
||||
# authentication request return codes:
|
||||
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
|
||||
|
||||
|
||||
# channel request failed reasons:
|
||||
(OPEN_SUCCEEDED,
|
||||
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
|
||||
OPEN_FAILED_CONNECT_FAILED,
|
||||
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE,
|
||||
OPEN_FAILED_RESOURCE_SHORTAGE) = range(0, 5)
|
||||
|
||||
|
||||
CONNECTION_FAILED_CODE = {
|
||||
1: 'Administratively prohibited',
|
||||
2: 'Connect failed',
|
||||
3: 'Unknown channel type',
|
||||
4: 'Resource shortage'
|
||||
}
|
||||
|
||||
|
||||
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
|
||||
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
|
||||
|
||||
from rng import StrongLockingRandomPool
|
||||
|
||||
# keep a crypto-strong PRNG nearby
|
||||
randpool = StrongLockingRandomPool()
|
||||
|
||||
import sys
|
||||
if sys.version_info < (2, 3):
|
||||
try:
|
||||
import logging
|
||||
except:
|
||||
import logging22 as logging
|
||||
import select
|
||||
PY22 = True
|
||||
|
||||
import socket
|
||||
if not hasattr(socket, 'timeout'):
|
||||
class timeout(socket.error): pass
|
||||
socket.timeout = timeout
|
||||
del timeout
|
||||
else:
|
||||
import logging
|
||||
PY22 = False
|
||||
|
||||
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
@ -1,36 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Compression implementations for a Transport.
|
||||
"""
|
||||
|
||||
import zlib
|
||||
|
||||
|
||||
class ZlibCompressor (object):
|
||||
def __init__(self):
|
||||
self.z = zlib.compressobj(9)
|
||||
|
||||
def __call__(self, data):
|
||||
return self.z.compress(data) + self.z.flush(zlib.Z_FULL_FLUSH)
|
||||
|
||||
|
||||
class ZlibDecompressor (object):
|
||||
def __init__(self):
|
||||
self.z = zlib.decompressobj()
|
||||
|
||||
def __call__(self, data):
|
||||
return self.z.decompress(data)
|
||||
@ -1,107 +0,0 @@
|
||||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SSHConfig}.
|
||||
"""
|
||||
|
||||
import fnmatch
|
||||
|
||||
|
||||
class SSHConfig (object):
|
||||
"""
|
||||
Representation of config information as stored in the format used by
|
||||
OpenSSH. Queries can be made via L{lookup}. The format is described in
|
||||
OpenSSH's C{ssh_config} man page. This class is provided primarily as a
|
||||
convenience to posix users (since the OpenSSH format is a de-facto
|
||||
standard on posix) but should work fine on Windows too.
|
||||
|
||||
@since: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new OpenSSH config object.
|
||||
"""
|
||||
self._config = [ { 'host': '*' } ]
|
||||
|
||||
def parse(self, file_obj):
|
||||
"""
|
||||
Read an OpenSSH config from the given file object.
|
||||
|
||||
@param file_obj: a file-like object to read the config file from
|
||||
@type file_obj: file
|
||||
"""
|
||||
configs = [self._config[0]]
|
||||
for line in file_obj:
|
||||
line = line.rstrip('\n').lstrip()
|
||||
if (line == '') or (line[0] == '#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip().lower()
|
||||
else:
|
||||
# find first whitespace, and split there
|
||||
i = 0
|
||||
while (i < len(line)) and not line[i].isspace():
|
||||
i += 1
|
||||
if i == len(line):
|
||||
raise Exception('Unparsable line: %r' % line)
|
||||
key = line[:i].lower()
|
||||
value = line[i:].lstrip()
|
||||
|
||||
if key == 'host':
|
||||
del configs[:]
|
||||
# the value may be multiple hosts, space-delimited
|
||||
for host in value.split():
|
||||
# do we have a pre-existing host config to append to?
|
||||
matches = [c for c in self._config if c['host'] == host]
|
||||
if len(matches) > 0:
|
||||
configs.append(matches[0])
|
||||
else:
|
||||
config = { 'host': host }
|
||||
self._config.append(config)
|
||||
configs.append(config)
|
||||
else:
|
||||
for config in configs:
|
||||
config[key] = value
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Return a dict of config options for a given hostname.
|
||||
|
||||
The host-matching rules of OpenSSH's C{ssh_config} man page are used,
|
||||
which means that all configuration options from matching host
|
||||
specifications are merged, with more specific hostmasks taking
|
||||
precedence. In other words, if C{"Port"} is set under C{"Host *"}
|
||||
and also C{"Host *.example.com"}, and the lookup is for
|
||||
C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
|
||||
will win out.
|
||||
|
||||
The keys in the returned dict are all normalized to lowercase (look for
|
||||
C{"port"}, not C{"Port"}. No other processing is done to the keys or
|
||||
values.
|
||||
|
||||
@param hostname: the hostname to lookup
|
||||
@type hostname: str
|
||||
"""
|
||||
matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
|
||||
# sort in order of shortest match (usually '*') to longest
|
||||
matches.sort(lambda x,y: cmp(len(x['host']), len(y['host'])))
|
||||
ret = {}
|
||||
for m in matches:
|
||||
ret.update(m)
|
||||
del ret['host']
|
||||
return ret
|
||||
@ -1,194 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{DSSKey}
|
||||
"""
|
||||
|
||||
from Crypto.PublicKey import DSA
|
||||
from Crypto.Hash import SHA
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
from paramiko.ber import BER, BERException
|
||||
from paramiko.pkey import PKey
|
||||
|
||||
|
||||
class DSSKey (PKey):
|
||||
"""
|
||||
Representation of a DSS key which can be used to sign an verify SSH2
|
||||
data.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None):
|
||||
self.p = None
|
||||
self.q = None
|
||||
self.g = None
|
||||
self.y = None
|
||||
self.x = None
|
||||
if file_obj is not None:
|
||||
self._from_private_key(file_obj, password)
|
||||
return
|
||||
if filename is not None:
|
||||
self._from_private_key_file(filename, password)
|
||||
return
|
||||
if (msg is None) and (data is not None):
|
||||
msg = Message(data)
|
||||
if vals is not None:
|
||||
self.p, self.q, self.g, self.y = vals
|
||||
else:
|
||||
if msg is None:
|
||||
raise SSHException('Key object may not be empty')
|
||||
if msg.get_string() != 'ssh-dss':
|
||||
raise SSHException('Invalid key')
|
||||
self.p = msg.get_mpint()
|
||||
self.q = msg.get_mpint()
|
||||
self.g = msg.get_mpint()
|
||||
self.y = msg.get_mpint()
|
||||
self.size = util.bit_length(self.p)
|
||||
|
||||
def __str__(self):
|
||||
m = Message()
|
||||
m.add_string('ssh-dss')
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.q)
|
||||
m.add_mpint(self.g)
|
||||
m.add_mpint(self.y)
|
||||
return str(m)
|
||||
|
||||
def __hash__(self):
|
||||
h = hash(self.get_name())
|
||||
h = h * 37 + hash(self.p)
|
||||
h = h * 37 + hash(self.q)
|
||||
h = h * 37 + hash(self.g)
|
||||
h = h * 37 + hash(self.y)
|
||||
# h might be a long by now...
|
||||
return hash(h)
|
||||
|
||||
def get_name(self):
|
||||
return 'ssh-dss'
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return self.x is not None
|
||||
|
||||
def sign_ssh_data(self, rpool, data):
|
||||
digest = SHA.new(data).digest()
|
||||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
|
||||
# generate a suitable k
|
||||
qsize = len(util.deflate_long(self.q, 0))
|
||||
while True:
|
||||
k = util.inflate_long(rpool.get_bytes(qsize), 1)
|
||||
if (k > 2) and (k < self.q):
|
||||
break
|
||||
r, s = dss.sign(util.inflate_long(digest, 1), k)
|
||||
m = Message()
|
||||
m.add_string('ssh-dss')
|
||||
# apparently, in rare cases, r or s may be shorter than 20 bytes!
|
||||
rstr = util.deflate_long(r, 0)
|
||||
sstr = util.deflate_long(s, 0)
|
||||
if len(rstr) < 20:
|
||||
rstr = '\x00' * (20 - len(rstr)) + rstr
|
||||
if len(sstr) < 20:
|
||||
sstr = '\x00' * (20 - len(sstr)) + sstr
|
||||
m.add_string(rstr + sstr)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if len(str(msg)) == 40:
|
||||
# spies.com bug: signature has no header
|
||||
sig = str(msg)
|
||||
else:
|
||||
kind = msg.get_string()
|
||||
if kind != 'ssh-dss':
|
||||
return 0
|
||||
sig = msg.get_string()
|
||||
|
||||
# pull out (r, s) which are NOT encoded as mpints
|
||||
sigR = util.inflate_long(sig[:20], 1)
|
||||
sigS = util.inflate_long(sig[20:], 1)
|
||||
sigM = util.inflate_long(SHA.new(data).digest(), 1)
|
||||
|
||||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
|
||||
return dss.verify(sigM, (sigR, sigS))
|
||||
|
||||
def _encode_key(self):
|
||||
if self.x is None:
|
||||
raise SSHException('Not enough key information')
|
||||
keylist = [ 0, self.p, self.q, self.g, self.y, self.x ]
|
||||
try:
|
||||
b = BER()
|
||||
b.encode(keylist)
|
||||
except BERException:
|
||||
raise SSHException('Unable to create ber encoding of key')
|
||||
return str(b)
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
self._write_private_key_file('DSA', filename, self._encode_key(), password)
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
self._write_private_key('DSA', file_obj, self._encode_key(), password)
|
||||
|
||||
def generate(bits=1024, progress_func=None):
|
||||
"""
|
||||
Generate a new private DSS key. This factory function can be used to
|
||||
generate a new host key or authentication key.
|
||||
|
||||
@param bits: number of bits the generated key should be.
|
||||
@type bits: int
|
||||
@param progress_func: an optional function to call at key points in
|
||||
key generation (used by C{pyCrypto.PublicKey}).
|
||||
@type progress_func: function
|
||||
@return: new private key
|
||||
@rtype: L{DSSKey}
|
||||
"""
|
||||
randpool.stir()
|
||||
dsa = DSA.generate(bits, randpool.get_bytes, progress_func)
|
||||
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
|
||||
key.x = dsa.x
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file('DSA', filename, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _from_private_key(self, file_obj, password):
|
||||
data = self._read_private_key('DSA', file_obj, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _decode_key(self, data):
|
||||
# private key file contains:
|
||||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException, x:
|
||||
raise SSHException('Unable to parse key file: ' + str(x))
|
||||
if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0):
|
||||
raise SSHException('not a valid DSA private key file (bad ber encoding)')
|
||||
self.p = keylist[1]
|
||||
self.q = keylist[2]
|
||||
self.g = keylist[3]
|
||||
self.y = keylist[4]
|
||||
self.x = keylist[5]
|
||||
self.size = util.bit_length(self.p)
|
||||
@ -1,453 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
BufferedFile.
|
||||
"""
|
||||
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
class BufferedFile (object):
|
||||
"""
|
||||
Reusable base class to implement python-style file buffering around a
|
||||
simpler stream.
|
||||
"""
|
||||
|
||||
_DEFAULT_BUFSIZE = 8192
|
||||
|
||||
SEEK_SET = 0
|
||||
SEEK_CUR = 1
|
||||
SEEK_END = 2
|
||||
|
||||
FLAG_READ = 0x1
|
||||
FLAG_WRITE = 0x2
|
||||
FLAG_APPEND = 0x4
|
||||
FLAG_BINARY = 0x10
|
||||
FLAG_BUFFERED = 0x20
|
||||
FLAG_LINE_BUFFERED = 0x40
|
||||
FLAG_UNIVERSAL_NEWLINE = 0x80
|
||||
|
||||
def __init__(self):
|
||||
self.newlines = None
|
||||
self._flags = 0
|
||||
self._bufsize = self._DEFAULT_BUFSIZE
|
||||
self._wbuffer = StringIO()
|
||||
self._rbuffer = ''
|
||||
self._at_trailing_cr = False
|
||||
self._closed = False
|
||||
# pos - position within the file, according to the user
|
||||
# realpos - position according the OS
|
||||
# (these may be different because we buffer for line reading)
|
||||
self._pos = self._realpos = 0
|
||||
# size only matters for seekable files
|
||||
self._size = 0
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Returns an iterator that can be used to iterate over the lines in this
|
||||
file. This iterator happens to return the file itself, since a file is
|
||||
its own iterator.
|
||||
|
||||
@raise ValueError: if the file is closed.
|
||||
|
||||
@return: an interator.
|
||||
@rtype: iterator
|
||||
"""
|
||||
if self._closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the file. Future read and write operations will fail.
|
||||
"""
|
||||
self.flush()
|
||||
self._closed = True
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
Write out any data in the write buffer. This may do nothing if write
|
||||
buffering is not turned on.
|
||||
"""
|
||||
self._write_all(self._wbuffer.getvalue())
|
||||
self._wbuffer = StringIO()
|
||||
return
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Returns the next line from the input, or raises L{StopIteration} when
|
||||
EOF is hit. Unlike python file objects, it's okay to mix calls to
|
||||
C{next} and L{readline}.
|
||||
|
||||
@raise StopIteration: when the end of the file is reached.
|
||||
|
||||
@return: a line read from the file.
|
||||
@rtype: str
|
||||
"""
|
||||
line = self.readline()
|
||||
if not line:
|
||||
raise StopIteration
|
||||
return line
|
||||
|
||||
def read(self, size=None):
|
||||
"""
|
||||
Read at most C{size} bytes from the file (less if we hit the end of the
|
||||
file first). If the C{size} argument is negative or omitted, read all
|
||||
the remaining data in the file.
|
||||
|
||||
@param size: maximum number of bytes to read
|
||||
@type size: int
|
||||
@return: data read from the file, or an empty string if EOF was
|
||||
encountered immediately
|
||||
@rtype: str
|
||||
"""
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & self.FLAG_READ):
|
||||
raise IOError('File is not open for reading')
|
||||
if (size is None) or (size < 0):
|
||||
# go for broke
|
||||
result = self._rbuffer
|
||||
self._rbuffer = ''
|
||||
self._pos += len(result)
|
||||
while True:
|
||||
try:
|
||||
new_data = self._read(self._DEFAULT_BUFSIZE)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
break
|
||||
result += new_data
|
||||
self._realpos += len(new_data)
|
||||
self._pos += len(new_data)
|
||||
return result
|
||||
if size <= len(self._rbuffer):
|
||||
result = self._rbuffer[:size]
|
||||
self._rbuffer = self._rbuffer[size:]
|
||||
self._pos += len(result)
|
||||
return result
|
||||
while len(self._rbuffer) < size:
|
||||
read_size = size - len(self._rbuffer)
|
||||
if self._flags & self.FLAG_BUFFERED:
|
||||
read_size = max(self._bufsize, read_size)
|
||||
try:
|
||||
new_data = self._read(read_size)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
break
|
||||
self._rbuffer += new_data
|
||||
self._realpos += len(new_data)
|
||||
result = self._rbuffer[:size]
|
||||
self._rbuffer = self._rbuffer[size:]
|
||||
self._pos += len(result)
|
||||
return result
|
||||
|
||||
def readline(self, size=None):
|
||||
"""
|
||||
Read one entire line from the file. A trailing newline character is
|
||||
kept in the string (but may be absent when a file ends with an
|
||||
incomplete line). If the size argument is present and non-negative, it
|
||||
is a maximum byte count (including the trailing newline) and an
|
||||
incomplete line may be returned. An empty string is returned only when
|
||||
EOF is encountered immediately.
|
||||
|
||||
@note: Unlike stdio's C{fgets()}, the returned string contains null
|
||||
characters (C{'\\0'}) if they occurred in the input.
|
||||
|
||||
@param size: maximum length of returned string.
|
||||
@type size: int
|
||||
@return: next line of the file, or an empty string if the end of the
|
||||
file has been reached.
|
||||
@rtype: str
|
||||
"""
|
||||
# it's almost silly how complex this function is.
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & self.FLAG_READ):
|
||||
raise IOError('File not open for reading')
|
||||
line = self._rbuffer
|
||||
while True:
|
||||
if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0):
|
||||
# edge case: the newline may be '\r\n' and we may have read
|
||||
# only the first '\r' last time.
|
||||
if line[0] == '\n':
|
||||
line = line[1:]
|
||||
self._record_newline('\r\n')
|
||||
else:
|
||||
self._record_newline('\r')
|
||||
self._at_trailing_cr = False
|
||||
# check size before looking for a linefeed, in case we already have
|
||||
# enough.
|
||||
if (size is not None) and (size >= 0):
|
||||
if len(line) >= size:
|
||||
# truncate line and return
|
||||
self._rbuffer = line[size:]
|
||||
line = line[:size]
|
||||
self._pos += len(line)
|
||||
return line
|
||||
n = size - len(line)
|
||||
else:
|
||||
n = self._bufsize
|
||||
if ('\n' in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
|
||||
break
|
||||
try:
|
||||
new_data = self._read(n)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
self._rbuffer = ''
|
||||
self._pos += len(line)
|
||||
return line
|
||||
line += new_data
|
||||
self._realpos += len(new_data)
|
||||
# find the newline
|
||||
pos = line.find('\n')
|
||||
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
|
||||
rpos = line.find('\r')
|
||||
if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
|
||||
pos = rpos
|
||||
xpos = pos + 1
|
||||
if (line[pos] == '\r') and (xpos < len(line)) and (line[xpos] == '\n'):
|
||||
xpos += 1
|
||||
self._rbuffer = line[xpos:]
|
||||
lf = line[pos:xpos]
|
||||
line = line[:pos] + '\n'
|
||||
if (len(self._rbuffer) == 0) and (lf == '\r'):
|
||||
# we could read the line up to a '\r' and there could still be a
|
||||
# '\n' following that we read next time. note that and eat it.
|
||||
self._at_trailing_cr = True
|
||||
else:
|
||||
self._record_newline(lf)
|
||||
self._pos += len(line)
|
||||
return line
|
||||
|
||||
def readlines(self, sizehint=None):
|
||||
"""
|
||||
Read all remaining lines using L{readline} and return them as a list.
|
||||
If the optional C{sizehint} argument is present, instead of reading up
|
||||
to EOF, whole lines totalling approximately sizehint bytes (possibly
|
||||
after rounding up to an internal buffer size) are read.
|
||||
|
||||
@param sizehint: desired maximum number of bytes to read.
|
||||
@type sizehint: int
|
||||
@return: list of lines read from the file.
|
||||
@rtype: list
|
||||
"""
|
||||
lines = []
|
||||
bytes = 0
|
||||
while True:
|
||||
line = self.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
lines.append(line)
|
||||
bytes += len(line)
|
||||
if (sizehint is not None) and (bytes >= sizehint):
|
||||
break
|
||||
return lines
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
"""
|
||||
Set the file's current position, like stdio's C{fseek}. Not all file
|
||||
objects support seeking.
|
||||
|
||||
@note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek
|
||||
operations will be undone at the next write (as the file position
|
||||
will move back to the end of the file).
|
||||
|
||||
@param offset: position to move to within the file, relative to
|
||||
C{whence}.
|
||||
@type offset: int
|
||||
@param whence: type of movement: 0 = absolute; 1 = relative to the
|
||||
current position; 2 = relative to the end of the file.
|
||||
@type whence: int
|
||||
|
||||
@raise IOError: if the file doesn't support random access.
|
||||
"""
|
||||
raise IOError('File does not support seeking.')
|
||||
|
||||
def tell(self):
|
||||
"""
|
||||
Return the file's current position. This may not be accurate or
|
||||
useful if the underlying file doesn't support random access, or was
|
||||
opened in append mode.
|
||||
|
||||
@return: file position (in bytes).
|
||||
@rtype: int
|
||||
"""
|
||||
return self._pos
|
||||
|
||||
def write(self, data):
|
||||
"""
|
||||
Write data to the file. If write buffering is on (C{bufsize} was
|
||||
specified and non-zero), some or all of the data may not actually be
|
||||
written yet. (Use L{flush} or L{close} to force buffered data to be
|
||||
written out.)
|
||||
|
||||
@param data: data to write.
|
||||
@type data: str
|
||||
"""
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & self.FLAG_WRITE):
|
||||
raise IOError('File not open for writing')
|
||||
if not (self._flags & self.FLAG_BUFFERED):
|
||||
self._write_all(data)
|
||||
return
|
||||
self._wbuffer.write(data)
|
||||
if self._flags & self.FLAG_LINE_BUFFERED:
|
||||
# only scan the new data for linefeed, to avoid wasting time.
|
||||
last_newline_pos = data.rfind('\n')
|
||||
if last_newline_pos >= 0:
|
||||
wbuf = self._wbuffer.getvalue()
|
||||
last_newline_pos += len(wbuf) - len(data)
|
||||
self._write_all(wbuf[:last_newline_pos + 1])
|
||||
self._wbuffer = StringIO()
|
||||
self._wbuffer.write(wbuf[last_newline_pos + 1:])
|
||||
return
|
||||
# even if we're line buffering, if the buffer has grown past the
|
||||
# buffer size, force a flush.
|
||||
if self._wbuffer.tell() >= self._bufsize:
|
||||
self.flush()
|
||||
return
|
||||
|
||||
def writelines(self, sequence):
|
||||
"""
|
||||
Write a sequence of strings to the file. The sequence can be any
|
||||
iterable object producing strings, typically a list of strings. (The
|
||||
name is intended to match L{readlines}; C{writelines} does not add line
|
||||
separators.)
|
||||
|
||||
@param sequence: an iterable sequence of strings.
|
||||
@type sequence: sequence
|
||||
"""
|
||||
for line in sequence:
|
||||
self.write(line)
|
||||
return
|
||||
|
||||
def xreadlines(self):
|
||||
"""
|
||||
Identical to C{iter(f)}. This is a deprecated file interface that
|
||||
predates python iterator support.
|
||||
|
||||
@return: an iterator.
|
||||
@rtype: iterator
|
||||
"""
|
||||
return self
|
||||
|
||||
|
||||
### overrides...
|
||||
|
||||
|
||||
def _read(self, size):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
Read data from the stream. Return C{None} or raise C{EOFError} to
|
||||
indicate EOF.
|
||||
"""
|
||||
raise EOFError()
|
||||
|
||||
def _write(self, data):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
Write data into the stream.
|
||||
"""
|
||||
raise IOError('write not implemented')
|
||||
|
||||
def _get_size(self):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
Return the size of the file. This is called from within L{_set_mode}
|
||||
if the file is opened in append mode, so the file position can be
|
||||
tracked and L{seek} and L{tell} will work correctly. If the file is
|
||||
a stream that can't be randomly accessed, you don't need to override
|
||||
this method,
|
||||
"""
|
||||
return 0
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _set_mode(self, mode='r', bufsize=-1):
|
||||
"""
|
||||
Subclasses call this method to initialize the BufferedFile.
|
||||
"""
|
||||
# set bufsize in any event, because it's used for readline().
|
||||
self._bufsize = self._DEFAULT_BUFSIZE
|
||||
if bufsize < 0:
|
||||
# do no buffering by default, because otherwise writes will get
|
||||
# buffered in a way that will probably confuse people.
|
||||
bufsize = 0
|
||||
if bufsize == 1:
|
||||
# apparently, line buffering only affects writes. reads are only
|
||||
# buffered if you call readline (directly or indirectly: iterating
|
||||
# over a file will indirectly call readline).
|
||||
self._flags |= self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED
|
||||
elif bufsize > 1:
|
||||
self._bufsize = bufsize
|
||||
self._flags |= self.FLAG_BUFFERED
|
||||
self._flags &= ~self.FLAG_LINE_BUFFERED
|
||||
elif bufsize == 0:
|
||||
# unbuffered
|
||||
self._flags &= ~(self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED)
|
||||
|
||||
if ('r' in mode) or ('+' in mode):
|
||||
self._flags |= self.FLAG_READ
|
||||
if ('w' in mode) or ('+' in mode):
|
||||
self._flags |= self.FLAG_WRITE
|
||||
if ('a' in mode):
|
||||
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
|
||||
self._size = self._get_size()
|
||||
self._pos = self._realpos = self._size
|
||||
if ('b' in mode):
|
||||
self._flags |= self.FLAG_BINARY
|
||||
if ('U' in mode):
|
||||
self._flags |= self.FLAG_UNIVERSAL_NEWLINE
|
||||
# built-in file objects have this attribute to store which kinds of
|
||||
# line terminations they've seen:
|
||||
# <http://www.python.org/doc/current/lib/built-in-funcs.html>
|
||||
self.newlines = None
|
||||
|
||||
def _write_all(self, data):
|
||||
# the underlying stream may be something that does partial writes (like
|
||||
# a socket).
|
||||
while len(data) > 0:
|
||||
count = self._write(data)
|
||||
data = data[count:]
|
||||
if self._flags & self.FLAG_APPEND:
|
||||
self._size += count
|
||||
self._pos = self._realpos = self._size
|
||||
else:
|
||||
self._pos += count
|
||||
self._realpos += count
|
||||
return None
|
||||
|
||||
def _record_newline(self, newline):
|
||||
# silliness about tracking what kinds of newlines we've seen.
|
||||
# i don't understand why it can be None, a string, or a tuple, instead
|
||||
# of just always being a tuple, but we'll emulate that behavior anyway.
|
||||
if not (self._flags & self.FLAG_UNIVERSAL_NEWLINE):
|
||||
return
|
||||
if self.newlines is None:
|
||||
self.newlines = newline
|
||||
elif (type(self.newlines) is str) and (self.newlines != newline):
|
||||
self.newlines = (self.newlines, newline)
|
||||
elif newline not in self.newlines:
|
||||
self.newlines += (newline,)
|
||||
@ -1,313 +0,0 @@
|
||||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{HostKeys}
|
||||
"""
|
||||
|
||||
import base64
|
||||
from Crypto.Hash import SHA, HMAC
|
||||
import UserDict
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.rsakey import RSAKey
|
||||
|
||||
|
||||
class HostKeyEntry:
|
||||
"""
|
||||
Representation of a line in an OpenSSH-style "known hosts" file.
|
||||
"""
|
||||
|
||||
def __init__(self, hostnames=None, key=None):
|
||||
self.valid = (hostnames is not None) and (key is not None)
|
||||
self.hostnames = hostnames
|
||||
self.key = key
|
||||
|
||||
def from_line(cls, line):
|
||||
"""
|
||||
Parses the given line of text to find the names for the host,
|
||||
the type of key, and the key data. The line is expected to be in the
|
||||
format used by the openssh known_hosts file.
|
||||
|
||||
Lines are expected to not have leading or trailing whitespace.
|
||||
We don't bother to check for comments or empty lines. All of
|
||||
that should be taken care of before sending the line to us.
|
||||
|
||||
@param line: a line from an OpenSSH known_hosts file
|
||||
@type line: str
|
||||
"""
|
||||
fields = line.split(' ')
|
||||
if len(fields) < 3:
|
||||
# Bad number of fields
|
||||
return None
|
||||
fields = fields[:3]
|
||||
|
||||
names, keytype, key = fields
|
||||
names = names.split(',')
|
||||
|
||||
# Decide what kind of key we're looking at and create an object
|
||||
# to hold it accordingly.
|
||||
if keytype == 'ssh-rsa':
|
||||
key = RSAKey(data=base64.decodestring(key))
|
||||
elif keytype == 'ssh-dss':
|
||||
key = DSSKey(data=base64.decodestring(key))
|
||||
else:
|
||||
return None
|
||||
|
||||
return cls(names, key)
|
||||
from_line = classmethod(from_line)
|
||||
|
||||
def to_line(self):
|
||||
"""
|
||||
Returns a string in OpenSSH known_hosts file format, or None if
|
||||
the object is not in a valid state. A trailing newline is
|
||||
included.
|
||||
"""
|
||||
if self.valid:
|
||||
return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(),
|
||||
self.key.get_base64())
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
|
||||
|
||||
|
||||
class HostKeys (UserDict.DictMixin):
|
||||
"""
|
||||
Representation of an openssh-style "known hosts" file. Host keys can be
|
||||
read from one or more files, and then individual hosts can be looked up to
|
||||
verify server keys during SSH negotiation.
|
||||
|
||||
A HostKeys object can be treated like a dict; any dict lookup is equivalent
|
||||
to calling L{lookup}.
|
||||
|
||||
@since: 1.5.3
|
||||
"""
|
||||
|
||||
def __init__(self, filename=None):
|
||||
"""
|
||||
Create a new HostKeys object, optionally loading keys from an openssh
|
||||
style host-key file.
|
||||
|
||||
@param filename: filename to load host keys from, or C{None}
|
||||
@type filename: str
|
||||
"""
|
||||
# emulate a dict of { hostname: { keytype: PKey } }
|
||||
self._entries = []
|
||||
if filename is not None:
|
||||
self.load(filename)
|
||||
|
||||
def add(self, hostname, keytype, key):
|
||||
"""
|
||||
Add a host key entry to the table. Any existing entry for a
|
||||
C{(hostname, keytype)} pair will be replaced.
|
||||
|
||||
@param hostname: the hostname (or IP) to add
|
||||
@type hostname: str
|
||||
@param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"})
|
||||
@type keytype: str
|
||||
@param key: the key to add
|
||||
@type key: L{PKey}
|
||||
"""
|
||||
for e in self._entries:
|
||||
if (hostname in e.hostnames) and (e.key.get_name() == keytype):
|
||||
e.key = key
|
||||
return
|
||||
self._entries.append(HostKeyEntry([hostname], key))
|
||||
|
||||
def load(self, filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by openssh.
|
||||
This type of file unfortunately doesn't exist on Windows, but on
|
||||
posix, it will usually be stored in
|
||||
C{os.path.expanduser("~/.ssh/known_hosts")}.
|
||||
|
||||
If this method is called multiple times, the host keys are merged,
|
||||
not cleared. So multiple calls to C{load} will just call L{add},
|
||||
replacing any existing entries and adding new ones.
|
||||
|
||||
@param filename: name of the file to read host keys from
|
||||
@type filename: str
|
||||
|
||||
@raise IOError: if there was an error reading the file
|
||||
"""
|
||||
f = open(filename, 'r')
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == '#'):
|
||||
continue
|
||||
e = HostKeyEntry.from_line(line)
|
||||
if e is not None:
|
||||
self._entries.append(e)
|
||||
f.close()
|
||||
|
||||
def save(self, filename):
|
||||
"""
|
||||
Save host keys into a file, in the format used by openssh. The order of
|
||||
keys in the file will be preserved when possible (if these keys were
|
||||
loaded from a file originally). The single exception is that combined
|
||||
lines will be split into individual key lines, which is arguably a bug.
|
||||
|
||||
@param filename: name of the file to write
|
||||
@type filename: str
|
||||
|
||||
@raise IOError: if there was an error writing the file
|
||||
|
||||
@since: 1.6.1
|
||||
"""
|
||||
f = open(filename, 'w')
|
||||
for e in self._entries:
|
||||
line = e.to_line()
|
||||
if line:
|
||||
f.write(line)
|
||||
f.close()
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Find a hostkey entry for a given hostname or IP. If no entry is found,
|
||||
C{None} is returned. Otherwise a dictionary of keytype to key is
|
||||
returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}.
|
||||
|
||||
@param hostname: the hostname (or IP) to lookup
|
||||
@type hostname: str
|
||||
@return: keys associated with this host (or C{None})
|
||||
@rtype: dict(str, L{PKey})
|
||||
"""
|
||||
class SubDict (UserDict.DictMixin):
|
||||
def __init__(self, hostname, entries, hostkeys):
|
||||
self._hostname = hostname
|
||||
self._entries = entries
|
||||
self._hostkeys = hostkeys
|
||||
|
||||
def __getitem__(self, key):
|
||||
for e in self._entries:
|
||||
if e.key.get_name() == key:
|
||||
return e.key
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
for e in self._entries:
|
||||
if e.key is None:
|
||||
continue
|
||||
if e.key.get_name() == key:
|
||||
# replace
|
||||
e.key = val
|
||||
break
|
||||
else:
|
||||
# add a new one
|
||||
e = HostKeyEntry([hostname], val)
|
||||
self._entries.append(e)
|
||||
self._hostkeys._entries.append(e)
|
||||
|
||||
def keys(self):
|
||||
return [e.key.get_name() for e in self._entries if e.key is not None]
|
||||
|
||||
entries = []
|
||||
for e in self._entries:
|
||||
for h in e.hostnames:
|
||||
if (h.startswith('|1|') and (self.hash_host(hostname, h) == h)) or (h == hostname):
|
||||
entries.append(e)
|
||||
if len(entries) == 0:
|
||||
return None
|
||||
return SubDict(hostname, entries, self)
|
||||
|
||||
def check(self, hostname, key):
|
||||
"""
|
||||
Return True if the given key is associated with the given hostname
|
||||
in this dictionary.
|
||||
|
||||
@param hostname: hostname (or IP) of the SSH server
|
||||
@type hostname: str
|
||||
@param key: the key to check
|
||||
@type key: L{PKey}
|
||||
@return: C{True} if the key is associated with the hostname; C{False}
|
||||
if not
|
||||
@rtype: bool
|
||||
"""
|
||||
k = self.lookup(hostname)
|
||||
if k is None:
|
||||
return False
|
||||
host_key = k.get(key.get_name(), None)
|
||||
if host_key is None:
|
||||
return False
|
||||
return str(host_key) == str(key)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Remove all host keys from the dictionary.
|
||||
"""
|
||||
self._entries = []
|
||||
|
||||
def __getitem__(self, key):
|
||||
ret = self.lookup(key)
|
||||
if ret is None:
|
||||
raise KeyError(key)
|
||||
return ret
|
||||
|
||||
def __setitem__(self, hostname, entry):
|
||||
# don't use this please.
|
||||
if len(entry) == 0:
|
||||
self._entries.append(HostKeyEntry([hostname], None))
|
||||
return
|
||||
for key_type in entry.keys():
|
||||
found = False
|
||||
for e in self._entries:
|
||||
if (hostname in e.hostnames) and (e.key.get_name() == key_type):
|
||||
# replace
|
||||
e.key = entry[key_type]
|
||||
found = True
|
||||
if not found:
|
||||
self._entries.append(HostKeyEntry([hostname], entry[key_type]))
|
||||
|
||||
def keys(self):
|
||||
# python 2.4 sets would be nice here.
|
||||
ret = []
|
||||
for e in self._entries:
|
||||
for h in e.hostnames:
|
||||
if h not in ret:
|
||||
ret.append(h)
|
||||
return ret
|
||||
|
||||
def values(self):
|
||||
ret = []
|
||||
for k in self.keys():
|
||||
ret.append(self.lookup(k))
|
||||
return ret
|
||||
|
||||
def hash_host(hostname, salt=None):
|
||||
"""
|
||||
Return a "hashed" form of the hostname, as used by openssh when storing
|
||||
hashed hostnames in the known_hosts file.
|
||||
|
||||
@param hostname: the hostname to hash
|
||||
@type hostname: str
|
||||
@param salt: optional salt to use when hashing (must be 20 bytes long)
|
||||
@type salt: str
|
||||
@return: the hashed hostname
|
||||
@rtype: str
|
||||
"""
|
||||
if salt is None:
|
||||
salt = randpool.get_bytes(SHA.digest_size)
|
||||
else:
|
||||
if salt.startswith('|1|'):
|
||||
salt = salt.split('|')[2]
|
||||
salt = base64.decodestring(salt)
|
||||
assert len(salt) == SHA.digest_size
|
||||
hmac = HMAC.HMAC(salt, hostname, SHA).digest()
|
||||
hostkey = '|1|%s|%s' % (base64.encodestring(salt), base64.encodestring(hmac))
|
||||
return hostkey.replace('\n', '')
|
||||
hash_host = staticmethod(hash_host)
|
||||
|
||||
@ -1,241 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Variant on L{KexGroup1 <paramiko.kex_group1.KexGroup1>} where the prime "p" and
|
||||
generator "g" are provided by the server. A bit more work is required on the
|
||||
client side, and a B{lot} more on the server side.
|
||||
"""
|
||||
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.Util import number
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
_MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \
|
||||
_MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35)
|
||||
|
||||
|
||||
class KexGex (object):
|
||||
|
||||
name = 'diffie-hellman-group-exchange-sha1'
|
||||
min_bits = 1024
|
||||
max_bits = 8192
|
||||
preferred_bits = 2048
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.p = None
|
||||
self.q = None
|
||||
self.g = None
|
||||
self.x = None
|
||||
self.e = None
|
||||
self.f = None
|
||||
self.old_style = False
|
||||
|
||||
def start_kex(self, _test_old_style=False):
|
||||
if self.transport.server_mode:
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD)
|
||||
return
|
||||
# request a bit range: we accept (min_bits) to (max_bits), but prefer
|
||||
# (preferred_bits). according to the spec, we shouldn't pull the
|
||||
# minimum up above 1024.
|
||||
m = Message()
|
||||
if _test_old_style:
|
||||
# only used for unit tests: we shouldn't ever send this
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST_OLD))
|
||||
m.add_int(self.preferred_bits)
|
||||
self.old_style = True
|
||||
else:
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST))
|
||||
m.add_int(self.min_bits)
|
||||
m.add_int(self.preferred_bits)
|
||||
m.add_int(self.max_bits)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
if ptype == _MSG_KEXDH_GEX_REQUEST:
|
||||
return self._parse_kexdh_gex_request(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_GROUP:
|
||||
return self._parse_kexdh_gex_group(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_INIT:
|
||||
return self._parse_kexdh_gex_init(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_REPLY:
|
||||
return self._parse_kexdh_gex_reply(m)
|
||||
elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD:
|
||||
return self._parse_kexdh_gex_request_old(m)
|
||||
raise SSHException('KexGex asked to handle packet type %d' % ptype)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < (p-1)/2).
|
||||
q = (self.p - 1) // 2
|
||||
qnorm = util.deflate_long(q, 0)
|
||||
qhbyte = ord(qnorm[0])
|
||||
bytes = len(qnorm)
|
||||
qmask = 0xff
|
||||
while not (qhbyte & 0x80):
|
||||
qhbyte <<= 1
|
||||
qmask >>= 1
|
||||
while True:
|
||||
self.transport.randpool.stir()
|
||||
x_bytes = self.transport.randpool.get_bytes(bytes)
|
||||
x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
|
||||
x = util.inflate_long(x_bytes, 1)
|
||||
if (x > 1) and (x < q):
|
||||
break
|
||||
self.x = x
|
||||
|
||||
def _parse_kexdh_gex_request(self, m):
|
||||
minbits = m.get_int()
|
||||
preferredbits = m.get_int()
|
||||
maxbits = m.get_int()
|
||||
# smoosh the user's preferred size into our own limits
|
||||
if preferredbits > self.max_bits:
|
||||
preferredbits = self.max_bits
|
||||
if preferredbits < self.min_bits:
|
||||
preferredbits = self.min_bits
|
||||
# fix min/max if they're inconsistent. technically, we could just pout
|
||||
# and hang up, but there's no harm in giving them the benefit of the
|
||||
# doubt and just picking a bitsize for them.
|
||||
if minbits > preferredbits:
|
||||
minbits = preferredbits
|
||||
if maxbits < preferredbits:
|
||||
maxbits = preferredbits
|
||||
# now save a copy
|
||||
self.min_bits = minbits
|
||||
self.preferred_bits = preferredbits
|
||||
self.max_bits = maxbits
|
||||
# generate prime
|
||||
pack = self.transport._get_modulus_pack()
|
||||
if pack is None:
|
||||
raise SSHException('Can\'t do server-side gex with no modulus pack')
|
||||
self.transport._log(DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits))
|
||||
self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
|
||||
|
||||
def _parse_kexdh_gex_request_old(self, m):
|
||||
# same as above, but without min_bits or max_bits (used by older clients like putty)
|
||||
self.preferred_bits = m.get_int()
|
||||
# smoosh the user's preferred size into our own limits
|
||||
if self.preferred_bits > self.max_bits:
|
||||
self.preferred_bits = self.max_bits
|
||||
if self.preferred_bits < self.min_bits:
|
||||
self.preferred_bits = self.min_bits
|
||||
# generate prime
|
||||
pack = self.transport._get_modulus_pack()
|
||||
if pack is None:
|
||||
raise SSHException('Can\'t do server-side gex with no modulus pack')
|
||||
self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits,))
|
||||
self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
|
||||
self.old_style = True
|
||||
|
||||
def _parse_kexdh_gex_group(self, m):
|
||||
self.p = m.get_mpint()
|
||||
self.g = m.get_mpint()
|
||||
# reject if p's bit length < 1024 or > 8192
|
||||
bitlen = util.bit_length(self.p)
|
||||
if (bitlen < 1024) or (bitlen > 8192):
|
||||
raise SSHException('Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen)
|
||||
self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen)
|
||||
self._generate_x()
|
||||
# now compute e = g^x mod p
|
||||
self.e = pow(self.g, self.x, self.p)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
|
||||
|
||||
def _parse_kexdh_gex_init(self, m):
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > self.p - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
self._generate_x()
|
||||
self.f = pow(self.g, self.x, self.p)
|
||||
K = pow(self.e, self.x, self.p)
|
||||
key = str(self.transport.get_server_key())
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.remote_version, self.transport.local_version,
|
||||
self.transport.remote_kex_init, self.transport.local_kex_init,
|
||||
key)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = SHA.new(str(hm)).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(str(sig))
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexdh_gex_reply(self, m):
|
||||
host_key = m.get_string()
|
||||
self.f = m.get_mpint()
|
||||
sig = m.get_string()
|
||||
if (self.f < 1) or (self.f > self.p - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
K = pow(self.f, self.x, self.p)
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.local_version, self.transport.remote_version,
|
||||
self.transport.local_kex_init, self.transport.remote_kex_init,
|
||||
host_key)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.min_bits)
|
||||
hm.add_int(self.preferred_bits)
|
||||
if not self.old_style:
|
||||
hm.add_int(self.max_bits)
|
||||
hm.add_mpint(self.p)
|
||||
hm.add_mpint(self.g)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, SHA.new(str(hm)).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
@ -1,133 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
|
||||
1024 bit key halves, using a known "p" prime and "g" generator.
|
||||
"""
|
||||
|
||||
from Crypto.Hash import SHA
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
|
||||
|
||||
# draft-ietf-secsh-transport-09.txt, page 17
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
|
||||
G = 2
|
||||
|
||||
|
||||
class KexGroup1(object):
|
||||
|
||||
name = 'diffie-hellman-group1-sha1'
|
||||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.x = 0L
|
||||
self.e = 0L
|
||||
self.f = 0L
|
||||
|
||||
def start_kex(self):
|
||||
self._generate_x()
|
||||
if self.transport.server_mode:
|
||||
# compute f = g^x mod p, but don't send it yet
|
||||
self.f = pow(G, self.x, P)
|
||||
self.transport._expect_packet(_MSG_KEXDH_INIT)
|
||||
return
|
||||
# compute e = g^x mod p (where g=2), and send it
|
||||
self.e = pow(G, self.x, P)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_INIT))
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_REPLY)
|
||||
|
||||
def parse_next(self, ptype, m):
|
||||
if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT):
|
||||
return self._parse_kexdh_init(m)
|
||||
elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY):
|
||||
return self._parse_kexdh_reply(m)
|
||||
raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < q), where q is (p-1)/2.
|
||||
# p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
|
||||
# therefore q can be approximated as a 2^1023. we drop the subset of
|
||||
# potential x where the first 63 bits are 1, because some of those will be
|
||||
# larger than q (but this is a tiny tiny subset of potential x).
|
||||
while 1:
|
||||
self.transport.randpool.stir()
|
||||
x_bytes = self.transport.randpool.get_bytes(128)
|
||||
x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:]
|
||||
if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \
|
||||
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
|
||||
break
|
||||
self.x = util.inflate_long(x_bytes)
|
||||
|
||||
def _parse_kexdh_reply(self, m):
|
||||
# client mode
|
||||
host_key = m.get_string()
|
||||
self.f = m.get_mpint()
|
||||
if (self.f < 1) or (self.f > P - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
sig = m.get_string()
|
||||
K = pow(self.f, self.x, P)
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.local_version, self.transport.remote_version,
|
||||
self.transport.local_kex_init, self.transport.remote_kex_init)
|
||||
hm.add_string(host_key)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, SHA.new(str(hm)).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
def _parse_kexdh_init(self, m):
|
||||
# server mode
|
||||
self.e = m.get_mpint()
|
||||
if (self.e < 1) or (self.e > P - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
K = pow(self.e, self.x, P)
|
||||
key = str(self.transport.get_server_key())
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.remote_version, self.transport.local_version,
|
||||
self.transport.remote_kex_init, self.transport.local_kex_init)
|
||||
hm.add_string(key)
|
||||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = SHA.new(str(hm)).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_REPLY))
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(str(sig))
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
@ -1,63 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Stub out logging on python < 2.3.
|
||||
"""
|
||||
|
||||
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARNING = 30
|
||||
ERROR = 40
|
||||
CRITICAL = 50
|
||||
|
||||
|
||||
def getLogger(name):
|
||||
return _logger
|
||||
|
||||
|
||||
class logger (object):
|
||||
def __init__(self):
|
||||
self.handlers = [ ]
|
||||
self.level = ERROR
|
||||
|
||||
def setLevel(self, level):
|
||||
self.level = level
|
||||
|
||||
def addHandler(self, h):
|
||||
self.handlers.append(h)
|
||||
|
||||
def addFilter(self, filter):
|
||||
pass
|
||||
|
||||
def log(self, level, text):
|
||||
if level >= self.level:
|
||||
for h in self.handlers:
|
||||
h.f.write(text + '\n')
|
||||
h.f.flush()
|
||||
|
||||
class StreamHandler (object):
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def setFormatter(self, f):
|
||||
pass
|
||||
|
||||
class Formatter (object):
|
||||
def __init__(self, x, y):
|
||||
pass
|
||||
|
||||
_logger = logger()
|
||||
@ -1,298 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Implementation of an SSH2 "message".
|
||||
"""
|
||||
|
||||
import struct
|
||||
import cStringIO
|
||||
|
||||
from paramiko import util
|
||||
|
||||
|
||||
class Message (object):
|
||||
"""
|
||||
An SSH2 I{Message} is a stream of bytes that encodes some combination of
|
||||
strings, integers, bools, and infinite-precision integers (known in python
|
||||
as I{long}s). This class builds or breaks down such a byte stream.
|
||||
|
||||
Normally you don't need to deal with anything this low-level, but it's
|
||||
exposed for people implementing custom extensions, or features that
|
||||
paramiko doesn't support yet.
|
||||
"""
|
||||
|
||||
def __init__(self, content=None):
|
||||
"""
|
||||
Create a new SSH2 Message.
|
||||
|
||||
@param content: the byte stream to use as the Message content (passed
|
||||
in only when decomposing a Message).
|
||||
@type content: string
|
||||
"""
|
||||
if content != None:
|
||||
self.packet = cStringIO.StringIO(content)
|
||||
else:
|
||||
self.packet = cStringIO.StringIO()
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the byte stream content of this Message, as a string.
|
||||
|
||||
@return: the contents of this Message.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.packet.getvalue()
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string representation of this object, for debugging.
|
||||
|
||||
@rtype: string
|
||||
"""
|
||||
return 'paramiko.Message(' + repr(self.packet.getvalue()) + ')'
|
||||
|
||||
def rewind(self):
|
||||
"""
|
||||
Rewind the message to the beginning as if no items had been parsed
|
||||
out of it yet.
|
||||
"""
|
||||
self.packet.seek(0)
|
||||
|
||||
def get_remainder(self):
|
||||
"""
|
||||
Return the bytes of this Message that haven't already been parsed and
|
||||
returned.
|
||||
|
||||
@return: a string of the bytes not parsed yet.
|
||||
@rtype: string
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
remainder = self.packet.read()
|
||||
self.packet.seek(position)
|
||||
return remainder
|
||||
|
||||
def get_so_far(self):
|
||||
"""
|
||||
Returns the bytes of this Message that have been parsed and returned.
|
||||
The string passed into a Message's constructor can be regenerated by
|
||||
concatenating C{get_so_far} and L{get_remainder}.
|
||||
|
||||
@return: a string of the bytes parsed so far.
|
||||
@rtype: string
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
self.rewind()
|
||||
return self.packet.read(position)
|
||||
|
||||
def get_bytes(self, n):
|
||||
"""
|
||||
Return the next C{n} bytes of the Message, without decomposing into
|
||||
an int, string, etc. Just the raw bytes are returned.
|
||||
|
||||
@return: a string of the next C{n} bytes of the Message, or a string
|
||||
of C{n} zero bytes, if there aren't C{n} bytes remaining.
|
||||
@rtype: string
|
||||
"""
|
||||
b = self.packet.read(n)
|
||||
if len(b) < n:
|
||||
return b + '\x00' * (n - len(b))
|
||||
return b
|
||||
|
||||
def get_byte(self):
|
||||
"""
|
||||
Return the next byte of the Message, without decomposing it. This
|
||||
is equivalent to L{get_bytes(1)<get_bytes>}.
|
||||
|
||||
@return: the next byte of the Message, or C{'\000'} if there aren't
|
||||
any bytes remaining.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.get_bytes(1)
|
||||
|
||||
def get_boolean(self):
|
||||
"""
|
||||
Fetch a boolean from the stream.
|
||||
|
||||
@return: C{True} or C{False} (from the Message).
|
||||
@rtype: bool
|
||||
"""
|
||||
b = self.get_bytes(1)
|
||||
return b != '\x00'
|
||||
|
||||
def get_int(self):
|
||||
"""
|
||||
Fetch an int from the stream.
|
||||
|
||||
@return: a 32-bit unsigned integer.
|
||||
@rtype: int
|
||||
"""
|
||||
return struct.unpack('>I', self.get_bytes(4))[0]
|
||||
|
||||
def get_int64(self):
|
||||
"""
|
||||
Fetch a 64-bit int from the stream.
|
||||
|
||||
@return: a 64-bit unsigned integer.
|
||||
@rtype: long
|
||||
"""
|
||||
return struct.unpack('>Q', self.get_bytes(8))[0]
|
||||
|
||||
def get_mpint(self):
|
||||
"""
|
||||
Fetch a long int (mpint) from the stream.
|
||||
|
||||
@return: an arbitrary-length integer.
|
||||
@rtype: long
|
||||
"""
|
||||
return util.inflate_long(self.get_string())
|
||||
|
||||
def get_string(self):
|
||||
"""
|
||||
Fetch a string from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
contain another byte-stream Message.)
|
||||
|
||||
@return: a string.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.get_bytes(self.get_int())
|
||||
|
||||
def get_list(self):
|
||||
"""
|
||||
Fetch a list of strings from the stream. These are trivially encoded
|
||||
as comma-separated values in a string.
|
||||
|
||||
@return: a list of strings.
|
||||
@rtype: list of strings
|
||||
"""
|
||||
return self.get_string().split(',')
|
||||
|
||||
def add_bytes(self, b):
|
||||
"""
|
||||
Write bytes to the stream, without any formatting.
|
||||
|
||||
@param b: bytes to add
|
||||
@type b: str
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
||||
def add_byte(self, b):
|
||||
"""
|
||||
Write a single byte to the stream, without any formatting.
|
||||
|
||||
@param b: byte to add
|
||||
@type b: str
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
||||
def add_boolean(self, b):
|
||||
"""
|
||||
Add a boolean value to the stream.
|
||||
|
||||
@param b: boolean value to add
|
||||
@type b: bool
|
||||
"""
|
||||
if b:
|
||||
self.add_byte('\x01')
|
||||
else:
|
||||
self.add_byte('\x00')
|
||||
return self
|
||||
|
||||
def add_int(self, n):
|
||||
"""
|
||||
Add an integer to the stream.
|
||||
|
||||
@param n: integer to add
|
||||
@type n: int
|
||||
"""
|
||||
self.packet.write(struct.pack('>I', n))
|
||||
return self
|
||||
|
||||
def add_int64(self, n):
|
||||
"""
|
||||
Add a 64-bit int to the stream.
|
||||
|
||||
@param n: long int to add
|
||||
@type n: long
|
||||
"""
|
||||
self.packet.write(struct.pack('>Q', n))
|
||||
return self
|
||||
|
||||
def add_mpint(self, z):
|
||||
"""
|
||||
Add a long int to the stream, encoded as an infinite-precision
|
||||
integer. This method only works on positive numbers.
|
||||
|
||||
@param z: long int to add
|
||||
@type z: long
|
||||
"""
|
||||
self.add_string(util.deflate_long(z))
|
||||
return self
|
||||
|
||||
def add_string(self, s):
|
||||
"""
|
||||
Add a string to the stream.
|
||||
|
||||
@param s: string to add
|
||||
@type s: str
|
||||
"""
|
||||
self.add_int(len(s))
|
||||
self.packet.write(s)
|
||||
return self
|
||||
|
||||
def add_list(self, l):
|
||||
"""
|
||||
Add a list of strings to the stream. They are encoded identically to
|
||||
a single string of values separated by commas. (Yes, really, that's
|
||||
how SSH2 does it.)
|
||||
|
||||
@param l: list of strings to add
|
||||
@type l: list(str)
|
||||
"""
|
||||
self.add_string(','.join(l))
|
||||
return self
|
||||
|
||||
def _add(self, i):
|
||||
if type(i) is str:
|
||||
return self.add_string(i)
|
||||
elif type(i) is int:
|
||||
return self.add_int(i)
|
||||
elif type(i) is long:
|
||||
if i > 0xffffffffL:
|
||||
return self.add_mpint(i)
|
||||
else:
|
||||
return self.add_int(i)
|
||||
elif type(i) is bool:
|
||||
return self.add_boolean(i)
|
||||
elif type(i) is list:
|
||||
return self.add_list(i)
|
||||
else:
|
||||
raise Exception('Unknown type')
|
||||
|
||||
def add(self, *seq):
|
||||
"""
|
||||
Add a sequence of items to the stream. The values are encoded based
|
||||
on their type: str, int, bool, list, or long.
|
||||
|
||||
@param seq: the sequence of items
|
||||
@type seq: sequence
|
||||
|
||||
@bug: longs are encoded non-deterministically. Don't use this method.
|
||||
"""
|
||||
for item in seq:
|
||||
self._add(item)
|
||||
@ -1,485 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Packetizer.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
|
||||
|
||||
got_r_hmac = False
|
||||
try:
|
||||
import r_hmac
|
||||
got_r_hmac = True
|
||||
except ImportError:
|
||||
pass
|
||||
def compute_hmac(key, message, digest_class):
|
||||
if got_r_hmac:
|
||||
return r_hmac.HMAC(key, message, digest_class).digest()
|
||||
from Crypto.Hash import HMAC
|
||||
return HMAC.HMAC(key, message, digest_class).digest()
|
||||
|
||||
|
||||
class NeedRekeyException (Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Packetizer (object):
|
||||
"""
|
||||
Implementation of the base SSH packet protocol.
|
||||
"""
|
||||
|
||||
# READ the secsh RFC's before raising these values. if anything,
|
||||
# they should probably be lower.
|
||||
REKEY_PACKETS = pow(2, 30)
|
||||
REKEY_BYTES = pow(2, 30)
|
||||
|
||||
def __init__(self, socket):
|
||||
self.__socket = socket
|
||||
self.__logger = None
|
||||
self.__closed = False
|
||||
self.__dump_packets = False
|
||||
self.__need_rekey = False
|
||||
self.__init_count = 0
|
||||
self.__remainder = ''
|
||||
|
||||
# used for noticing when to re-key:
|
||||
self.__sent_bytes = 0
|
||||
self.__sent_packets = 0
|
||||
self.__received_bytes = 0
|
||||
self.__received_packets = 0
|
||||
self.__received_packets_overflow = 0
|
||||
|
||||
# current inbound/outbound ciphering:
|
||||
self.__block_size_out = 8
|
||||
self.__block_size_in = 8
|
||||
self.__mac_size_out = 0
|
||||
self.__mac_size_in = 0
|
||||
self.__block_engine_out = None
|
||||
self.__block_engine_in = None
|
||||
self.__mac_engine_out = None
|
||||
self.__mac_engine_in = None
|
||||
self.__mac_key_out = ''
|
||||
self.__mac_key_in = ''
|
||||
self.__compress_engine_out = None
|
||||
self.__compress_engine_in = None
|
||||
self.__sequence_number_out = 0L
|
||||
self.__sequence_number_in = 0L
|
||||
|
||||
# lock around outbound writes (packet computation)
|
||||
self.__write_lock = threading.RLock()
|
||||
|
||||
# keepalives:
|
||||
self.__keepalive_interval = 0
|
||||
self.__keepalive_last = time.time()
|
||||
self.__keepalive_callback = None
|
||||
|
||||
def set_log(self, log):
|
||||
"""
|
||||
Set the python log object to use for logging.
|
||||
"""
|
||||
self.__logger = log
|
||||
|
||||
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
|
||||
"""
|
||||
Switch outbound data cipher.
|
||||
"""
|
||||
self.__block_engine_out = block_engine
|
||||
self.__block_size_out = block_size
|
||||
self.__mac_engine_out = mac_engine
|
||||
self.__mac_size_out = mac_size
|
||||
self.__mac_key_out = mac_key
|
||||
self.__sent_bytes = 0
|
||||
self.__sent_packets = 0
|
||||
# wait until the reset happens in both directions before clearing rekey flag
|
||||
self.__init_count |= 1
|
||||
if self.__init_count == 3:
|
||||
self.__init_count = 0
|
||||
self.__need_rekey = False
|
||||
|
||||
def set_inbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
|
||||
"""
|
||||
Switch inbound data cipher.
|
||||
"""
|
||||
self.__block_engine_in = block_engine
|
||||
self.__block_size_in = block_size
|
||||
self.__mac_engine_in = mac_engine
|
||||
self.__mac_size_in = mac_size
|
||||
self.__mac_key_in = mac_key
|
||||
self.__received_bytes = 0
|
||||
self.__received_packets = 0
|
||||
self.__received_packets_overflow = 0
|
||||
# wait until the reset happens in both directions before clearing rekey flag
|
||||
self.__init_count |= 2
|
||||
if self.__init_count == 3:
|
||||
self.__init_count = 0
|
||||
self.__need_rekey = False
|
||||
|
||||
def set_outbound_compressor(self, compressor):
|
||||
self.__compress_engine_out = compressor
|
||||
|
||||
def set_inbound_compressor(self, compressor):
|
||||
self.__compress_engine_in = compressor
|
||||
|
||||
def close(self):
|
||||
self.__closed = True
|
||||
self.__socket.close()
|
||||
|
||||
def set_hexdump(self, hexdump):
|
||||
self.__dump_packets = hexdump
|
||||
|
||||
def get_hexdump(self):
|
||||
return self.__dump_packets
|
||||
|
||||
def get_mac_size_in(self):
|
||||
return self.__mac_size_in
|
||||
|
||||
def get_mac_size_out(self):
|
||||
return self.__mac_size_out
|
||||
|
||||
def need_rekey(self):
|
||||
"""
|
||||
Returns C{True} if a new set of keys needs to be negotiated. This
|
||||
will be triggered during a packet read or write, so it should be
|
||||
checked after every read or write, or at least after every few.
|
||||
|
||||
@return: C{True} if a new set of keys needs to be negotiated
|
||||
"""
|
||||
return self.__need_rekey
|
||||
|
||||
def set_keepalive(self, interval, callback):
|
||||
"""
|
||||
Turn on/off the callback keepalive. If C{interval} seconds pass with
|
||||
no data read from or written to the socket, the callback will be
|
||||
executed and the timer will be reset.
|
||||
"""
|
||||
self.__keepalive_interval = interval
|
||||
self.__keepalive_callback = callback
|
||||
self.__keepalive_last = time.time()
|
||||
|
||||
def read_all(self, n, check_rekey=False):
|
||||
"""
|
||||
Read as close to N bytes as possible, blocking as long as necessary.
|
||||
|
||||
@param n: number of bytes to read
|
||||
@type n: int
|
||||
@return: the data read
|
||||
@rtype: str
|
||||
@raise EOFError: if the socket was closed before all the bytes could
|
||||
be read
|
||||
"""
|
||||
out = ''
|
||||
# handle over-reading from reading the banner line
|
||||
if len(self.__remainder) > 0:
|
||||
out = self.__remainder[:n]
|
||||
self.__remainder = self.__remainder[n:]
|
||||
n -= len(out)
|
||||
if PY22:
|
||||
return self._py22_read_all(n, out)
|
||||
while n > 0:
|
||||
got_timeout = False
|
||||
try:
|
||||
x = self.__socket.recv(n)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
out += x
|
||||
n -= len(x)
|
||||
except socket.timeout:
|
||||
got_timeout = True
|
||||
except socket.error, e:
|
||||
# on Linux, sometimes instead of socket.timeout, we get
|
||||
# EAGAIN. this is a bug in recent (> 2.6.9) kernels but
|
||||
# we need to work around it.
|
||||
if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN):
|
||||
got_timeout = True
|
||||
elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR):
|
||||
# syscall interrupted; try again
|
||||
pass
|
||||
elif self.__closed:
|
||||
raise EOFError()
|
||||
else:
|
||||
raise
|
||||
if got_timeout:
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
if check_rekey and (len(out) == 0) and self.__need_rekey:
|
||||
raise NeedRekeyException()
|
||||
self._check_keepalive()
|
||||
return out
|
||||
|
||||
def write_all(self, out):
|
||||
self.__keepalive_last = time.time()
|
||||
while len(out) > 0:
|
||||
got_timeout = False
|
||||
try:
|
||||
n = self.__socket.send(out)
|
||||
except socket.timeout:
|
||||
got_timeout = True
|
||||
except socket.error, e:
|
||||
if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN):
|
||||
got_timeout = True
|
||||
elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR):
|
||||
# syscall interrupted; try again
|
||||
pass
|
||||
else:
|
||||
n = -1
|
||||
except Exception:
|
||||
# could be: (32, 'Broken pipe')
|
||||
n = -1
|
||||
if got_timeout:
|
||||
n = 0
|
||||
if self.__closed:
|
||||
n = -1
|
||||
if n < 0:
|
||||
raise EOFError()
|
||||
if n == len(out):
|
||||
break
|
||||
out = out[n:]
|
||||
return
|
||||
|
||||
def readline(self, timeout):
|
||||
"""
|
||||
Read a line from the socket. We assume no data is pending after the
|
||||
line, so it's okay to attempt large reads.
|
||||
"""
|
||||
buf = self.__remainder
|
||||
while not '\n' in buf:
|
||||
buf += self._read_timeout(timeout)
|
||||
n = buf.index('\n')
|
||||
self.__remainder = buf[n+1:]
|
||||
buf = buf[:n]
|
||||
if (len(buf) > 0) and (buf[-1] == '\r'):
|
||||
buf = buf[:-1]
|
||||
return buf
|
||||
|
||||
def send_message(self, data):
|
||||
"""
|
||||
Write a block of data using the current cipher, as an SSH block.
|
||||
"""
|
||||
# encrypt this sucka
|
||||
data = str(data)
|
||||
cmd = ord(data[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
cmd_name = '$%x' % cmd
|
||||
orig_len = len(data)
|
||||
self.__write_lock.acquire()
|
||||
try:
|
||||
if self.__compress_engine_out is not None:
|
||||
data = self.__compress_engine_out(data)
|
||||
packet = self._build_packet(data)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len))
|
||||
self._log(DEBUG, util.format_binary(packet, 'OUT: '))
|
||||
if self.__block_engine_out != None:
|
||||
out = self.__block_engine_out.encrypt(packet)
|
||||
else:
|
||||
out = packet
|
||||
# + mac
|
||||
if self.__block_engine_out != None:
|
||||
payload = struct.pack('>I', self.__sequence_number_out) + packet
|
||||
out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out]
|
||||
self.__sequence_number_out = (self.__sequence_number_out + 1) & 0xffffffffL
|
||||
self.write_all(out)
|
||||
|
||||
self.__sent_bytes += len(out)
|
||||
self.__sent_packets += 1
|
||||
if (self.__sent_packets % 100) == 0:
|
||||
# stirring the randpool takes 30ms on my ibook!!
|
||||
randpool.stir()
|
||||
if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
|
||||
and not self.__need_rekey:
|
||||
# only ask once for rekeying
|
||||
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
|
||||
(self.__sent_packets, self.__sent_bytes))
|
||||
self.__received_packets_overflow = 0
|
||||
self._trigger_rekey()
|
||||
finally:
|
||||
self.__write_lock.release()
|
||||
|
||||
def read_message(self):
|
||||
"""
|
||||
Only one thread should ever be in this function (no other locking is
|
||||
done).
|
||||
|
||||
@raise SSHException: if the packet is mangled
|
||||
@raise NeedRekeyException: if the transport should rekey
|
||||
"""
|
||||
header = self.read_all(self.__block_size_in, check_rekey=True)
|
||||
if self.__block_engine_in != None:
|
||||
header = self.__block_engine_in.decrypt(header)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(header, 'IN: '));
|
||||
packet_size = struct.unpack('>I', header[:4])[0]
|
||||
# leftover contains decrypted bytes from the first block (after the length field)
|
||||
leftover = header[4:]
|
||||
if (packet_size - len(leftover)) % self.__block_size_in != 0:
|
||||
raise SSHException('Invalid packet blocking')
|
||||
buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
|
||||
packet = buf[:packet_size - len(leftover)]
|
||||
post_packet = buf[packet_size - len(leftover):]
|
||||
if self.__block_engine_in != None:
|
||||
packet = self.__block_engine_in.decrypt(packet)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(packet, 'IN: '));
|
||||
packet = leftover + packet
|
||||
|
||||
if self.__mac_size_in > 0:
|
||||
mac = post_packet[:self.__mac_size_in]
|
||||
mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet
|
||||
my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in]
|
||||
if my_mac != mac:
|
||||
raise SSHException('Mismatched MAC')
|
||||
padding = ord(packet[0])
|
||||
payload = packet[1:packet_size - padding]
|
||||
randpool.add_event()
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
|
||||
|
||||
if self.__compress_engine_in is not None:
|
||||
payload = self.__compress_engine_in(payload)
|
||||
|
||||
msg = Message(payload[1:])
|
||||
msg.seqno = self.__sequence_number_in
|
||||
self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL
|
||||
|
||||
# check for rekey
|
||||
self.__received_bytes += packet_size + self.__mac_size_in + 4
|
||||
self.__received_packets += 1
|
||||
if self.__need_rekey:
|
||||
# we've asked to rekey -- give them 20 packets to comply before
|
||||
# dropping the connection
|
||||
self.__received_packets_overflow += 1
|
||||
if self.__received_packets_overflow >= 20:
|
||||
raise SSHException('Remote transport is ignoring rekey requests')
|
||||
elif (self.__received_packets >= self.REKEY_PACKETS) or \
|
||||
(self.__received_bytes >= self.REKEY_BYTES):
|
||||
# only ask once for rekeying
|
||||
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
|
||||
(self.__received_packets, self.__received_bytes))
|
||||
self.__received_packets_overflow = 0
|
||||
self._trigger_rekey()
|
||||
|
||||
cmd = ord(payload[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
cmd_name = '$%x' % cmd
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload)))
|
||||
return cmd, msg
|
||||
|
||||
|
||||
########## protected
|
||||
|
||||
|
||||
def _log(self, level, msg):
|
||||
if self.__logger is None:
|
||||
return
|
||||
if issubclass(type(msg), list):
|
||||
for m in msg:
|
||||
self.__logger.log(level, m)
|
||||
else:
|
||||
self.__logger.log(level, msg)
|
||||
|
||||
def _check_keepalive(self):
|
||||
if (not self.__keepalive_interval) or (not self.__block_engine_out) or \
|
||||
self.__need_rekey:
|
||||
# wait till we're encrypting, and not in the middle of rekeying
|
||||
return
|
||||
now = time.time()
|
||||
if now > self.__keepalive_last + self.__keepalive_interval:
|
||||
self.__keepalive_callback()
|
||||
self.__keepalive_last = now
|
||||
|
||||
def _py22_read_all(self, n, out):
|
||||
while n > 0:
|
||||
r, w, e = select.select([self.__socket], [], [], 0.1)
|
||||
if self.__socket not in r:
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
self._check_keepalive()
|
||||
else:
|
||||
x = self.__socket.recv(n)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
out += x
|
||||
n -= len(x)
|
||||
return out
|
||||
|
||||
def _py22_read_timeout(self, timeout):
|
||||
start = time.time()
|
||||
while True:
|
||||
r, w, e = select.select([self.__socket], [], [], 0.1)
|
||||
if self.__socket in r:
|
||||
x = self.__socket.recv(1)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
break
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
now = time.time()
|
||||
if now - start >= timeout:
|
||||
raise socket.timeout()
|
||||
return x
|
||||
|
||||
def _read_timeout(self, timeout):
|
||||
if PY22:
|
||||
return self._py22_read_timeout(timeout)
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
x = self.__socket.recv(128)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
break
|
||||
except socket.timeout:
|
||||
pass
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
now = time.time()
|
||||
if now - start >= timeout:
|
||||
raise socket.timeout()
|
||||
return x
|
||||
|
||||
def _build_packet(self, payload):
|
||||
# pad up at least 4 bytes, to nearest block-size (usually 8)
|
||||
bsize = self.__block_size_out
|
||||
padding = 3 + bsize - ((len(payload) + 8) % bsize)
|
||||
packet = struct.pack('>IB', len(payload) + padding + 1, padding)
|
||||
packet += payload
|
||||
if self.__block_engine_out is not None:
|
||||
packet += randpool.get_bytes(padding)
|
||||
else:
|
||||
# cute trick i caught openssh doing: if we're not encrypting,
|
||||
# don't waste random bytes for the padding
|
||||
packet += (chr(0) * padding)
|
||||
return packet
|
||||
|
||||
def _trigger_rekey(self):
|
||||
# outside code should check for this flag
|
||||
self.__need_rekey = True
|
||||
@ -1,144 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of a one-way pipe where the read end can be used in select().
|
||||
Normally this is trivial, but Windows makes it nearly impossible.
|
||||
|
||||
The pipe acts like an Event, which can be set or cleared. When set, the pipe
|
||||
will trigger as readable in select().
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
|
||||
|
||||
def make_pipe ():
|
||||
if sys.platform[:3] != 'win':
|
||||
p = PosixPipe()
|
||||
else:
|
||||
p = WindowsPipe()
|
||||
return p
|
||||
|
||||
|
||||
class PosixPipe (object):
|
||||
def __init__ (self):
|
||||
self._rfd, self._wfd = os.pipe()
|
||||
self._set = False
|
||||
self._forever = False
|
||||
self._closed = False
|
||||
|
||||
def close (self):
|
||||
os.close(self._rfd)
|
||||
os.close(self._wfd)
|
||||
# used for unit tests:
|
||||
self._closed = True
|
||||
|
||||
def fileno (self):
|
||||
return self._rfd
|
||||
|
||||
def clear (self):
|
||||
if not self._set or self._forever:
|
||||
return
|
||||
os.read(self._rfd, 1)
|
||||
self._set = False
|
||||
|
||||
def set (self):
|
||||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
os.write(self._wfd, '*')
|
||||
|
||||
def set_forever (self):
|
||||
self._forever = True
|
||||
self.set()
|
||||
|
||||
|
||||
class WindowsPipe (object):
|
||||
"""
|
||||
On Windows, only an OS-level "WinSock" may be used in select(), but reads
|
||||
and writes must be to the actual socket object.
|
||||
"""
|
||||
def __init__ (self):
|
||||
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
serv.bind(('127.0.0.1', 0))
|
||||
serv.listen(1)
|
||||
|
||||
# need to save sockets in _rsock/_wsock so they don't get closed
|
||||
self._rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._rsock.connect(('127.0.0.1', serv.getsockname()[1]))
|
||||
|
||||
self._wsock, addr = serv.accept()
|
||||
serv.close()
|
||||
self._set = False
|
||||
self._forever = False
|
||||
self._closed = False
|
||||
|
||||
def close (self):
|
||||
self._rsock.close()
|
||||
self._wsock.close()
|
||||
# used for unit tests:
|
||||
self._closed = True
|
||||
|
||||
def fileno (self):
|
||||
return self._rsock.fileno()
|
||||
|
||||
def clear (self):
|
||||
if not self._set or self._forever:
|
||||
return
|
||||
self._rsock.recv(1)
|
||||
self._set = False
|
||||
|
||||
def set (self):
|
||||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
self._wsock.send('*')
|
||||
|
||||
def set_forever (self):
|
||||
self._forever = True
|
||||
self.set()
|
||||
|
||||
|
||||
class OrPipe (object):
|
||||
def __init__(self, pipe):
|
||||
self._set = False
|
||||
self._partner = None
|
||||
self._pipe = pipe
|
||||
|
||||
def set(self):
|
||||
self._set = True
|
||||
if not self._partner._set:
|
||||
self._pipe.set()
|
||||
|
||||
def clear(self):
|
||||
self._set = False
|
||||
if not self._partner._set:
|
||||
self._pipe.clear()
|
||||
|
||||
|
||||
def make_or_pipe(pipe):
|
||||
"""
|
||||
wraps a pipe into two pipe-like objects which are "or"d together to
|
||||
affect the real pipe. if either returned pipe is set, the wrapped pipe
|
||||
is set. when both are cleared, the wrapped pipe is cleared.
|
||||
"""
|
||||
p1 = OrPipe(pipe)
|
||||
p2 = OrPipe(pipe)
|
||||
p1._partner = p2
|
||||
p2._partner = p1
|
||||
return p1, p2
|
||||
|
||||
@ -1,377 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Common API for all public keys.
|
||||
"""
|
||||
|
||||
import base64
|
||||
from binascii import hexlify, unhexlify
|
||||
import os
|
||||
|
||||
from Crypto.Hash import MD5
|
||||
from Crypto.Cipher import DES3
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.ssh_exception import SSHException, PasswordRequiredException
|
||||
|
||||
|
||||
class PKey (object):
|
||||
"""
|
||||
Base class for public keys.
|
||||
"""
|
||||
|
||||
# known encryption types for private key files:
|
||||
_CIPHER_TABLE = {
|
||||
'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, msg=None, data=None):
|
||||
"""
|
||||
Create a new instance of this public key type. If C{msg} is given,
|
||||
the key's public part(s) will be filled in from the message. If
|
||||
C{data} is given, the key's public part(s) will be filled in from
|
||||
the string.
|
||||
|
||||
@param msg: an optional SSH L{Message} containing a public key of this
|
||||
type.
|
||||
@type msg: L{Message}
|
||||
@param data: an optional string containing a public key of this type
|
||||
@type data: str
|
||||
|
||||
@raise SSHException: if a key cannot be created from the C{data} or
|
||||
C{msg} given, or no key was passed in.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a string of an SSH L{Message} made up of the public part(s) of
|
||||
this key. This string is suitable for passing to L{__init__} to
|
||||
re-create the key object later.
|
||||
|
||||
@return: string representation of an SSH key message.
|
||||
@rtype: str
|
||||
"""
|
||||
return ''
|
||||
|
||||
def __cmp__(self, other):
|
||||
"""
|
||||
Compare this key to another. Returns 0 if this key is equivalent to
|
||||
the given key, or non-0 if they are different. Only the public parts
|
||||
of the key are compared, so a public key will compare equal to its
|
||||
corresponding private key.
|
||||
|
||||
@param other: key to compare to.
|
||||
@type other: L{PKey}
|
||||
@return: 0 if the two keys are equivalent, non-0 otherwise.
|
||||
@rtype: int
|
||||
"""
|
||||
hs = hash(self)
|
||||
ho = hash(other)
|
||||
if hs != ho:
|
||||
return cmp(hs, ho)
|
||||
return cmp(str(self), str(other))
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Return the name of this private key implementation.
|
||||
|
||||
@return: name of this private key type, in SSH terminology (for
|
||||
example, C{"ssh-rsa"}).
|
||||
@rtype: str
|
||||
"""
|
||||
return ''
|
||||
|
||||
def get_bits(self):
|
||||
"""
|
||||
Return the number of significant bits in this key. This is useful
|
||||
for judging the relative security of a key.
|
||||
|
||||
@return: bits in the key.
|
||||
@rtype: int
|
||||
"""
|
||||
return 0
|
||||
|
||||
def can_sign(self):
|
||||
"""
|
||||
Return C{True} if this key has the private part necessary for signing
|
||||
data.
|
||||
|
||||
@return: C{True} if this is a private key.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_fingerprint(self):
|
||||
"""
|
||||
Return an MD5 fingerprint of the public part of this key. Nothing
|
||||
secret is revealed.
|
||||
|
||||
@return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
|
||||
format.
|
||||
@rtype: str
|
||||
"""
|
||||
return MD5.new(str(self)).digest()
|
||||
|
||||
def get_base64(self):
|
||||
"""
|
||||
Return a base64 string containing the public part of this key. Nothing
|
||||
secret is revealed. This format is compatible with that used to store
|
||||
public key files or recognized host keys.
|
||||
|
||||
@return: a base64 string containing the public part of the key.
|
||||
@rtype: str
|
||||
"""
|
||||
return base64.encodestring(str(self)).replace('\n', '')
|
||||
|
||||
def sign_ssh_data(self, randpool, data):
|
||||
"""
|
||||
Sign a blob of data with this private key, and return a L{Message}
|
||||
representing an SSH signature message.
|
||||
|
||||
@param randpool: a secure random number generator.
|
||||
@type randpool: L{Crypto.Util.randpool.RandomPool}
|
||||
@param data: the data to sign.
|
||||
@type data: str
|
||||
@return: an SSH signature message.
|
||||
@rtype: L{Message}
|
||||
"""
|
||||
return ''
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
"""
|
||||
Given a blob of data, and an SSH message representing a signature of
|
||||
that data, verify that it was signed with this key.
|
||||
|
||||
@param data: the data that was signed.
|
||||
@type data: str
|
||||
@param msg: an SSH signature message
|
||||
@type msg: L{Message}
|
||||
@return: C{True} if the signature verifies correctly; C{False}
|
||||
otherwise.
|
||||
@rtype: boolean
|
||||
"""
|
||||
return False
|
||||
|
||||
def from_private_key_file(cls, filename, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key file. If the private
|
||||
key is encrypted and C{password} is not C{None}, the given password
|
||||
will be used to decrypt the key (otherwise L{PasswordRequiredException}
|
||||
is thrown). Through the magic of python, this factory method will
|
||||
exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but
|
||||
is useless on the abstract PKey class.
|
||||
|
||||
@param filename: name of the file to read
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted
|
||||
@type password: str
|
||||
@return: a new key object based on the given private key
|
||||
@rtype: L{PKey}
|
||||
|
||||
@raise IOError: if there was an error reading the file
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
encrypted, and C{password} is C{None}
|
||||
@raise SSHException: if the key file is invalid
|
||||
"""
|
||||
key = cls(filename=filename, password=password)
|
||||
return key
|
||||
from_private_key_file = classmethod(from_private_key_file)
|
||||
|
||||
def from_private_key(cls, file_obj, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key from a file (or file-like)
|
||||
object. If the private key is encrypted and C{password} is not C{None},
|
||||
the given password will be used to decrypt the key (otherwise
|
||||
L{PasswordRequiredException} is thrown).
|
||||
|
||||
@param file_obj: the file to read from
|
||||
@type file_obj: file
|
||||
@param password: an optional password to use to decrypt the key, if it's
|
||||
encrypted
|
||||
@type password: str
|
||||
@return: a new key object based on the given private key
|
||||
@rtype: L{PKey}
|
||||
|
||||
@raise IOError: if there was an error reading the key
|
||||
@raise PasswordRequiredException: if the private key file is encrypted,
|
||||
and C{password} is C{None}
|
||||
@raise SSHException: if the key file is invalid
|
||||
"""
|
||||
key = cls(file_obj=file_obj, password=password)
|
||||
return key
|
||||
from_private_key = classmethod(from_private_key)
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
"""
|
||||
Write private key contents into a file. If the password is not
|
||||
C{None}, the key is encrypted before writing.
|
||||
|
||||
@param filename: name of the file to write
|
||||
@type filename: str
|
||||
@param password: an optional password to use to encrypt the key file
|
||||
@type password: str
|
||||
|
||||
@raise IOError: if there was an error writing the file
|
||||
@raise SSHException: if the key is invalid
|
||||
"""
|
||||
raise Exception('Not implemented in PKey')
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
"""
|
||||
Write private key contents into a file (or file-like) object. If the
|
||||
password is not C{None}, the key is encrypted before writing.
|
||||
|
||||
@param file_obj: the file object to write into
|
||||
@type file_obj: file
|
||||
@param password: an optional password to use to encrypt the key
|
||||
@type password: str
|
||||
|
||||
@raise IOError: if there was an error writing to the file
|
||||
@raise SSHException: if the key is invalid
|
||||
"""
|
||||
raise Exception('Not implemented in PKey')
|
||||
|
||||
def _read_private_key_file(self, tag, filename, password=None):
|
||||
"""
|
||||
Read an SSH2-format private key file, looking for a string of the type
|
||||
C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we
|
||||
find, and return it as a string. If the private key is encrypted and
|
||||
C{password} is not C{None}, the given password will be used to decrypt
|
||||
the key (otherwise L{PasswordRequiredException} is thrown).
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: str
|
||||
@param filename: name of the file to read.
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted.
|
||||
@type password: str
|
||||
@return: data blob that makes up the private key.
|
||||
@rtype: str
|
||||
|
||||
@raise IOError: if there was an error reading the file.
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
encrypted, and C{password} is C{None}.
|
||||
@raise SSHException: if the key file is invalid.
|
||||
"""
|
||||
f = open(filename, 'r')
|
||||
data = self._read_private_key(tag, f, password)
|
||||
f.close()
|
||||
return data
|
||||
|
||||
def _read_private_key(self, tag, f, password=None):
|
||||
lines = f.readlines()
|
||||
start = 0
|
||||
while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'):
|
||||
start += 1
|
||||
if start >= len(lines):
|
||||
raise SSHException('not a valid ' + tag + ' private key file')
|
||||
# parse any headers first
|
||||
headers = {}
|
||||
start += 1
|
||||
while start < len(lines):
|
||||
l = lines[start].split(': ')
|
||||
if len(l) == 1:
|
||||
break
|
||||
headers[l[0].lower()] = l[1].strip()
|
||||
start += 1
|
||||
# find end
|
||||
end = start
|
||||
while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)):
|
||||
end += 1
|
||||
# if we trudged to the end of the file, just try to cope.
|
||||
try:
|
||||
data = base64.decodestring(''.join(lines[start:end]))
|
||||
except base64.binascii.Error, e:
|
||||
raise SSHException('base64 decoding error: ' + str(e))
|
||||
if 'proc-type' not in headers:
|
||||
# unencryped: done
|
||||
return data
|
||||
# encrypted keyfile: will need a password
|
||||
if headers['proc-type'] != '4,ENCRYPTED':
|
||||
raise SSHException('Unknown private key structure "%s"' % headers['proc-type'])
|
||||
try:
|
||||
encryption_type, saltstr = headers['dek-info'].split(',')
|
||||
except:
|
||||
raise SSHException('Can\'t parse DEK-info in private key file')
|
||||
if encryption_type not in self._CIPHER_TABLE:
|
||||
raise SSHException('Unknown private key cipher "%s"' % encryption_type)
|
||||
# if no password was passed in, raise an exception pointing out that we need one
|
||||
if password is None:
|
||||
raise PasswordRequiredException('Private key file is encrypted')
|
||||
cipher = self._CIPHER_TABLE[encryption_type]['cipher']
|
||||
keysize = self._CIPHER_TABLE[encryption_type]['keysize']
|
||||
mode = self._CIPHER_TABLE[encryption_type]['mode']
|
||||
salt = unhexlify(saltstr)
|
||||
key = util.generate_key_bytes(MD5, salt, password, keysize)
|
||||
return cipher.new(key, mode, salt).decrypt(data)
|
||||
|
||||
def _write_private_key_file(self, tag, filename, data, password=None):
|
||||
"""
|
||||
Write an SSH2-format private key file in a form that can be read by
|
||||
paramiko or openssh. If no password is given, the key is written in
|
||||
a trivially-encoded format (base64) which is completely insecure. If
|
||||
a password is given, DES-EDE3-CBC is used.
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: str
|
||||
@param filename: name of the file to write.
|
||||
@type filename: str
|
||||
@param data: data blob that makes up the private key.
|
||||
@type data: str
|
||||
@param password: an optional password to use to encrypt the file.
|
||||
@type password: str
|
||||
|
||||
@raise IOError: if there was an error writing the file.
|
||||
"""
|
||||
f = open(filename, 'w', 0600)
|
||||
# grrr... the mode doesn't always take hold
|
||||
os.chmod(filename, 0600)
|
||||
self._write_private_key(tag, f, data, password)
|
||||
f.close()
|
||||
|
||||
def _write_private_key(self, tag, f, data, password=None):
|
||||
f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag)
|
||||
if password is not None:
|
||||
# since we only support one cipher here, use it
|
||||
cipher_name = self._CIPHER_TABLE.keys()[0]
|
||||
cipher = self._CIPHER_TABLE[cipher_name]['cipher']
|
||||
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
|
||||
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
|
||||
mode = self._CIPHER_TABLE[cipher_name]['mode']
|
||||
salt = randpool.get_bytes(8)
|
||||
key = util.generate_key_bytes(MD5, salt, password, keysize)
|
||||
if len(data) % blocksize != 0:
|
||||
n = blocksize - len(data) % blocksize
|
||||
#data += randpool.get_bytes(n)
|
||||
# that would make more sense ^, but it confuses openssh.
|
||||
data += '\0' * n
|
||||
data = cipher.new(key, mode, salt).encrypt(data)
|
||||
f.write('Proc-Type: 4,ENCRYPTED\n')
|
||||
f.write('DEK-Info: %s,%s\n' % (cipher_name, hexlify(salt).upper()))
|
||||
f.write('\n')
|
||||
s = base64.encodestring(data)
|
||||
# re-wrap to 64-char lines
|
||||
s = ''.join(s.split('\n'))
|
||||
s = '\n'.join([s[i : i+64] for i in range(0, len(s), 64)])
|
||||
f.write(s)
|
||||
f.write('\n')
|
||||
f.write('-----END %s PRIVATE KEY-----\n' % tag)
|
||||
@ -1,148 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Utility functions for dealing with primes.
|
||||
"""
|
||||
|
||||
from Crypto.Util import number
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
def _generate_prime(bits, randpool):
|
||||
"primtive attempt at prime generation"
|
||||
hbyte_mask = pow(2, bits % 8) - 1
|
||||
while True:
|
||||
# loop catches the case where we increment n into a higher bit-range
|
||||
x = randpool.get_bytes((bits+7) // 8)
|
||||
if hbyte_mask > 0:
|
||||
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
|
||||
n = util.inflate_long(x, 1)
|
||||
n |= 1
|
||||
n |= (1 << (bits - 1))
|
||||
while not number.isPrime(n):
|
||||
n += 2
|
||||
if util.bit_length(n) == bits:
|
||||
break
|
||||
return n
|
||||
|
||||
def _roll_random(rpool, n):
|
||||
"returns a random # from 0 to N-1"
|
||||
bits = util.bit_length(n-1)
|
||||
bytes = (bits + 7) // 8
|
||||
hbyte_mask = pow(2, bits % 8) - 1
|
||||
|
||||
# so here's the plan:
|
||||
# we fetch as many random bits as we'd need to fit N-1, and if the
|
||||
# generated number is >= N, we try again. in the worst case (N-1 is a
|
||||
# power of 2), we have slightly better than 50% odds of getting one that
|
||||
# fits, so i can't guarantee that this loop will ever finish, but the odds
|
||||
# of it looping forever should be infinitesimal.
|
||||
while True:
|
||||
x = rpool.get_bytes(bytes)
|
||||
if hbyte_mask > 0:
|
||||
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
|
||||
num = util.inflate_long(x, 1)
|
||||
if num < n:
|
||||
break
|
||||
return num
|
||||
|
||||
|
||||
class ModulusPack (object):
|
||||
"""
|
||||
convenience object for holding the contents of the /etc/ssh/moduli file,
|
||||
on systems that have such a file.
|
||||
"""
|
||||
|
||||
def __init__(self, rpool):
|
||||
# pack is a hash of: bits -> [ (generator, modulus) ... ]
|
||||
self.pack = {}
|
||||
self.discarded = []
|
||||
self.randpool = rpool
|
||||
|
||||
def _parse_modulus(self, line):
|
||||
timestamp, mod_type, tests, tries, size, generator, modulus = line.split()
|
||||
mod_type = int(mod_type)
|
||||
tests = int(tests)
|
||||
tries = int(tries)
|
||||
size = int(size)
|
||||
generator = int(generator)
|
||||
modulus = long(modulus, 16)
|
||||
|
||||
# weed out primes that aren't at least:
|
||||
# type 2 (meets basic structural requirements)
|
||||
# test 4 (more than just a small-prime sieve)
|
||||
# tries < 100 if test & 4 (at least 100 tries of miller-rabin)
|
||||
if (mod_type < 2) or (tests < 4) or ((tests & 4) and (tests < 8) and (tries < 100)):
|
||||
self.discarded.append((modulus, 'does not meet basic requirements'))
|
||||
return
|
||||
if generator == 0:
|
||||
generator = 2
|
||||
|
||||
# there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay!
|
||||
# call cnn!) where it understates the bit lengths of these primes by 1.
|
||||
# this is okay.
|
||||
bl = util.bit_length(modulus)
|
||||
if (bl != size) and (bl != size + 1):
|
||||
self.discarded.append((modulus, 'incorrectly reported bit length %d' % size))
|
||||
return
|
||||
if bl not in self.pack:
|
||||
self.pack[bl] = []
|
||||
self.pack[bl].append((generator, modulus))
|
||||
|
||||
def read_file(self, filename):
|
||||
"""
|
||||
@raise IOError: passed from any file operations that fail.
|
||||
"""
|
||||
self.pack = {}
|
||||
f = open(filename, 'r')
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == '#'):
|
||||
continue
|
||||
try:
|
||||
self._parse_modulus(line)
|
||||
except:
|
||||
continue
|
||||
f.close()
|
||||
|
||||
def get_modulus(self, min, prefer, max):
|
||||
bitsizes = self.pack.keys()
|
||||
bitsizes.sort()
|
||||
if len(bitsizes) == 0:
|
||||
raise SSHException('no moduli available')
|
||||
good = -1
|
||||
# find nearest bitsize >= preferred
|
||||
for b in bitsizes:
|
||||
if (b >= prefer) and (b < max) and ((b < good) or (good == -1)):
|
||||
good = b
|
||||
# if that failed, find greatest bitsize >= min
|
||||
if good == -1:
|
||||
for b in bitsizes:
|
||||
if (b >= min) and (b < max) and (b > good):
|
||||
good = b
|
||||
if good == -1:
|
||||
# their entire (min, max) range has no intersection with our range.
|
||||
# if their range is below ours, pick the smallest. otherwise pick
|
||||
# the largest. it'll be out of their range requirement either way,
|
||||
# but we'll be sending them the closest one we have.
|
||||
good = bitsizes[0]
|
||||
if min > good:
|
||||
good = bitsizes[-1]
|
||||
# now pick a random modulus of this bitsize
|
||||
n = _roll_random(self.randpool, len(self.pack[good]))
|
||||
return self.pack[good][n]
|
||||
@ -1,69 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Resource manager.
|
||||
"""
|
||||
|
||||
import weakref
|
||||
|
||||
|
||||
class ResourceManager (object):
|
||||
"""
|
||||
A registry of objects and resources that should be closed when those
|
||||
objects are deleted.
|
||||
|
||||
This is meant to be a safer alternative to python's C{__del__} method,
|
||||
which can cause reference cycles to never be collected. Objects registered
|
||||
with the ResourceManager can be collected but still free resources when
|
||||
they die.
|
||||
|
||||
Resources are registered using L{register}, and when an object is garbage
|
||||
collected, each registered resource is closed by having its C{close()}
|
||||
method called. Multiple resources may be registered per object, but a
|
||||
resource will only be closed once, even if multiple objects register it.
|
||||
(The last object to register it wins.)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._table = {}
|
||||
|
||||
def register(self, obj, resource):
|
||||
"""
|
||||
Register a resource to be closed with an object is collected.
|
||||
|
||||
When the given C{obj} is garbage-collected by the python interpreter,
|
||||
the C{resource} will be closed by having its C{close()} method called.
|
||||
Any exceptions are ignored.
|
||||
|
||||
@param obj: the object to track
|
||||
@type obj: object
|
||||
@param resource: the resource to close when the object is collected
|
||||
@type resource: object
|
||||
"""
|
||||
def callback(ref):
|
||||
try:
|
||||
resource.close()
|
||||
except:
|
||||
pass
|
||||
del self._table[id(resource)]
|
||||
|
||||
# keep the weakref in a table so it sticks around long enough to get
|
||||
# its callback called. :)
|
||||
self._table[id(resource)] = weakref.ref(obj, callback)
|
||||
|
||||
|
||||
# singleton
|
||||
ResourceManager = ResourceManager()
|
||||
@ -1,105 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# -*- coding: ascii -*-
|
||||
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import sys
|
||||
import threading
|
||||
from Crypto.Util.randpool import RandomPool as _RandomPool
|
||||
|
||||
try:
|
||||
import platform
|
||||
except ImportError:
|
||||
platform = None # Not available using Python 2.2
|
||||
|
||||
def _strxor(a, b):
|
||||
assert len(a) == len(b)
|
||||
return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), a, b))
|
||||
|
||||
## Find a strong random entropy source, depending on the detected platform.
|
||||
## WARNING TO DEVELOPERS: This will fail on some systems, but do NOT use
|
||||
## Crypto.Util.randpool.RandomPool as a fall-back. RandomPool will happily run
|
||||
## with very little entropy, thus _silently_ defeating any security that
|
||||
## Paramiko attempts to provide. (This is current as of PyCrypto 2.0.1).
|
||||
|
||||
if ((platform is not None and platform.system().lower() == 'windows') or
|
||||
sys.platform == 'win32'):
|
||||
# MS Windows
|
||||
from paramiko import rng_win32
|
||||
rng_device = rng_win32.open_rng_device()
|
||||
else:
|
||||
# Assume POSIX (any system where /dev/urandom exists)
|
||||
from paramiko import rng_posix
|
||||
rng_device = rng_posix.open_rng_device()
|
||||
|
||||
|
||||
class StrongLockingRandomPool(object):
|
||||
"""Wrapper around RandomPool guaranteeing strong random numbers.
|
||||
|
||||
Crypto.Util.randpool.RandomPool will silently operate even if it is seeded
|
||||
with little or no entropy, and it provides no prediction resistance if its
|
||||
state is ever compromised throughout its runtime. It is also not thread-safe.
|
||||
|
||||
This wrapper augments RandomPool by XORing its output with random bits from
|
||||
the operating system, and by controlling access to the underlying
|
||||
RandomPool using an exclusive lock.
|
||||
"""
|
||||
|
||||
def __init__(self, instance=None):
|
||||
if instance is None:
|
||||
instance = _RandomPool()
|
||||
self.randpool = instance
|
||||
self.randpool_lock = threading.Lock()
|
||||
self.entropy = rng_device
|
||||
|
||||
# Stir 256 bits of entropy from the RNG device into the RandomPool.
|
||||
self.randpool.stir(self.entropy.read(32))
|
||||
self.entropy.randomize()
|
||||
|
||||
def stir(self, s=''):
|
||||
self.randpool_lock.acquire()
|
||||
try:
|
||||
self.randpool.stir(s)
|
||||
finally:
|
||||
self.randpool_lock.release()
|
||||
self.entropy.randomize()
|
||||
|
||||
def randomize(self, N=0):
|
||||
self.randpool_lock.acquire()
|
||||
try:
|
||||
self.randpool.randomize(N)
|
||||
finally:
|
||||
self.randpool_lock.release()
|
||||
self.entropy.randomize()
|
||||
|
||||
def add_event(self, s=''):
|
||||
self.randpool_lock.acquire()
|
||||
try:
|
||||
self.randpool.add_event(s)
|
||||
finally:
|
||||
self.randpool_lock.release()
|
||||
|
||||
def get_bytes(self, N):
|
||||
self.randpool_lock.acquire()
|
||||
try:
|
||||
randpool_data = self.randpool.get_bytes(N)
|
||||
finally:
|
||||
self.randpool_lock.release()
|
||||
entropy_data = self.entropy.read(N)
|
||||
result = _strxor(randpool_data, entropy_data)
|
||||
assert len(randpool_data) == N and len(entropy_data) == N and len(result) == N
|
||||
return result
|
||||
|
||||
# vim:set ts=4 sw=4 sts=4 expandtab:
|
||||
@ -1,93 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# -*- coding: ascii -*-
|
||||
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
class _RNG(object):
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
|
||||
def read(self, bytes):
|
||||
return self.file.read(bytes)
|
||||
|
||||
def close(self):
|
||||
return self.file.close()
|
||||
|
||||
def randomize(self):
|
||||
return
|
||||
|
||||
def open_rng_device(device_path=None):
|
||||
"""Open /dev/urandom and perform some sanity checks."""
|
||||
|
||||
f = None
|
||||
g = None
|
||||
|
||||
if device_path is None:
|
||||
device_path = "/dev/urandom"
|
||||
|
||||
try:
|
||||
# Try to open /dev/urandom now so that paramiko will be able to access
|
||||
# it even if os.chroot() is invoked later.
|
||||
try:
|
||||
f = open(device_path, "rb", 0)
|
||||
except EnvironmentError:
|
||||
raise error("Unable to open /dev/urandom")
|
||||
|
||||
# Open a second file descriptor for sanity checking later.
|
||||
try:
|
||||
g = open(device_path, "rb", 0)
|
||||
except EnvironmentError:
|
||||
raise error("Unable to open /dev/urandom")
|
||||
|
||||
# Check that /dev/urandom is a character special device, not a regular file.
|
||||
st = os.fstat(f.fileno()) # f
|
||||
if stat.S_ISREG(st.st_mode) or not stat.S_ISCHR(st.st_mode):
|
||||
raise error("/dev/urandom is not a character special device")
|
||||
|
||||
st = os.fstat(g.fileno()) # g
|
||||
if stat.S_ISREG(st.st_mode) or not stat.S_ISCHR(st.st_mode):
|
||||
raise error("/dev/urandom is not a character special device")
|
||||
|
||||
# Check that /dev/urandom always returns the number of bytes requested
|
||||
x = f.read(20)
|
||||
y = g.read(20)
|
||||
if len(x) != 20 or len(y) != 20:
|
||||
raise error("Error reading from /dev/urandom: input truncated")
|
||||
|
||||
# Check that different reads return different data
|
||||
if x == y:
|
||||
raise error("/dev/urandom is broken; returning identical data: %r == %r" % (x, y))
|
||||
|
||||
# Close the duplicate file object
|
||||
g.close()
|
||||
|
||||
# Return the first file object
|
||||
return _RNG(f)
|
||||
|
||||
except error:
|
||||
if f is not None:
|
||||
f.close()
|
||||
if g is not None:
|
||||
g.close()
|
||||
raise
|
||||
|
||||
# vim:set ts=4 sw=4 sts=4 expandtab:
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# -*- coding: ascii -*-
|
||||
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
# Try to import the "winrandom" module
|
||||
try:
|
||||
from Crypto.Util import winrandom as _winrandom
|
||||
except ImportError:
|
||||
_winrandom = None
|
||||
|
||||
# Try to import the "urandom" module
|
||||
try:
|
||||
from os import urandom as _urandom
|
||||
except ImportError:
|
||||
_urandom = None
|
||||
|
||||
|
||||
class _RNG(object):
|
||||
def __init__(self, readfunc):
|
||||
self.read = readfunc
|
||||
|
||||
def randomize(self):
|
||||
# According to "Cryptanalysis of the Random Number Generator of the
|
||||
# Windows Operating System", by Leo Dorrendorf and Zvi Gutterman
|
||||
# and Benny Pinkas <http://eprint.iacr.org/2007/419>,
|
||||
# CryptGenRandom only updates its internal state using kernel-provided
|
||||
# random data every 128KiB of output.
|
||||
self.read(128*1024) # discard 128 KiB of output
|
||||
|
||||
def _open_winrandom():
|
||||
if _winrandom is None:
|
||||
raise error("Crypto.Util.winrandom module not found")
|
||||
|
||||
# Check that we can open the winrandom module
|
||||
try:
|
||||
r0 = _winrandom.new()
|
||||
r1 = _winrandom.new()
|
||||
except Exception, exc:
|
||||
raise error("winrandom.new() failed: %s" % str(exc), exc)
|
||||
|
||||
# Check that we can read from the winrandom module
|
||||
try:
|
||||
x = r0.get_bytes(20)
|
||||
y = r1.get_bytes(20)
|
||||
except Exception, exc:
|
||||
raise error("winrandom get_bytes failed: %s" % str(exc), exc)
|
||||
|
||||
# Check that the requested number of bytes are returned
|
||||
if len(x) != 20 or len(y) != 20:
|
||||
raise error("Error reading from winrandom: input truncated")
|
||||
|
||||
# Check that different reads return different data
|
||||
if x == y:
|
||||
raise error("winrandom broken: returning identical data")
|
||||
|
||||
return _RNG(r0.get_bytes)
|
||||
|
||||
def _open_urandom():
|
||||
if _urandom is None:
|
||||
raise error("os.urandom function not found")
|
||||
|
||||
# Check that we can read from os.urandom()
|
||||
try:
|
||||
x = _urandom(20)
|
||||
y = _urandom(20)
|
||||
except Exception, exc:
|
||||
raise error("os.urandom failed: %s" % str(exc), exc)
|
||||
|
||||
# Check that the requested number of bytes are returned
|
||||
if len(x) != 20 or len(y) != 20:
|
||||
raise error("os.urandom failed: input truncated")
|
||||
|
||||
# Check that different reads return different data
|
||||
if x == y:
|
||||
raise error("os.urandom failed: returning identical data")
|
||||
|
||||
return _RNG(_urandom)
|
||||
|
||||
def open_rng_device():
|
||||
# Try using the Crypto.Util.winrandom module
|
||||
try:
|
||||
return _open_winrandom()
|
||||
except error:
|
||||
pass
|
||||
|
||||
# Several versions of PyCrypto do not contain the winrandom module, but
|
||||
# Python >= 2.4 has os.urandom, so try to use that.
|
||||
try:
|
||||
return _open_urandom()
|
||||
except error:
|
||||
pass
|
||||
|
||||
# SECURITY NOTE: DO NOT USE Crypto.Util.randpool.RandomPool HERE!
|
||||
# If we got to this point, RandomPool will silently run with very little
|
||||
# entropy. (This is current as of PyCrypto 2.0.1).
|
||||
# See http://www.lag.net/pipermail/paramiko/2008-January/000599.html
|
||||
# and http://www.lag.net/pipermail/paramiko/2008-April/000678.html
|
||||
|
||||
raise error("Unable to find a strong random entropy source. You cannot run this software securely under the current configuration.")
|
||||
|
||||
# vim:set ts=4 sw=4 sts=4 expandtab:
|
||||
@ -1,183 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{RSAKey}
|
||||
"""
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Hash import SHA, MD5
|
||||
from Crypto.Cipher import DES3
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.ber import BER, BERException
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
class RSAKey (PKey):
|
||||
"""
|
||||
Representation of an RSA key which can be used to sign and verify SSH2
|
||||
data.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None):
|
||||
self.n = None
|
||||
self.e = None
|
||||
self.d = None
|
||||
self.p = None
|
||||
self.q = None
|
||||
if file_obj is not None:
|
||||
self._from_private_key(file_obj, password)
|
||||
return
|
||||
if filename is not None:
|
||||
self._from_private_key_file(filename, password)
|
||||
return
|
||||
if (msg is None) and (data is not None):
|
||||
msg = Message(data)
|
||||
if vals is not None:
|
||||
self.e, self.n = vals
|
||||
else:
|
||||
if msg is None:
|
||||
raise SSHException('Key object may not be empty')
|
||||
if msg.get_string() != 'ssh-rsa':
|
||||
raise SSHException('Invalid key')
|
||||
self.e = msg.get_mpint()
|
||||
self.n = msg.get_mpint()
|
||||
self.size = util.bit_length(self.n)
|
||||
|
||||
def __str__(self):
|
||||
m = Message()
|
||||
m.add_string('ssh-rsa')
|
||||
m.add_mpint(self.e)
|
||||
m.add_mpint(self.n)
|
||||
return str(m)
|
||||
|
||||
def __hash__(self):
|
||||
h = hash(self.get_name())
|
||||
h = h * 37 + hash(self.e)
|
||||
h = h * 37 + hash(self.n)
|
||||
return hash(h)
|
||||
|
||||
def get_name(self):
|
||||
return 'ssh-rsa'
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return self.d is not None
|
||||
|
||||
def sign_ssh_data(self, rpool, data):
|
||||
digest = SHA.new(data).digest()
|
||||
rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
|
||||
sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), '')[0], 0)
|
||||
m = Message()
|
||||
m.add_string('ssh-rsa')
|
||||
m.add_string(sig)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if msg.get_string() != 'ssh-rsa':
|
||||
return False
|
||||
sig = util.inflate_long(msg.get_string(), True)
|
||||
# verify the signature by SHA'ing the data and encrypting it using the
|
||||
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
|
||||
# hash into a string as long as the RSA key.
|
||||
hash_obj = util.inflate_long(self._pkcs1imify(SHA.new(data).digest()), True)
|
||||
rsa = RSA.construct((long(self.n), long(self.e)))
|
||||
return rsa.verify(hash_obj, (sig,))
|
||||
|
||||
def _encode_key(self):
|
||||
if (self.p is None) or (self.q is None):
|
||||
raise SSHException('Not enough key info to write private key file')
|
||||
keylist = [ 0, self.n, self.e, self.d, self.p, self.q,
|
||||
self.d % (self.p - 1), self.d % (self.q - 1),
|
||||
util.mod_inverse(self.q, self.p) ]
|
||||
try:
|
||||
b = BER()
|
||||
b.encode(keylist)
|
||||
except BERException:
|
||||
raise SSHException('Unable to create ber encoding of key')
|
||||
return str(b)
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
self._write_private_key_file('RSA', filename, self._encode_key(), password)
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
self._write_private_key('RSA', file_obj, self._encode_key(), password)
|
||||
|
||||
def generate(bits, progress_func=None):
|
||||
"""
|
||||
Generate a new private RSA key. This factory function can be used to
|
||||
generate a new host key or authentication key.
|
||||
|
||||
@param bits: number of bits the generated key should be.
|
||||
@type bits: int
|
||||
@param progress_func: an optional function to call at key points in
|
||||
key generation (used by C{pyCrypto.PublicKey}).
|
||||
@type progress_func: function
|
||||
@return: new private key
|
||||
@rtype: L{RSAKey}
|
||||
"""
|
||||
randpool.stir()
|
||||
rsa = RSA.generate(bits, randpool.get_bytes, progress_func)
|
||||
key = RSAKey(vals=(rsa.e, rsa.n))
|
||||
key.d = rsa.d
|
||||
key.p = rsa.p
|
||||
key.q = rsa.q
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _pkcs1imify(self, data):
|
||||
"""
|
||||
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
|
||||
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
|
||||
"""
|
||||
SHA1_DIGESTINFO = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
|
||||
size = len(util.deflate_long(self.n, 0))
|
||||
filler = '\xff' * (size - len(SHA1_DIGESTINFO) - len(data) - 3)
|
||||
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file('RSA', filename, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _from_private_key(self, file_obj, password):
|
||||
data = self._read_private_key('RSA', file_obj, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _decode_key(self, data):
|
||||
# private key file contains:
|
||||
# RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException:
|
||||
raise SSHException('Unable to parse key file')
|
||||
if (type(keylist) is not list) or (len(keylist) < 4) or (keylist[0] != 0):
|
||||
raise SSHException('Not a valid RSA private key file (bad ber encoding)')
|
||||
self.n = keylist[1]
|
||||
self.e = keylist[2]
|
||||
self.d = keylist[3]
|
||||
# not really needed
|
||||
self.p = keylist[4]
|
||||
self.q = keylist[5]
|
||||
self.size = util.bit_length(self.n)
|
||||
@ -1,629 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{ServerInterface} is an interface to override for server support.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
|
||||
|
||||
class InteractiveQuery (object):
|
||||
"""
|
||||
A query (set of prompts) for a user during interactive authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, name='', instructions='', *prompts):
|
||||
"""
|
||||
Create a new interactive query to send to the client. The name and
|
||||
instructions are optional, but are generally displayed to the end
|
||||
user. A list of prompts may be included, or they may be added via
|
||||
the L{add_prompt} method.
|
||||
|
||||
@param name: name of this query
|
||||
@type name: str
|
||||
@param instructions: user instructions (usually short) about this query
|
||||
@type instructions: str
|
||||
@param prompts: one or more authentication prompts
|
||||
@type prompts: str
|
||||
"""
|
||||
self.name = name
|
||||
self.instructions = instructions
|
||||
self.prompts = []
|
||||
for x in prompts:
|
||||
if (type(x) is str) or (type(x) is unicode):
|
||||
self.add_prompt(x)
|
||||
else:
|
||||
self.add_prompt(x[0], x[1])
|
||||
|
||||
def add_prompt(self, prompt, echo=True):
|
||||
"""
|
||||
Add a prompt to this query. The prompt should be a (reasonably short)
|
||||
string. Multiple prompts can be added to the same query.
|
||||
|
||||
@param prompt: the user prompt
|
||||
@type prompt: str
|
||||
@param echo: C{True} (default) if the user's response should be echoed;
|
||||
C{False} if not (for a password or similar)
|
||||
@type echo: bool
|
||||
"""
|
||||
self.prompts.append((prompt, echo))
|
||||
|
||||
|
||||
class ServerInterface (object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
in server mode.
|
||||
|
||||
Methods on this class are called from paramiko's primary thread, so you
|
||||
shouldn't do too much work in them. (Certainly nothing that blocks or
|
||||
sleeps.)
|
||||
"""
|
||||
|
||||
def check_channel_request(self, kind, chanid):
|
||||
"""
|
||||
Determine if a channel request of a given type will be granted, and
|
||||
return C{OPEN_SUCCEEDED} or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
If you allow channel requests (and an ssh server that didn't would be
|
||||
useless), you should also override some of the channel request methods
|
||||
below, which are used to determine which services will be allowed on
|
||||
a given channel:
|
||||
- L{check_channel_pty_request}
|
||||
- L{check_channel_shell_request}
|
||||
- L{check_channel_subsystem_request}
|
||||
- L{check_channel_window_change_request}
|
||||
- L{check_channel_x11_request}
|
||||
|
||||
The C{chanid} parameter is a small number that uniquely identifies the
|
||||
channel within a L{Transport}. A L{Channel} object is not created
|
||||
unless this method returns C{OPEN_SUCCEEDED} -- once a
|
||||
L{Channel} object is created, you can call L{Channel.get_id} to
|
||||
retrieve the channel ID.
|
||||
|
||||
The return value should either be C{OPEN_SUCCEEDED} (or
|
||||
C{0}) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
|
||||
- C{OPEN_FAILED_CONNECT_FAILED}
|
||||
- C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
|
||||
- C{OPEN_FAILED_RESOURCE_SHORTAGE}
|
||||
|
||||
The default implementation always returns
|
||||
C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
|
||||
|
||||
@param kind: the kind of channel the client would like to open
|
||||
(usually C{"session"}).
|
||||
@type kind: str
|
||||
@param chanid: ID of the channel
|
||||
@type chanid: int
|
||||
@return: a success or failure code (listed above)
|
||||
@rtype: int
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
def get_allowed_auths(self, username):
|
||||
"""
|
||||
Return a list of authentication methods supported by the server.
|
||||
This list is sent to clients attempting to authenticate, to inform them
|
||||
of authentication methods that might be successful.
|
||||
|
||||
The "list" is actually a string of comma-separated names of types of
|
||||
authentication. Possible values are C{"password"}, C{"publickey"},
|
||||
and C{"none"}.
|
||||
|
||||
The default implementation always returns C{"password"}.
|
||||
|
||||
@param username: the username requesting authentication.
|
||||
@type username: str
|
||||
@return: a comma-separated list of authentication types
|
||||
@rtype: str
|
||||
"""
|
||||
return 'password'
|
||||
|
||||
def check_auth_none(self, username):
|
||||
"""
|
||||
Determine if a client may open channels with no (further)
|
||||
authentication.
|
||||
|
||||
Return L{AUTH_FAILED} if the client must authenticate, or
|
||||
L{AUTH_SUCCESSFUL} if it's okay for the client to not
|
||||
authenticate.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
|
||||
@param username: the username of the client.
|
||||
@type username: str
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds.
|
||||
@rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_password(self, username, password):
|
||||
"""
|
||||
Determine if a given username and password supplied by the client is
|
||||
acceptable for use in authentication.
|
||||
|
||||
Return L{AUTH_FAILED} if the password is not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the password is accepted and completes
|
||||
the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
authentication is stateful, and this key is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
|
||||
@param username: the username of the authenticating client.
|
||||
@type username: str
|
||||
@param password: the password given by the client.
|
||||
@type password: str
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
|
||||
successful, but authentication must continue.
|
||||
@rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_publickey(self, username, key):
|
||||
"""
|
||||
Determine if a given key supplied by the client is acceptable for use
|
||||
in authentication. You should override this method in server mode to
|
||||
check the username and key and decide if you would accept a signature
|
||||
made using this key.
|
||||
|
||||
Return L{AUTH_FAILED} if the key is not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the key is accepted and completes the
|
||||
authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
authentication is stateful, and this password is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
Note that you don't have to actually verify any key signtature here.
|
||||
If you're willing to accept the key, paramiko will do the work of
|
||||
verifying the client's signature.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
|
||||
@param username: the username of the authenticating client
|
||||
@type username: str
|
||||
@param key: the key object provided by the client
|
||||
@type key: L{PKey <pkey.PKey>}
|
||||
@return: L{AUTH_FAILED} if the client can't authenticate
|
||||
with this key; L{AUTH_SUCCESSFUL} if it can;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with
|
||||
this key but must continue with authentication
|
||||
@rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_interactive(self, username, submethods):
|
||||
"""
|
||||
Begin an interactive authentication challenge, if supported. You
|
||||
should override this method in server mode if you want to support the
|
||||
C{"keyboard-interactive"} auth type, which requires you to send a
|
||||
series of questions for the client to answer.
|
||||
|
||||
Return L{AUTH_FAILED} if this auth method isn't supported. Otherwise,
|
||||
you should return an L{InteractiveQuery} object containing the prompts
|
||||
and instructions for the user. The response will be sent via a call
|
||||
to L{check_auth_interactive_response}.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
|
||||
@param username: the username of the authenticating client
|
||||
@type username: str
|
||||
@param submethods: a comma-separated list of methods preferred by the
|
||||
client (usually empty)
|
||||
@type submethods: str
|
||||
@return: L{AUTH_FAILED} if this auth method isn't supported; otherwise
|
||||
an object containing queries for the user
|
||||
@rtype: int or L{InteractiveQuery}
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_auth_interactive_response(self, responses):
|
||||
"""
|
||||
Continue or finish an interactive authentication challenge, if
|
||||
supported. You should override this method in server mode if you want
|
||||
to support the C{"keyboard-interactive"} auth type.
|
||||
|
||||
Return L{AUTH_FAILED} if the responses are not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the responses are accepted and complete
|
||||
the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
authentication is stateful, and this set of responses is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
If you wish to continue interactive authentication with more questions,
|
||||
you may return an L{InteractiveQuery} object, which should cause the
|
||||
client to respond with more answers, calling this method again. This
|
||||
cycle can continue indefinitely.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
|
||||
@param responses: list of responses from the client
|
||||
@type responses: list(str)
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if the interactive auth is
|
||||
successful, but authentication must continue; otherwise an object
|
||||
containing queries for the user
|
||||
@rtype: int or L{InteractiveQuery}
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
def check_port_forward_request(self, address, port):
|
||||
"""
|
||||
Handle a request for port forwarding. The client is asking that
|
||||
connections to the given address and port be forwarded back across
|
||||
this ssh connection. An address of C{"0.0.0.0"} indicates a global
|
||||
address (any address associated with this server) and a port of C{0}
|
||||
indicates that no specific port is requested (usually the OS will pick
|
||||
a port).
|
||||
|
||||
The default implementation always returns C{False}, rejecting the
|
||||
port forwarding request. If the request is accepted, you should return
|
||||
the port opened for listening.
|
||||
|
||||
@param address: the requested address
|
||||
@type address: str
|
||||
@param port: the requested port
|
||||
@type port: int
|
||||
@return: the port number that was opened for listening, or C{False} to
|
||||
reject
|
||||
@rtype: int
|
||||
"""
|
||||
return False
|
||||
|
||||
def cancel_port_forward_request(self, address, port):
|
||||
"""
|
||||
The client would like to cancel a previous port-forwarding request.
|
||||
If the given address and port is being forwarded across this ssh
|
||||
connection, the port should be closed.
|
||||
|
||||
@param address: the forwarded address
|
||||
@type address: str
|
||||
@param port: the forwarded port
|
||||
@type port: int
|
||||
"""
|
||||
pass
|
||||
|
||||
def check_global_request(self, kind, msg):
|
||||
"""
|
||||
Handle a global request of the given C{kind}. This method is called
|
||||
in server mode and client mode, whenever the remote host makes a global
|
||||
request. If there are any arguments to the request, they will be in
|
||||
C{msg}.
|
||||
|
||||
There aren't any useful global requests defined, aside from port
|
||||
forwarding, so usually this type of request is an extension to the
|
||||
protocol.
|
||||
|
||||
If the request was successful and you would like to return contextual
|
||||
data to the remote host, return a tuple. Items in the tuple will be
|
||||
sent back with the successful result. (Note that the items in the
|
||||
tuple can only be strings, ints, longs, or bools.)
|
||||
|
||||
The default implementation always returns C{False}, indicating that it
|
||||
does not support any global requests.
|
||||
|
||||
@note: Port forwarding requests are handled separately, in
|
||||
L{check_port_forward_request}.
|
||||
|
||||
@param kind: the kind of global request being made.
|
||||
@type kind: str
|
||||
@param msg: any extra arguments to the request.
|
||||
@type msg: L{Message}
|
||||
@return: C{True} or a tuple of data if the request was granted;
|
||||
C{False} otherwise.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
### Channel requests
|
||||
|
||||
|
||||
def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight,
|
||||
modes):
|
||||
"""
|
||||
Determine if a pseudo-terminal of the given dimensions (usually
|
||||
requested for shell access) can be provided on the given channel.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param term: type of terminal requested (for example, C{"vt100"}).
|
||||
@type term: str
|
||||
@param width: width of screen in characters.
|
||||
@type width: int
|
||||
@param height: height of screen in characters.
|
||||
@type height: int
|
||||
@param pixelwidth: width of screen in pixels, if known (may be C{0} if
|
||||
unknown).
|
||||
@type pixelwidth: int
|
||||
@param pixelheight: height of screen in pixels, if known (may be C{0}
|
||||
if unknown).
|
||||
@type pixelheight: int
|
||||
@return: C{True} if the psuedo-terminal has been allocated; C{False}
|
||||
otherwise.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_shell_request(self, channel):
|
||||
"""
|
||||
Determine if a shell will be provided to the client on the given
|
||||
channel. If this method returns C{True}, the channel should be
|
||||
connected to the stdin/stdout of a shell (or something that acts like
|
||||
a shell).
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@return: C{True} if this channel is now hooked up to a shell; C{False}
|
||||
if a shell can't or won't be provided.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_exec_request(self, channel, command):
|
||||
"""
|
||||
Determine if a shell command will be executed for the client. If this
|
||||
method returns C{True}, the channel should be connected to the stdin,
|
||||
stdout, and stderr of the shell command.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param command: the command to execute.
|
||||
@type command: str
|
||||
@return: C{True} if this channel is now hooked up to the stdin,
|
||||
stdout, and stderr of the executing command; C{False} if the
|
||||
command will not be executed.
|
||||
@rtype: bool
|
||||
|
||||
@since: 1.1
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_subsystem_request(self, channel, name):
|
||||
"""
|
||||
Determine if a requested subsystem will be provided to the client on
|
||||
the given channel. If this method returns C{True}, all future I/O
|
||||
through this channel will be assumed to be connected to the requested
|
||||
subsystem. An example of a subsystem is C{sftp}.
|
||||
|
||||
The default implementation checks for a subsystem handler assigned via
|
||||
L{Transport.set_subsystem_handler}.
|
||||
If one has been set, the handler is invoked and this method returns
|
||||
C{True}. Otherwise it returns C{False}.
|
||||
|
||||
@note: Because the default implementation uses the L{Transport} to
|
||||
identify valid subsystems, you probably won't need to override this
|
||||
method.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@return: C{True} if this channel is now hooked up to the requested
|
||||
subsystem; C{False} if that subsystem can't or won't be provided.
|
||||
@rtype: bool
|
||||
"""
|
||||
handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name)
|
||||
if handler_class is None:
|
||||
return False
|
||||
handler = handler_class(channel, name, self, *larg, **kwarg)
|
||||
handler.start()
|
||||
return True
|
||||
|
||||
def check_channel_window_change_request(self, channel, width, height, pixelwidth, pixelheight):
|
||||
"""
|
||||
Determine if the pseudo-terminal on the given channel can be resized.
|
||||
This only makes sense if a pty was previously allocated on it.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param width: width of screen in characters.
|
||||
@type width: int
|
||||
@param height: height of screen in characters.
|
||||
@type height: int
|
||||
@param pixelwidth: width of screen in pixels, if known (may be C{0} if
|
||||
unknown).
|
||||
@type pixelwidth: int
|
||||
@param pixelheight: height of screen in pixels, if known (may be C{0}
|
||||
if unknown).
|
||||
@type pixelheight: int
|
||||
@return: C{True} if the terminal was resized; C{False} if not.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number):
|
||||
"""
|
||||
Determine if the client will be provided with an X11 session. If this
|
||||
method returns C{True}, X11 applications should be routed through new
|
||||
SSH channels, using L{Transport.open_x11_channel}.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the X11 request arrived on
|
||||
@type channel: L{Channel}
|
||||
@param single_connection: C{True} if only a single X11 channel should
|
||||
be opened
|
||||
@type single_connection: bool
|
||||
@param auth_protocol: the protocol used for X11 authentication
|
||||
@type auth_protocol: str
|
||||
@param auth_cookie: the cookie used to authenticate to X11
|
||||
@type auth_cookie: str
|
||||
@param screen_number: the number of the X11 screen to connect to
|
||||
@type screen_number: int
|
||||
@return: C{True} if the X11 session was opened; C{False} if not
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
|
||||
"""
|
||||
Determine if a local port forwarding channel will be granted, and
|
||||
return C{OPEN_SUCCEEDED} or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
The C{chanid} parameter is a small number that uniquely identifies the
|
||||
channel within a L{Transport}. A L{Channel} object is not created
|
||||
unless this method returns C{OPEN_SUCCEEDED} -- once a
|
||||
L{Channel} object is created, you can call L{Channel.get_id} to
|
||||
retrieve the channel ID.
|
||||
|
||||
The origin and destination parameters are (ip_address, port) tuples
|
||||
that correspond to both ends of the TCP connection in the forwarding
|
||||
tunnel.
|
||||
|
||||
The return value should either be C{OPEN_SUCCEEDED} (or
|
||||
C{0}) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
|
||||
- C{OPEN_FAILED_CONNECT_FAILED}
|
||||
- C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
|
||||
- C{OPEN_FAILED_RESOURCE_SHORTAGE}
|
||||
|
||||
The default implementation always returns
|
||||
C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
|
||||
|
||||
@param chanid: ID of the channel
|
||||
@type chanid: int
|
||||
@param origin: 2-tuple containing the IP address and port of the
|
||||
originator (client side)
|
||||
@type origin: tuple
|
||||
@param destination: 2-tuple containing the IP address and port of the
|
||||
destination (server side)
|
||||
@type destination: tuple
|
||||
@return: a success or failure code (listed above)
|
||||
@rtype: int
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
|
||||
class SubsystemHandler (threading.Thread):
|
||||
"""
|
||||
Handler for a subsytem in server mode. If you create a subclass of this
|
||||
class and pass it to
|
||||
L{Transport.set_subsystem_handler},
|
||||
an object of this
|
||||
class will be created for each request for this subsystem. Each new object
|
||||
will be executed within its own new thread by calling L{start_subsystem}.
|
||||
When that method completes, the channel is closed.
|
||||
|
||||
For example, if you made a subclass C{MP3Handler} and registered it as the
|
||||
handler for subsystem C{"mp3"}, then whenever a client has successfully
|
||||
authenticated and requests subsytem C{"mp3"}, an object of class
|
||||
C{MP3Handler} will be created, and L{start_subsystem} will be called on
|
||||
it from a new thread.
|
||||
"""
|
||||
def __init__(self, channel, name, server):
|
||||
"""
|
||||
Create a new handler for a channel. This is used by L{ServerInterface}
|
||||
to start up a new handler when a channel requests this subsystem. You
|
||||
don't need to override this method, but if you do, be sure to pass the
|
||||
C{channel} and C{name} parameters through to the original C{__init__}
|
||||
method here.
|
||||
|
||||
@param channel: the channel associated with this subsystem request.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param server: the server object for the session that started this
|
||||
subsystem
|
||||
@type server: L{ServerInterface}
|
||||
"""
|
||||
threading.Thread.__init__(self, target=self._run)
|
||||
self.__channel = channel
|
||||
self.__transport = channel.get_transport()
|
||||
self.__name = name
|
||||
self.__server = server
|
||||
|
||||
def get_server(self):
|
||||
"""
|
||||
Return the L{ServerInterface} object associated with this channel and
|
||||
subsystem.
|
||||
|
||||
@rtype: L{ServerInterface}
|
||||
"""
|
||||
return self.__server
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.__transport._log(DEBUG, 'Starting handler for subsystem %s' % self.__name)
|
||||
self.start_subsystem(self.__name, self.__transport, self.__channel)
|
||||
except Exception, e:
|
||||
self.__transport._log(ERROR, 'Exception in subsystem handler for "%s": %s' %
|
||||
(self.__name, str(e)))
|
||||
self.__transport._log(ERROR, util.tb_strings())
|
||||
try:
|
||||
self.finish_subsystem()
|
||||
except:
|
||||
pass
|
||||
|
||||
def start_subsystem(self, name, transport, channel):
|
||||
"""
|
||||
Process an ssh subsystem in server mode. This method is called on a
|
||||
new object (and in a new thread) for each subsystem request. It is
|
||||
assumed that all subsystem logic will take place here, and when the
|
||||
subsystem is finished, this method will return. After this method
|
||||
returns, the channel is closed.
|
||||
|
||||
The combination of C{transport} and C{channel} are unique; this handler
|
||||
corresponds to exactly one L{Channel} on one L{Transport}.
|
||||
|
||||
@note: It is the responsibility of this method to exit if the
|
||||
underlying L{Transport} is closed. This can be done by checking
|
||||
L{Transport.is_active} or noticing an EOF
|
||||
on the L{Channel}. If this method loops forever without checking
|
||||
for this case, your python interpreter may refuse to exit because
|
||||
this thread will still be running.
|
||||
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param transport: the server-mode L{Transport}.
|
||||
@type transport: L{Transport}
|
||||
@param channel: the channel associated with this subsystem request.
|
||||
@type channel: L{Channel}
|
||||
"""
|
||||
pass
|
||||
|
||||
def finish_subsystem(self):
|
||||
"""
|
||||
Perform any cleanup at the end of a subsystem. The default
|
||||
implementation just closes the channel.
|
||||
|
||||
@since: 1.1
|
||||
"""
|
||||
self.__channel.close()
|
||||
@ -1,185 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.channel import Channel
|
||||
from paramiko.message import Message
|
||||
|
||||
|
||||
CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \
|
||||
CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \
|
||||
CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK \
|
||||
= range(1, 21)
|
||||
CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106)
|
||||
CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202)
|
||||
|
||||
SFTP_OK = 0
|
||||
SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, \
|
||||
SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED = range(1, 9)
|
||||
|
||||
SFTP_DESC = [ 'Success',
|
||||
'End of file',
|
||||
'No such file',
|
||||
'Permission denied',
|
||||
'Failure',
|
||||
'Bad message',
|
||||
'No connection',
|
||||
'Connection lost',
|
||||
'Operation unsupported' ]
|
||||
|
||||
SFTP_FLAG_READ = 0x1
|
||||
SFTP_FLAG_WRITE = 0x2
|
||||
SFTP_FLAG_APPEND = 0x4
|
||||
SFTP_FLAG_CREATE = 0x8
|
||||
SFTP_FLAG_TRUNC = 0x10
|
||||
SFTP_FLAG_EXCL = 0x20
|
||||
|
||||
_VERSION = 3
|
||||
|
||||
|
||||
# for debugging
|
||||
CMD_NAMES = {
|
||||
CMD_INIT: 'init',
|
||||
CMD_VERSION: 'version',
|
||||
CMD_OPEN: 'open',
|
||||
CMD_CLOSE: 'close',
|
||||
CMD_READ: 'read',
|
||||
CMD_WRITE: 'write',
|
||||
CMD_LSTAT: 'lstat',
|
||||
CMD_FSTAT: 'fstat',
|
||||
CMD_SETSTAT: 'setstat',
|
||||
CMD_FSETSTAT: 'fsetstat',
|
||||
CMD_OPENDIR: 'opendir',
|
||||
CMD_READDIR: 'readdir',
|
||||
CMD_REMOVE: 'remove',
|
||||
CMD_MKDIR: 'mkdir',
|
||||
CMD_RMDIR: 'rmdir',
|
||||
CMD_REALPATH: 'realpath',
|
||||
CMD_STAT: 'stat',
|
||||
CMD_RENAME: 'rename',
|
||||
CMD_READLINK: 'readlink',
|
||||
CMD_SYMLINK: 'symlink',
|
||||
CMD_STATUS: 'status',
|
||||
CMD_HANDLE: 'handle',
|
||||
CMD_DATA: 'data',
|
||||
CMD_NAME: 'name',
|
||||
CMD_ATTRS: 'attrs',
|
||||
CMD_EXTENDED: 'extended',
|
||||
CMD_EXTENDED_REPLY: 'extended_reply'
|
||||
}
|
||||
|
||||
|
||||
class SFTPError (Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseSFTP (object):
|
||||
def __init__(self):
|
||||
self.logger = util.get_logger('paramiko.sftp')
|
||||
self.sock = None
|
||||
self.ultra_debug = False
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _send_version(self):
|
||||
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
|
||||
t, data = self._read_packet()
|
||||
if t != CMD_VERSION:
|
||||
raise SFTPError('Incompatible sftp protocol')
|
||||
version = struct.unpack('>I', data[:4])[0]
|
||||
# if version != _VERSION:
|
||||
# raise SFTPError('Incompatible sftp protocol')
|
||||
return version
|
||||
|
||||
def _send_server_version(self):
|
||||
# winscp will freak out if the server sends version info before the
|
||||
# client finishes sending INIT.
|
||||
t, data = self._read_packet()
|
||||
if t != CMD_INIT:
|
||||
raise SFTPError('Incompatible sftp protocol')
|
||||
version = struct.unpack('>I', data[:4])[0]
|
||||
# advertise that we support "check-file"
|
||||
extension_pairs = [ 'check-file', 'md5,sha1' ]
|
||||
msg = Message()
|
||||
msg.add_int(_VERSION)
|
||||
msg.add(*extension_pairs)
|
||||
self._send_packet(CMD_VERSION, str(msg))
|
||||
return version
|
||||
|
||||
def _log(self, level, msg, *args):
|
||||
self.logger.log(level, msg, *args)
|
||||
|
||||
def _write_all(self, out):
|
||||
while len(out) > 0:
|
||||
n = self.sock.send(out)
|
||||
if n <= 0:
|
||||
raise EOFError()
|
||||
if n == len(out):
|
||||
return
|
||||
out = out[n:]
|
||||
return
|
||||
|
||||
def _read_all(self, n):
|
||||
out = ''
|
||||
while n > 0:
|
||||
if isinstance(self.sock, socket.socket):
|
||||
# sometimes sftp is used directly over a socket instead of
|
||||
# through a paramiko channel. in this case, check periodically
|
||||
# if the socket is closed. (for some reason, recv() won't ever
|
||||
# return or raise an exception, but calling select on a closed
|
||||
# socket will.)
|
||||
while True:
|
||||
read, write, err = select.select([ self.sock ], [], [], 0.1)
|
||||
if len(read) > 0:
|
||||
x = self.sock.recv(n)
|
||||
break
|
||||
else:
|
||||
x = self.sock.recv(n)
|
||||
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
out += x
|
||||
n -= len(x)
|
||||
return out
|
||||
|
||||
def _send_packet(self, t, packet):
|
||||
#self._log(DEBUG2, 'write: %s (len=%d)' % (CMD_NAMES.get(t, '0x%02x' % t), len(packet)))
|
||||
out = struct.pack('>I', len(packet) + 1) + chr(t) + packet
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, util.format_binary(out, 'OUT: '))
|
||||
self._write_all(out)
|
||||
|
||||
def _read_packet(self):
|
||||
x = self._read_all(4)
|
||||
# most sftp servers won't accept packets larger than about 32k, so
|
||||
# anything with the high byte set (> 16MB) is just garbage.
|
||||
if x[0] != '\x00':
|
||||
raise SFTPError('Garbage packet received')
|
||||
size = struct.unpack('>I', x)[0]
|
||||
data = self._read_all(size)
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, util.format_binary(data, 'IN: '));
|
||||
if size > 0:
|
||||
t = ord(data[0])
|
||||
#self._log(DEBUG2, 'read: %s (len=%d)' % (CMD_NAMES.get(t), '0x%02x' % t, len(data)-1))
|
||||
return t, data[1:]
|
||||
return 0, ''
|
||||
@ -1,220 +0,0 @@
|
||||
# Copyright (C) 2003-2006 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
import stat
|
||||
import time
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
|
||||
|
||||
class SFTPAttributes (object):
|
||||
"""
|
||||
Representation of the attributes of a file (or proxied file) for SFTP in
|
||||
client or server mode. It attemps to mirror the object returned by
|
||||
C{os.stat} as closely as possible, so it may have the following fields,
|
||||
with the same meanings as those returned by an C{os.stat} object:
|
||||
- st_size
|
||||
- st_uid
|
||||
- st_gid
|
||||
- st_mode
|
||||
- st_atime
|
||||
- st_mtime
|
||||
|
||||
Because SFTP allows flags to have other arbitrary named attributes, these
|
||||
are stored in a dict named C{attr}. Occasionally, the filename is also
|
||||
stored, in C{filename}.
|
||||
"""
|
||||
|
||||
FLAG_SIZE = 1
|
||||
FLAG_UIDGID = 2
|
||||
FLAG_PERMISSIONS = 4
|
||||
FLAG_AMTIME = 8
|
||||
FLAG_EXTENDED = 0x80000000L
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new (empty) SFTPAttributes object. All fields will be empty.
|
||||
"""
|
||||
self._flags = 0
|
||||
self.st_size = None
|
||||
self.st_uid = None
|
||||
self.st_gid = None
|
||||
self.st_mode = None
|
||||
self.st_atime = None
|
||||
self.st_mtime = None
|
||||
self.attr = {}
|
||||
|
||||
def from_stat(cls, obj, filename=None):
|
||||
"""
|
||||
Create an SFTPAttributes object from an existing C{stat} object (an
|
||||
object returned by C{os.stat}).
|
||||
|
||||
@param obj: an object returned by C{os.stat} (or equivalent).
|
||||
@type obj: object
|
||||
@param filename: the filename associated with this file.
|
||||
@type filename: str
|
||||
@return: new L{SFTPAttributes} object with the same attribute fields.
|
||||
@rtype: L{SFTPAttributes}
|
||||
"""
|
||||
attr = cls()
|
||||
attr.st_size = obj.st_size
|
||||
attr.st_uid = obj.st_uid
|
||||
attr.st_gid = obj.st_gid
|
||||
attr.st_mode = obj.st_mode
|
||||
attr.st_atime = obj.st_atime
|
||||
attr.st_mtime = obj.st_mtime
|
||||
if filename is not None:
|
||||
attr.filename = filename
|
||||
return attr
|
||||
from_stat = classmethod(from_stat)
|
||||
|
||||
def __repr__(self):
|
||||
return '<SFTPAttributes: %s>' % self._debug_str()
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _from_msg(cls, msg, filename=None, longname=None):
|
||||
attr = cls()
|
||||
attr._unpack(msg)
|
||||
if filename is not None:
|
||||
attr.filename = filename
|
||||
if longname is not None:
|
||||
attr.longname = longname
|
||||
return attr
|
||||
_from_msg = classmethod(_from_msg)
|
||||
|
||||
def _unpack(self, msg):
|
||||
self._flags = msg.get_int()
|
||||
if self._flags & self.FLAG_SIZE:
|
||||
self.st_size = msg.get_int64()
|
||||
if self._flags & self.FLAG_UIDGID:
|
||||
self.st_uid = msg.get_int()
|
||||
self.st_gid = msg.get_int()
|
||||
if self._flags & self.FLAG_PERMISSIONS:
|
||||
self.st_mode = msg.get_int()
|
||||
if self._flags & self.FLAG_AMTIME:
|
||||
self.st_atime = msg.get_int()
|
||||
self.st_mtime = msg.get_int()
|
||||
if self._flags & self.FLAG_EXTENDED:
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
self.attr[msg.get_string()] = msg.get_string()
|
||||
|
||||
def _pack(self, msg):
|
||||
self._flags = 0
|
||||
if self.st_size is not None:
|
||||
self._flags |= self.FLAG_SIZE
|
||||
if (self.st_uid is not None) and (self.st_gid is not None):
|
||||
self._flags |= self.FLAG_UIDGID
|
||||
if self.st_mode is not None:
|
||||
self._flags |= self.FLAG_PERMISSIONS
|
||||
if (self.st_atime is not None) and (self.st_mtime is not None):
|
||||
self._flags |= self.FLAG_AMTIME
|
||||
if len(self.attr) > 0:
|
||||
self._flags |= self.FLAG_EXTENDED
|
||||
msg.add_int(self._flags)
|
||||
if self._flags & self.FLAG_SIZE:
|
||||
msg.add_int64(self.st_size)
|
||||
if self._flags & self.FLAG_UIDGID:
|
||||
msg.add_int(self.st_uid)
|
||||
msg.add_int(self.st_gid)
|
||||
if self._flags & self.FLAG_PERMISSIONS:
|
||||
msg.add_int(self.st_mode)
|
||||
if self._flags & self.FLAG_AMTIME:
|
||||
# throw away any fractional seconds
|
||||
msg.add_int(long(self.st_atime))
|
||||
msg.add_int(long(self.st_mtime))
|
||||
if self._flags & self.FLAG_EXTENDED:
|
||||
msg.add_int(len(self.attr))
|
||||
for key, val in self.attr.iteritems():
|
||||
msg.add_string(key)
|
||||
msg.add_string(val)
|
||||
return
|
||||
|
||||
def _debug_str(self):
|
||||
out = '[ '
|
||||
if self.st_size is not None:
|
||||
out += 'size=%d ' % self.st_size
|
||||
if (self.st_uid is not None) and (self.st_gid is not None):
|
||||
out += 'uid=%d gid=%d ' % (self.st_uid, self.st_gid)
|
||||
if self.st_mode is not None:
|
||||
out += 'mode=' + oct(self.st_mode) + ' '
|
||||
if (self.st_atime is not None) and (self.st_mtime is not None):
|
||||
out += 'atime=%d mtime=%d ' % (self.st_atime, self.st_mtime)
|
||||
for k, v in self.attr.iteritems():
|
||||
out += '"%s"=%r ' % (str(k), v)
|
||||
out += ']'
|
||||
return out
|
||||
|
||||
def _rwx(n, suid, sticky=False):
|
||||
if suid:
|
||||
suid = 2
|
||||
out = '-r'[n >> 2] + '-w'[(n >> 1) & 1]
|
||||
if sticky:
|
||||
out += '-xTt'[suid + (n & 1)]
|
||||
else:
|
||||
out += '-xSs'[suid + (n & 1)]
|
||||
return out
|
||||
_rwx = staticmethod(_rwx)
|
||||
|
||||
def __str__(self):
|
||||
"create a unix-style long description of the file (like ls -l)"
|
||||
if self.st_mode is not None:
|
||||
kind = stat.S_IFMT(self.st_mode)
|
||||
if kind == stat.S_IFIFO:
|
||||
ks = 'p'
|
||||
elif kind == stat.S_IFCHR:
|
||||
ks = 'c'
|
||||
elif kind == stat.S_IFDIR:
|
||||
ks = 'd'
|
||||
elif kind == stat.S_IFBLK:
|
||||
ks = 'b'
|
||||
elif kind == stat.S_IFREG:
|
||||
ks = '-'
|
||||
elif kind == stat.S_IFLNK:
|
||||
ks = 'l'
|
||||
elif kind == stat.S_IFSOCK:
|
||||
ks = 's'
|
||||
else:
|
||||
ks = '?'
|
||||
ks += self._rwx((self.st_mode & 0700) >> 6, self.st_mode & stat.S_ISUID)
|
||||
ks += self._rwx((self.st_mode & 070) >> 3, self.st_mode & stat.S_ISGID)
|
||||
ks += self._rwx(self.st_mode & 7, self.st_mode & stat.S_ISVTX, True)
|
||||
else:
|
||||
ks = '?---------'
|
||||
# compute display date
|
||||
if (self.st_mtime is None) or (self.st_mtime == 0xffffffff):
|
||||
# shouldn't really happen
|
||||
datestr = '(unknown date)'
|
||||
else:
|
||||
if abs(time.time() - self.st_mtime) > 15552000:
|
||||
# (15552000 = 6 months)
|
||||
datestr = time.strftime('%d %b %Y', time.localtime(self.st_mtime))
|
||||
else:
|
||||
datestr = time.strftime('%d %b %H:%M', time.localtime(self.st_mtime))
|
||||
filename = getattr(self, 'filename', '?')
|
||||
|
||||
# not all servers support uid/gid
|
||||
uid = self.st_uid
|
||||
gid = self.st_gid
|
||||
if uid is None:
|
||||
uid = 0
|
||||
if gid is None:
|
||||
gid = 0
|
||||
|
||||
return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename)
|
||||
|
||||
@ -1,723 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Client-mode SFTP support.
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import errno
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
import time
|
||||
import weakref
|
||||
|
||||
from paramiko.sftp import *
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.sftp_file import SFTPFile
|
||||
|
||||
|
||||
def _to_unicode(s):
|
||||
"""
|
||||
decode a string as ascii or utf8 if possible (as required by the sftp
|
||||
protocol). if neither works, just return a byte string because the server
|
||||
probably doesn't know the filename's encoding.
|
||||
"""
|
||||
try:
|
||||
return s.encode('ascii')
|
||||
except UnicodeError:
|
||||
try:
|
||||
return s.decode('utf-8')
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
|
||||
class SFTPClient (BaseSFTP):
|
||||
"""
|
||||
SFTP client object. C{SFTPClient} is used to open an sftp session across
|
||||
an open ssh L{Transport} and do remote file operations.
|
||||
"""
|
||||
|
||||
def __init__(self, sock):
|
||||
"""
|
||||
Create an SFTP client from an existing L{Channel}. The channel
|
||||
should already have requested the C{"sftp"} subsystem.
|
||||
|
||||
An alternate way to create an SFTP client context is by using
|
||||
L{from_transport}.
|
||||
|
||||
@param sock: an open L{Channel} using the C{"sftp"} subsystem
|
||||
@type sock: L{Channel}
|
||||
|
||||
@raise SSHException: if there's an exception while negotiating
|
||||
sftp
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
self.sock = sock
|
||||
self.ultra_debug = False
|
||||
self.request_number = 1
|
||||
# lock for request_number
|
||||
self._lock = threading.Lock()
|
||||
self._cwd = None
|
||||
# request # -> SFTPFile
|
||||
self._expecting = weakref.WeakValueDictionary()
|
||||
if type(sock) is Channel:
|
||||
# override default logger
|
||||
transport = self.sock.get_transport()
|
||||
self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
|
||||
self.ultra_debug = transport.get_hexdump()
|
||||
try:
|
||||
server_version = self._send_version()
|
||||
except EOFError, x:
|
||||
raise SSHException('EOF during negotiation')
|
||||
self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
|
||||
|
||||
def from_transport(cls, t):
|
||||
"""
|
||||
Create an SFTP client channel from an open L{Transport}.
|
||||
|
||||
@param t: an open L{Transport} which is already authenticated
|
||||
@type t: L{Transport}
|
||||
@return: a new L{SFTPClient} object, referring to an sftp session
|
||||
(channel) across the transport
|
||||
@rtype: L{SFTPClient}
|
||||
"""
|
||||
chan = t.open_session()
|
||||
if chan is None:
|
||||
return None
|
||||
chan.invoke_subsystem('sftp')
|
||||
return cls(chan)
|
||||
from_transport = classmethod(from_transport)
|
||||
|
||||
def _log(self, level, msg, *args):
|
||||
if isinstance(msg, list):
|
||||
for m in msg:
|
||||
super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args)))
|
||||
else:
|
||||
super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args)))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the SFTP session and its underlying channel.
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
self._log(INFO, 'sftp session closed.')
|
||||
self.sock.close()
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Return the underlying L{Channel} object for this SFTP session. This
|
||||
might be useful for doing things like setting a timeout on the channel.
|
||||
|
||||
@return: the SSH channel
|
||||
@rtype: L{Channel}
|
||||
|
||||
@since: 1.7.1
|
||||
"""
|
||||
return self.sock
|
||||
|
||||
def listdir(self, path='.'):
|
||||
"""
|
||||
Return a list containing the names of the entries in the given C{path}.
|
||||
The list is in arbitrary order. It does not include the special
|
||||
entries C{'.'} and C{'..'} even if they are present in the folder.
|
||||
This method is meant to mirror C{os.listdir} as closely as possible.
|
||||
For a list of full L{SFTPAttributes} objects, see L{listdir_attr}.
|
||||
|
||||
@param path: path to list (defaults to C{'.'})
|
||||
@type path: str
|
||||
@return: list of filenames
|
||||
@rtype: list of str
|
||||
"""
|
||||
return [f.filename for f in self.listdir_attr(path)]
|
||||
|
||||
def listdir_attr(self, path='.'):
|
||||
"""
|
||||
Return a list containing L{SFTPAttributes} objects corresponding to
|
||||
files in the given C{path}. The list is in arbitrary order. It does
|
||||
not include the special entries C{'.'} and C{'..'} even if they are
|
||||
present in the folder.
|
||||
|
||||
The returned L{SFTPAttributes} objects will each have an additional
|
||||
field: C{longname}, which may contain a formatted string of the file's
|
||||
attributes, in unix format. The content of this string will probably
|
||||
depend on the SFTP server implementation.
|
||||
|
||||
@param path: path to list (defaults to C{'.'})
|
||||
@type path: str
|
||||
@return: list of attributes
|
||||
@rtype: list of L{SFTPAttributes}
|
||||
|
||||
@since: 1.2
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'listdir(%r)' % path)
|
||||
t, msg = self._request(CMD_OPENDIR, path)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError('Expected handle')
|
||||
handle = msg.get_string()
|
||||
filelist = []
|
||||
while True:
|
||||
try:
|
||||
t, msg = self._request(CMD_READDIR, handle)
|
||||
except EOFError, e:
|
||||
# done with handle
|
||||
break
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError('Expected name response')
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
filename = _to_unicode(msg.get_string())
|
||||
longname = _to_unicode(msg.get_string())
|
||||
attr = SFTPAttributes._from_msg(msg, filename, longname)
|
||||
if (filename != '.') and (filename != '..'):
|
||||
filelist.append(attr)
|
||||
self._request(CMD_CLOSE, handle)
|
||||
return filelist
|
||||
|
||||
def open(self, filename, mode='r', bufsize=-1):
|
||||
"""
|
||||
Open a file on the remote server. The arguments are the same as for
|
||||
python's built-in C{file} (aka C{open}). A file-like object is
|
||||
returned, which closely mimics the behavior of a normal python file
|
||||
object.
|
||||
|
||||
The mode indicates how the file is to be opened: C{'r'} for reading,
|
||||
C{'w'} for writing (truncating an existing file), C{'a'} for appending,
|
||||
C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
|
||||
existing file), C{'a+'} for reading/appending. The python C{'b'} flag
|
||||
is ignored, since SSH treats all files as binary. The C{'U'} flag is
|
||||
supported in a compatible way.
|
||||
|
||||
Since 1.5.2, an C{'x'} flag indicates that the operation should only
|
||||
succeed if the file was created and did not previously exist. This has
|
||||
no direct mapping to python's file flags, but is commonly known as the
|
||||
C{O_EXCL} flag in posix.
|
||||
|
||||
The file will be buffered in standard python style by default, but
|
||||
can be altered with the C{bufsize} parameter. C{0} turns off
|
||||
buffering, C{1} uses line buffering, and any number greater than 1
|
||||
(C{>1}) uses that specific buffer size.
|
||||
|
||||
@param filename: name of the file to open
|
||||
@type filename: str
|
||||
@param mode: mode (python-style) to open in
|
||||
@type mode: str
|
||||
@param bufsize: desired buffering (-1 = default buffer size)
|
||||
@type bufsize: int
|
||||
@return: a file object representing the open file
|
||||
@rtype: SFTPFile
|
||||
|
||||
@raise IOError: if the file could not be opened.
|
||||
"""
|
||||
filename = self._adjust_cwd(filename)
|
||||
self._log(DEBUG, 'open(%r, %r)' % (filename, mode))
|
||||
imode = 0
|
||||
if ('r' in mode) or ('+' in mode):
|
||||
imode |= SFTP_FLAG_READ
|
||||
if ('w' in mode) or ('+' in mode) or ('a' in mode):
|
||||
imode |= SFTP_FLAG_WRITE
|
||||
if ('w' in mode):
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
|
||||
if ('a' in mode):
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
|
||||
if ('x' in mode):
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
|
||||
attrblock = SFTPAttributes()
|
||||
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError('Expected handle')
|
||||
handle = msg.get_string()
|
||||
self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
|
||||
return SFTPFile(self, handle, mode, bufsize)
|
||||
|
||||
# python continues to vacillate about "open" vs "file"...
|
||||
file = open
|
||||
|
||||
def remove(self, path):
|
||||
"""
|
||||
Remove the file at the given path. This only works on files; for
|
||||
removing folders (directories), use L{rmdir}.
|
||||
|
||||
@param path: path (absolute or relative) of the file to remove
|
||||
@type path: str
|
||||
|
||||
@raise IOError: if the path refers to a folder (directory)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'remove(%r)' % path)
|
||||
self._request(CMD_REMOVE, path)
|
||||
|
||||
unlink = remove
|
||||
|
||||
def rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename a file or folder from C{oldpath} to C{newpath}.
|
||||
|
||||
@param oldpath: existing name of the file or folder
|
||||
@type oldpath: str
|
||||
@param newpath: new name for the file or folder
|
||||
@type newpath: str
|
||||
|
||||
@raise IOError: if C{newpath} is a folder, or something else goes
|
||||
wrong
|
||||
"""
|
||||
oldpath = self._adjust_cwd(oldpath)
|
||||
newpath = self._adjust_cwd(newpath)
|
||||
self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath))
|
||||
self._request(CMD_RENAME, oldpath, newpath)
|
||||
|
||||
def mkdir(self, path, mode=0777):
|
||||
"""
|
||||
Create a folder (directory) named C{path} with numeric mode C{mode}.
|
||||
The default mode is 0777 (octal). On some systems, mode is ignored.
|
||||
Where it is used, the current umask value is first masked out.
|
||||
|
||||
@param path: name of the folder to create
|
||||
@type path: str
|
||||
@param mode: permissions (posix-style) for the newly-created folder
|
||||
@type mode: int
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self._request(CMD_MKDIR, path, attr)
|
||||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove the folder named C{path}.
|
||||
|
||||
@param path: name of the folder to remove
|
||||
@type path: str
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'rmdir(%r)' % path)
|
||||
self._request(CMD_RMDIR, path)
|
||||
|
||||
def stat(self, path):
|
||||
"""
|
||||
Retrieve information about a file on the remote system. The return
|
||||
value is an object whose attributes correspond to the attributes of
|
||||
python's C{stat} structure as returned by C{os.stat}, except that it
|
||||
contains fewer fields. An SFTP server may return as much or as little
|
||||
info as it wants, so the results may vary from server to server.
|
||||
|
||||
Unlike a python C{stat} object, the result may not be accessed as a
|
||||
tuple. This is mostly due to the author's slack factor.
|
||||
|
||||
The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
|
||||
C{st_atime}, and C{st_mtime}.
|
||||
|
||||
@param path: the filename to stat
|
||||
@type path: str
|
||||
@return: an object containing attributes about the given file
|
||||
@rtype: SFTPAttributes
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'stat(%r)' % path)
|
||||
t, msg = self._request(CMD_STAT, path)
|
||||
if t != CMD_ATTRS:
|
||||
raise SFTPError('Expected attributes')
|
||||
return SFTPAttributes._from_msg(msg)
|
||||
|
||||
def lstat(self, path):
|
||||
"""
|
||||
Retrieve information about a file on the remote system, without
|
||||
following symbolic links (shortcuts). This otherwise behaves exactly
|
||||
the same as L{stat}.
|
||||
|
||||
@param path: the filename to stat
|
||||
@type path: str
|
||||
@return: an object containing attributes about the given file
|
||||
@rtype: SFTPAttributes
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'lstat(%r)' % path)
|
||||
t, msg = self._request(CMD_LSTAT, path)
|
||||
if t != CMD_ATTRS:
|
||||
raise SFTPError('Expected attributes')
|
||||
return SFTPAttributes._from_msg(msg)
|
||||
|
||||
def symlink(self, source, dest):
|
||||
"""
|
||||
Create a symbolic link (shortcut) of the C{source} path at
|
||||
C{destination}.
|
||||
|
||||
@param source: path of the original file
|
||||
@type source: str
|
||||
@param dest: path of the newly created symlink
|
||||
@type dest: str
|
||||
"""
|
||||
dest = self._adjust_cwd(dest)
|
||||
self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
|
||||
if type(source) is unicode:
|
||||
source = source.encode('utf-8')
|
||||
self._request(CMD_SYMLINK, source, dest)
|
||||
|
||||
def chmod(self, path, mode):
|
||||
"""
|
||||
Change the mode (permissions) of a file. The permissions are
|
||||
unix-style and identical to those used by python's C{os.chmod}
|
||||
function.
|
||||
|
||||
@param path: path of the file to change the permissions of
|
||||
@type path: str
|
||||
@param mode: new permissions
|
||||
@type mode: int
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def chown(self, path, uid, gid):
|
||||
"""
|
||||
Change the owner (C{uid}) and group (C{gid}) of a file. As with
|
||||
python's C{os.chown} function, you must pass both arguments, so if you
|
||||
only want to change one, use L{stat} first to retrieve the current
|
||||
owner and group.
|
||||
|
||||
@param path: path of the file to change the owner and group of
|
||||
@type path: str
|
||||
@param uid: new owner's uid
|
||||
@type uid: int
|
||||
@param gid: new group id
|
||||
@type gid: int
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_uid, attr.st_gid = uid, gid
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def utime(self, path, times):
|
||||
"""
|
||||
Set the access and modified times of the file specified by C{path}. If
|
||||
C{times} is C{None}, then the file's access and modified times are set
|
||||
to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
|
||||
of the form C{(atime, mtime)}, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from python
|
||||
for the sake of consistency -- I apologize.
|
||||
|
||||
@param path: path of the file to modify
|
||||
@type path: str
|
||||
@param times: C{None} or a tuple of (access time, modified time) in
|
||||
standard internet epoch time (seconds since 01 January 1970 GMT)
|
||||
@type times: tuple(int)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
if times is None:
|
||||
times = (time.time(), time.time())
|
||||
self._log(DEBUG, 'utime(%r, %r)' % (path, times))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_atime, attr.st_mtime = times
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def truncate(self, path, size):
|
||||
"""
|
||||
Change the size of the file specified by C{path}. This usually extends
|
||||
or shrinks the size of the file, just like the C{truncate()} method on
|
||||
python file objects.
|
||||
|
||||
@param path: path of the file to modify
|
||||
@type path: str
|
||||
@param size: the new size of the file
|
||||
@type size: int or long
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_size = size
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
def readlink(self, path):
|
||||
"""
|
||||
Return the target of a symbolic link (shortcut). You can use
|
||||
L{symlink} to create these. The result may be either an absolute or
|
||||
relative pathname.
|
||||
|
||||
@param path: path of the symbolic link file
|
||||
@type path: str
|
||||
@return: target path
|
||||
@rtype: str
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'readlink(%r)' % path)
|
||||
t, msg = self._request(CMD_READLINK, path)
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError('Expected name response')
|
||||
count = msg.get_int()
|
||||
if count == 0:
|
||||
return None
|
||||
if count != 1:
|
||||
raise SFTPError('Readlink returned %d results' % count)
|
||||
return _to_unicode(msg.get_string())
|
||||
|
||||
def normalize(self, path):
|
||||
"""
|
||||
Return the normalized path (on the server) of a given path. This
|
||||
can be used to quickly resolve symbolic links or determine what the
|
||||
server is considering to be the "current folder" (by passing C{'.'}
|
||||
as C{path}).
|
||||
|
||||
@param path: path to be normalized
|
||||
@type path: str
|
||||
@return: normalized form of the given path
|
||||
@rtype: str
|
||||
|
||||
@raise IOError: if the path can't be resolved on the server
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'normalize(%r)' % path)
|
||||
t, msg = self._request(CMD_REALPATH, path)
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError('Expected name response')
|
||||
count = msg.get_int()
|
||||
if count != 1:
|
||||
raise SFTPError('Realpath returned %d results' % count)
|
||||
return _to_unicode(msg.get_string())
|
||||
|
||||
def chdir(self, path):
|
||||
"""
|
||||
Change the "current directory" of this SFTP session. Since SFTP
|
||||
doesn't really have the concept of a current working directory, this
|
||||
is emulated by paramiko. Once you use this method to set a working
|
||||
directory, all operations on this SFTPClient object will be relative
|
||||
to that path. You can pass in C{None} to stop using a current working
|
||||
directory.
|
||||
|
||||
@param path: new current working directory
|
||||
@type path: str
|
||||
|
||||
@raise IOError: if the requested path doesn't exist on the server
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
if path is None:
|
||||
self._cwd = None
|
||||
return
|
||||
if not stat.S_ISDIR(self.stat(path).st_mode):
|
||||
raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
|
||||
self._cwd = self.normalize(path).encode('utf-8')
|
||||
|
||||
def getcwd(self):
|
||||
"""
|
||||
Return the "current working directory" for this SFTP session, as
|
||||
emulated by paramiko. If no directory has been set with L{chdir},
|
||||
this method will return C{None}.
|
||||
|
||||
@return: the current working directory on the server, or C{None}
|
||||
@rtype: str
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
return self._cwd
|
||||
|
||||
def put(self, localpath, remotepath, callback=None):
|
||||
"""
|
||||
Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
|
||||
Any exception raised by operations will be passed through. This
|
||||
method is primarily provided as a convenience.
|
||||
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
@param localpath: the local file to copy
|
||||
@type localpath: str
|
||||
@param remotepath: the destination path on the SFTP server
|
||||
@type remotepath: str
|
||||
@param callback: optional callback function that accepts the bytes
|
||||
transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
@type callback: function(int, int)
|
||||
@return: an object containing attributes about the given file
|
||||
(since 1.7.4)
|
||||
@rtype: SFTPAttributes
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
file_size = os.stat(localpath).st_size
|
||||
fl = file(localpath, 'rb')
|
||||
try:
|
||||
fr = self.file(remotepath, 'wb')
|
||||
fr.set_pipelined(True)
|
||||
size = 0
|
||||
try:
|
||||
while True:
|
||||
data = fl.read(32768)
|
||||
if len(data) == 0:
|
||||
break
|
||||
fr.write(data)
|
||||
size += len(data)
|
||||
if callback is not None:
|
||||
callback(size, file_size)
|
||||
finally:
|
||||
fr.close()
|
||||
finally:
|
||||
fl.close()
|
||||
s = self.stat(remotepath)
|
||||
if s.st_size != size:
|
||||
raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
|
||||
return s
|
||||
|
||||
def get(self, remotepath, localpath, callback=None):
|
||||
"""
|
||||
Copy a remote file (C{remotepath}) from the SFTP server to the local
|
||||
host as C{localpath}. Any exception raised by operations will be
|
||||
passed through. This method is primarily provided as a convenience.
|
||||
|
||||
@param remotepath: the remote file to copy
|
||||
@type remotepath: str
|
||||
@param localpath: the destination path on the local host
|
||||
@type localpath: str
|
||||
@param callback: optional callback function that accepts the bytes
|
||||
transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
@type callback: function(int, int)
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
fr = self.file(remotepath, 'rb')
|
||||
file_size = self.stat(remotepath).st_size
|
||||
fr.prefetch()
|
||||
try:
|
||||
fl = file(localpath, 'wb')
|
||||
try:
|
||||
size = 0
|
||||
while True:
|
||||
data = fr.read(32768)
|
||||
if len(data) == 0:
|
||||
break
|
||||
fl.write(data)
|
||||
size += len(data)
|
||||
if callback is not None:
|
||||
callback(size, file_size)
|
||||
finally:
|
||||
fl.close()
|
||||
finally:
|
||||
fr.close()
|
||||
s = os.stat(localpath)
|
||||
if s.st_size != size:
|
||||
raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _request(self, t, *arg):
|
||||
num = self._async_request(type(None), t, *arg)
|
||||
return self._read_response(num)
|
||||
|
||||
def _async_request(self, fileobj, t, *arg):
|
||||
# this method may be called from other threads (prefetch)
|
||||
self._lock.acquire()
|
||||
try:
|
||||
msg = Message()
|
||||
msg.add_int(self.request_number)
|
||||
for item in arg:
|
||||
if type(item) is int:
|
||||
msg.add_int(item)
|
||||
elif type(item) is long:
|
||||
msg.add_int64(item)
|
||||
elif type(item) is str:
|
||||
msg.add_string(item)
|
||||
elif type(item) is SFTPAttributes:
|
||||
item._pack(msg)
|
||||
else:
|
||||
raise Exception('unknown type for %r type %r' % (item, type(item)))
|
||||
num = self.request_number
|
||||
self._expecting[num] = fileobj
|
||||
self._send_packet(t, str(msg))
|
||||
self.request_number += 1
|
||||
finally:
|
||||
self._lock.release()
|
||||
return num
|
||||
|
||||
def _read_response(self, waitfor=None):
|
||||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError, e:
|
||||
raise SSHException('Server connection dropped: %s' % (str(e),))
|
||||
msg = Message(data)
|
||||
num = msg.get_int()
|
||||
if num not in self._expecting:
|
||||
# might be response for a file that was closed before responses came back
|
||||
self._log(DEBUG, 'Unexpected response #%d' % (num,))
|
||||
if waitfor is None:
|
||||
# just doing a single check
|
||||
break
|
||||
continue
|
||||
fileobj = self._expecting[num]
|
||||
del self._expecting[num]
|
||||
if num == waitfor:
|
||||
# synchronous
|
||||
if t == CMD_STATUS:
|
||||
self._convert_status(msg)
|
||||
return t, msg
|
||||
if fileobj is not type(None):
|
||||
fileobj._async_response(t, msg)
|
||||
if waitfor is None:
|
||||
# just doing a single check
|
||||
break
|
||||
return (None, None)
|
||||
|
||||
def _finish_responses(self, fileobj):
|
||||
while fileobj in self._expecting.values():
|
||||
self._read_response()
|
||||
fileobj._check_exception()
|
||||
|
||||
def _convert_status(self, msg):
|
||||
"""
|
||||
Raises EOFError or IOError on error status; otherwise does nothing.
|
||||
"""
|
||||
code = msg.get_int()
|
||||
text = msg.get_string()
|
||||
if code == SFTP_OK:
|
||||
return
|
||||
elif code == SFTP_EOF:
|
||||
raise EOFError(text)
|
||||
elif code == SFTP_NO_SUCH_FILE:
|
||||
# clever idea from john a. meinel: map the error codes to errno
|
||||
raise IOError(errno.ENOENT, text)
|
||||
elif code == SFTP_PERMISSION_DENIED:
|
||||
raise IOError(errno.EACCES, text)
|
||||
else:
|
||||
raise IOError(text)
|
||||
|
||||
def _adjust_cwd(self, path):
|
||||
"""
|
||||
Return an adjusted path if we're emulating a "current working
|
||||
directory" for the server.
|
||||
"""
|
||||
if type(path) is unicode:
|
||||
path = path.encode('utf-8')
|
||||
if self._cwd is None:
|
||||
return path
|
||||
if (len(path) > 0) and (path[0] == '/'):
|
||||
# absolute path
|
||||
return path
|
||||
if self._cwd == '/':
|
||||
return self._cwd + path
|
||||
return self._cwd + '/' + path
|
||||
|
||||
|
||||
class SFTP (SFTPClient):
|
||||
"an alias for L{SFTPClient} for backwards compatability"
|
||||
pass
|
||||
@ -1,473 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SFTPFile}
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
|
||||
|
||||
class SFTPFile (BufferedFile):
|
||||
"""
|
||||
Proxy object for a file on the remote server, in client mode SFTP.
|
||||
"""
|
||||
|
||||
# Some sftp servers will choke if you send read/write requests larger than
|
||||
# this size.
|
||||
MAX_REQUEST_SIZE = 32768
|
||||
|
||||
def __init__(self, sftp, handle, mode='r', bufsize=-1):
|
||||
BufferedFile.__init__(self)
|
||||
self.sftp = sftp
|
||||
self.handle = handle
|
||||
BufferedFile._set_mode(self, mode, bufsize)
|
||||
self.pipelined = False
|
||||
self._prefetching = False
|
||||
self._prefetch_done = False
|
||||
self._prefetch_data = {}
|
||||
self._prefetch_reads = []
|
||||
self._saved_exception = None
|
||||
|
||||
def __del__(self):
|
||||
self._close(async=True)
|
||||
|
||||
def close(self):
|
||||
self._close(async=False)
|
||||
|
||||
def _close(self, async=False):
|
||||
# We allow double-close without signaling an error, because real
|
||||
# Python file objects do. However, we must protect against actually
|
||||
# sending multiple CMD_CLOSE packets, because after we close our
|
||||
# handle, the same handle may be re-allocated by the server, and we
|
||||
# may end up mysteriously closing some random other file. (This is
|
||||
# especially important because we unconditionally call close() from
|
||||
# __del__.)
|
||||
if self._closed:
|
||||
return
|
||||
self.sftp._log(DEBUG, 'close(%s)' % hexlify(self.handle))
|
||||
if self.pipelined:
|
||||
self.sftp._finish_responses(self)
|
||||
BufferedFile.close(self)
|
||||
try:
|
||||
if async:
|
||||
# GC'd file handle could be called from an arbitrary thread -- don't wait for a response
|
||||
self.sftp._async_request(type(None), CMD_CLOSE, self.handle)
|
||||
else:
|
||||
self.sftp._request(CMD_CLOSE, self.handle)
|
||||
except EOFError:
|
||||
# may have outlived the Transport connection
|
||||
pass
|
||||
except (IOError, socket.error):
|
||||
# may have outlived the Transport connection
|
||||
pass
|
||||
|
||||
def _data_in_prefetch_requests(self, offset, size):
|
||||
k = [i for i in self._prefetch_reads if i[0] <= offset]
|
||||
if len(k) == 0:
|
||||
return False
|
||||
k.sort(lambda x, y: cmp(x[0], y[0]))
|
||||
buf_offset, buf_size = k[-1]
|
||||
if buf_offset + buf_size <= offset:
|
||||
# prefetch request ends before this one begins
|
||||
return False
|
||||
if buf_offset + buf_size >= offset + size:
|
||||
# inclusive
|
||||
return True
|
||||
# well, we have part of the request. see if another chunk has the rest.
|
||||
return self._data_in_prefetch_requests(buf_offset + buf_size, offset + size - buf_offset - buf_size)
|
||||
|
||||
def _data_in_prefetch_buffers(self, offset):
|
||||
"""
|
||||
if a block of data is present in the prefetch buffers, at the given
|
||||
offset, return the offset of the relevant prefetch buffer. otherwise,
|
||||
return None. this guarantees nothing about the number of bytes
|
||||
collected in the prefetch buffer so far.
|
||||
"""
|
||||
k = [i for i in self._prefetch_data.keys() if i <= offset]
|
||||
if len(k) == 0:
|
||||
return None
|
||||
index = max(k)
|
||||
buf_offset = offset - index
|
||||
if buf_offset >= len(self._prefetch_data[index]):
|
||||
# it's not here
|
||||
return None
|
||||
return index
|
||||
|
||||
def _read_prefetch(self, size):
|
||||
"""
|
||||
read data out of the prefetch buffer, if possible. if the data isn't
|
||||
in the buffer, return None. otherwise, behaves like a normal read.
|
||||
"""
|
||||
# while not closed, and haven't fetched past the current position, and haven't reached EOF...
|
||||
while True:
|
||||
offset = self._data_in_prefetch_buffers(self._realpos)
|
||||
if offset is not None:
|
||||
break
|
||||
if self._prefetch_done or self._closed:
|
||||
break
|
||||
self.sftp._read_response()
|
||||
self._check_exception()
|
||||
if offset is None:
|
||||
self._prefetching = False
|
||||
return None
|
||||
prefetch = self._prefetch_data[offset]
|
||||
del self._prefetch_data[offset]
|
||||
|
||||
buf_offset = self._realpos - offset
|
||||
if buf_offset > 0:
|
||||
self._prefetch_data[offset] = prefetch[:buf_offset]
|
||||
prefetch = prefetch[buf_offset:]
|
||||
if size < len(prefetch):
|
||||
self._prefetch_data[self._realpos + size] = prefetch[size:]
|
||||
prefetch = prefetch[:size]
|
||||
return prefetch
|
||||
|
||||
def _read(self, size):
|
||||
size = min(size, self.MAX_REQUEST_SIZE)
|
||||
if self._prefetching:
|
||||
data = self._read_prefetch(size)
|
||||
if data is not None:
|
||||
return data
|
||||
t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size))
|
||||
if t != CMD_DATA:
|
||||
raise SFTPError('Expected data')
|
||||
return msg.get_string()
|
||||
|
||||
def _write(self, data):
|
||||
# may write less than requested if it would exceed max packet size
|
||||
chunk = min(len(data), self.MAX_REQUEST_SIZE)
|
||||
req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk]))
|
||||
if not self.pipelined or self.sftp.sock.recv_ready():
|
||||
t, msg = self.sftp._read_response(req)
|
||||
if t != CMD_STATUS:
|
||||
raise SFTPError('Expected status')
|
||||
# convert_status already called
|
||||
return chunk
|
||||
|
||||
def settimeout(self, timeout):
|
||||
"""
|
||||
Set a timeout on read/write operations on the underlying socket or
|
||||
ssh L{Channel}.
|
||||
|
||||
@see: L{Channel.settimeout}
|
||||
@param timeout: seconds to wait for a pending read/write operation
|
||||
before raising C{socket.timeout}, or C{None} for no timeout
|
||||
@type timeout: float
|
||||
"""
|
||||
self.sftp.sock.settimeout(timeout)
|
||||
|
||||
def gettimeout(self):
|
||||
"""
|
||||
Returns the timeout in seconds (as a float) associated with the socket
|
||||
or ssh L{Channel} used for this file.
|
||||
|
||||
@see: L{Channel.gettimeout}
|
||||
@rtype: float
|
||||
"""
|
||||
return self.sftp.sock.gettimeout()
|
||||
|
||||
def setblocking(self, blocking):
|
||||
"""
|
||||
Set blocking or non-blocking mode on the underiying socket or ssh
|
||||
L{Channel}.
|
||||
|
||||
@see: L{Channel.setblocking}
|
||||
@param blocking: 0 to set non-blocking mode; non-0 to set blocking
|
||||
mode.
|
||||
@type blocking: int
|
||||
"""
|
||||
self.sftp.sock.setblocking(blocking)
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
self.flush()
|
||||
if whence == self.SEEK_SET:
|
||||
self._realpos = self._pos = offset
|
||||
elif whence == self.SEEK_CUR:
|
||||
self._pos += offset
|
||||
self._realpos = self._pos
|
||||
else:
|
||||
self._realpos = self._pos = self._get_size() + offset
|
||||
self._rbuffer = ''
|
||||
|
||||
def stat(self):
|
||||
"""
|
||||
Retrieve information about this file from the remote system. This is
|
||||
exactly like L{SFTP.stat}, except that it operates on an already-open
|
||||
file.
|
||||
|
||||
@return: an object containing attributes about this file.
|
||||
@rtype: SFTPAttributes
|
||||
"""
|
||||
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||
if t != CMD_ATTRS:
|
||||
raise SFTPError('Expected attributes')
|
||||
return SFTPAttributes._from_msg(msg)
|
||||
|
||||
def chmod(self, mode):
|
||||
"""
|
||||
Change the mode (permissions) of this file. The permissions are
|
||||
unix-style and identical to those used by python's C{os.chmod}
|
||||
function.
|
||||
|
||||
@param mode: new permissions
|
||||
@type mode: int
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'chmod(%s, %r)' % (hexlify(self.handle), mode))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self.sftp._request(CMD_FSETSTAT, self.handle, attr)
|
||||
|
||||
def chown(self, uid, gid):
|
||||
"""
|
||||
Change the owner (C{uid}) and group (C{gid}) of this file. As with
|
||||
python's C{os.chown} function, you must pass both arguments, so if you
|
||||
only want to change one, use L{stat} first to retrieve the current
|
||||
owner and group.
|
||||
|
||||
@param uid: new owner's uid
|
||||
@type uid: int
|
||||
@param gid: new group id
|
||||
@type gid: int
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'chown(%s, %r, %r)' % (hexlify(self.handle), uid, gid))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_uid, attr.st_gid = uid, gid
|
||||
self.sftp._request(CMD_FSETSTAT, self.handle, attr)
|
||||
|
||||
def utime(self, times):
|
||||
"""
|
||||
Set the access and modified times of this file. If
|
||||
C{times} is C{None}, then the file's access and modified times are set
|
||||
to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
|
||||
of the form C{(atime, mtime)}, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from python
|
||||
for the sake of consistency -- I apologize.
|
||||
|
||||
@param times: C{None} or a tuple of (access time, modified time) in
|
||||
standard internet epoch time (seconds since 01 January 1970 GMT)
|
||||
@type times: tuple(int)
|
||||
"""
|
||||
if times is None:
|
||||
times = (time.time(), time.time())
|
||||
self.sftp._log(DEBUG, 'utime(%s, %r)' % (hexlify(self.handle), times))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_atime, attr.st_mtime = times
|
||||
self.sftp._request(CMD_FSETSTAT, self.handle, attr)
|
||||
|
||||
def truncate(self, size):
|
||||
"""
|
||||
Change the size of this file. This usually extends
|
||||
or shrinks the size of the file, just like the C{truncate()} method on
|
||||
python file objects.
|
||||
|
||||
@param size: the new size of the file
|
||||
@type size: int or long
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'truncate(%s, %r)' % (hexlify(self.handle), size))
|
||||
attr = SFTPAttributes()
|
||||
attr.st_size = size
|
||||
self.sftp._request(CMD_FSETSTAT, self.handle, attr)
|
||||
|
||||
def check(self, hash_algorithm, offset=0, length=0, block_size=0):
|
||||
"""
|
||||
Ask the server for a hash of a section of this file. This can be used
|
||||
to verify a successful upload or download, or for various rsync-like
|
||||
operations.
|
||||
|
||||
The file is hashed from C{offset}, for C{length} bytes. If C{length}
|
||||
is 0, the remainder of the file is hashed. Thus, if both C{offset}
|
||||
and C{length} are zero, the entire file is hashed.
|
||||
|
||||
Normally, C{block_size} will be 0 (the default), and this method will
|
||||
return a byte string representing the requested hash (for example, a
|
||||
string of length 16 for MD5, or 20 for SHA-1). If a non-zero
|
||||
C{block_size} is given, each chunk of the file (from C{offset} to
|
||||
C{offset + length}) of C{block_size} bytes is computed as a separate
|
||||
hash. The hash results are all concatenated and returned as a single
|
||||
string.
|
||||
|
||||
For example, C{check('sha1', 0, 1024, 512)} will return a string of
|
||||
length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes
|
||||
of the file, and the last 20 bytes will be the SHA-1 of the next 512
|
||||
bytes.
|
||||
|
||||
@param hash_algorithm: the name of the hash algorithm to use (normally
|
||||
C{"sha1"} or C{"md5"})
|
||||
@type hash_algorithm: str
|
||||
@param offset: offset into the file to begin hashing (0 means to start
|
||||
from the beginning)
|
||||
@type offset: int or long
|
||||
@param length: number of bytes to hash (0 means continue to the end of
|
||||
the file)
|
||||
@type length: int or long
|
||||
@param block_size: number of bytes to hash per result (must not be less
|
||||
than 256; 0 means to compute only one hash of the entire segment)
|
||||
@type block_size: int
|
||||
@return: string of bytes representing the hash of each block,
|
||||
concatenated together
|
||||
@rtype: str
|
||||
|
||||
@note: Many (most?) servers don't support this extension yet.
|
||||
|
||||
@raise IOError: if the server doesn't support the "check-file"
|
||||
extension, or possibly doesn't support the hash algorithm
|
||||
requested
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
t, msg = self.sftp._request(CMD_EXTENDED, 'check-file', self.handle,
|
||||
hash_algorithm, long(offset), long(length), block_size)
|
||||
ext = msg.get_string()
|
||||
alg = msg.get_string()
|
||||
data = msg.get_remainder()
|
||||
return data
|
||||
|
||||
def set_pipelined(self, pipelined=True):
|
||||
"""
|
||||
Turn on/off the pipelining of write operations to this file. When
|
||||
pipelining is on, paramiko won't wait for the server response after
|
||||
each write operation. Instead, they're collected as they come in.
|
||||
At the first non-write operation (including L{close}), all remaining
|
||||
server responses are collected. This means that if there was an error
|
||||
with one of your later writes, an exception might be thrown from
|
||||
within L{close} instead of L{write}.
|
||||
|
||||
By default, files are I{not} pipelined.
|
||||
|
||||
@param pipelined: C{True} if pipelining should be turned on for this
|
||||
file; C{False} otherwise
|
||||
@type pipelined: bool
|
||||
|
||||
@since: 1.5
|
||||
"""
|
||||
self.pipelined = pipelined
|
||||
|
||||
def prefetch(self):
|
||||
"""
|
||||
Pre-fetch the remaining contents of this file in anticipation of
|
||||
future L{read} calls. If reading the entire file, pre-fetching can
|
||||
dramatically improve the download speed by avoiding roundtrip latency.
|
||||
The file's contents are incrementally buffered in a background thread.
|
||||
|
||||
The prefetched data is stored in a buffer until read via the L{read}
|
||||
method. Once data has been read, it's removed from the buffer. The
|
||||
data may be read in a random order (using L{seek}); chunks of the
|
||||
buffer that haven't been read will continue to be buffered.
|
||||
|
||||
@since: 1.5.1
|
||||
"""
|
||||
size = self.stat().st_size
|
||||
# queue up async reads for the rest of the file
|
||||
chunks = []
|
||||
n = self._realpos
|
||||
while n < size:
|
||||
chunk = min(self.MAX_REQUEST_SIZE, size - n)
|
||||
chunks.append((n, chunk))
|
||||
n += chunk
|
||||
if len(chunks) > 0:
|
||||
self._start_prefetch(chunks)
|
||||
|
||||
def readv(self, chunks):
|
||||
"""
|
||||
Read a set of blocks from the file by (offset, length). This is more
|
||||
efficient than doing a series of L{seek} and L{read} calls, since the
|
||||
prefetch machinery is used to retrieve all the requested blocks at
|
||||
once.
|
||||
|
||||
@param chunks: a list of (offset, length) tuples indicating which
|
||||
sections of the file to read
|
||||
@type chunks: list(tuple(long, int))
|
||||
@return: a list of blocks read, in the same order as in C{chunks}
|
||||
@rtype: list(str)
|
||||
|
||||
@since: 1.5.4
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'readv(%s, %r)' % (hexlify(self.handle), chunks))
|
||||
|
||||
read_chunks = []
|
||||
for offset, size in chunks:
|
||||
# don't fetch data that's already in the prefetch buffer
|
||||
if self._data_in_prefetch_buffers(offset) or self._data_in_prefetch_requests(offset, size):
|
||||
continue
|
||||
|
||||
# break up anything larger than the max read size
|
||||
while size > 0:
|
||||
chunk_size = min(size, self.MAX_REQUEST_SIZE)
|
||||
read_chunks.append((offset, chunk_size))
|
||||
offset += chunk_size
|
||||
size -= chunk_size
|
||||
|
||||
self._start_prefetch(read_chunks)
|
||||
# now we can just devolve to a bunch of read()s :)
|
||||
for x in chunks:
|
||||
self.seek(x[0])
|
||||
yield self.read(x[1])
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _get_size(self):
|
||||
try:
|
||||
return self.stat().st_size
|
||||
except:
|
||||
return 0
|
||||
|
||||
def _start_prefetch(self, chunks):
|
||||
self._prefetching = True
|
||||
self._prefetch_done = False
|
||||
self._prefetch_reads.extend(chunks)
|
||||
|
||||
t = threading.Thread(target=self._prefetch_thread, args=(chunks,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def _prefetch_thread(self, chunks):
|
||||
# do these read requests in a temporary thread because there may be
|
||||
# a lot of them, so it may block.
|
||||
for offset, length in chunks:
|
||||
self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length))
|
||||
|
||||
def _async_response(self, t, msg):
|
||||
if t == CMD_STATUS:
|
||||
# save exception and re-raise it on next file operation
|
||||
try:
|
||||
self.sftp._convert_status(msg)
|
||||
except Exception, x:
|
||||
self._saved_exception = x
|
||||
return
|
||||
if t != CMD_DATA:
|
||||
raise SFTPError('Expected data')
|
||||
data = msg.get_string()
|
||||
offset, length = self._prefetch_reads.pop(0)
|
||||
self._prefetch_data[offset] = data
|
||||
if len(self._prefetch_reads) == 0:
|
||||
self._prefetch_done = True
|
||||
|
||||
def _check_exception(self):
|
||||
"if there's a saved exception, raise & clear it"
|
||||
if self._saved_exception is not None:
|
||||
x = self._saved_exception
|
||||
self._saved_exception = None
|
||||
raise x
|
||||
@ -1,199 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of an SFTP file handle (for server mode).
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
|
||||
|
||||
class SFTPHandle (object):
|
||||
"""
|
||||
Abstract object representing a handle to an open file (or folder) in an
|
||||
SFTP server implementation. Each handle has a string representation used
|
||||
by the client to refer to the underlying file.
|
||||
|
||||
Server implementations can (and should) subclass SFTPHandle to implement
|
||||
features of a file handle, like L{stat} or L{chattr}.
|
||||
"""
|
||||
def __init__(self, flags=0):
|
||||
"""
|
||||
Create a new file handle representing a local file being served over
|
||||
SFTP. If C{flags} is passed in, it's used to determine if the file
|
||||
is open in append mode.
|
||||
|
||||
@param flags: optional flags as passed to L{SFTPServerInterface.open}
|
||||
@type flags: int
|
||||
"""
|
||||
self.__flags = flags
|
||||
self.__name = None
|
||||
# only for handles to folders:
|
||||
self.__files = { }
|
||||
self.__tell = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
When a client closes a file, this method is called on the handle.
|
||||
Normally you would use this method to close the underlying OS level
|
||||
file object(s).
|
||||
|
||||
The default implementation checks for attributes on C{self} named
|
||||
C{readfile} and/or C{writefile}, and if either or both are present,
|
||||
their C{close()} methods are called. This means that if you are
|
||||
using the default implementations of L{read} and L{write}, this
|
||||
method's default implementation should be fine also.
|
||||
"""
|
||||
readfile = getattr(self, 'readfile', None)
|
||||
if readfile is not None:
|
||||
readfile.close()
|
||||
writefile = getattr(self, 'writefile', None)
|
||||
if writefile is not None:
|
||||
writefile.close()
|
||||
|
||||
def read(self, offset, length):
|
||||
"""
|
||||
Read up to C{length} bytes from this file, starting at position
|
||||
C{offset}. The offset may be a python long, since SFTP allows it
|
||||
to be 64 bits.
|
||||
|
||||
If the end of the file has been reached, this method may return an
|
||||
empty string to signify EOF, or it may also return L{SFTP_EOF}.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{readfile}, and if present, performs the read operation on the python
|
||||
file-like object found there. (This is meant as a time saver for the
|
||||
common case where you are wrapping a python file object.)
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param length: number of bytes to attempt to read.
|
||||
@type length: int
|
||||
@return: data read from the file, or an SFTP error code.
|
||||
@rtype: str
|
||||
"""
|
||||
readfile = getattr(self, 'readfile', None)
|
||||
if readfile is None:
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
if self.__tell is None:
|
||||
self.__tell = readfile.tell()
|
||||
if offset != self.__tell:
|
||||
readfile.seek(offset)
|
||||
self.__tell = offset
|
||||
data = readfile.read(length)
|
||||
except IOError, e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
self.__tell += len(data)
|
||||
return data
|
||||
|
||||
def write(self, offset, data):
|
||||
"""
|
||||
Write C{data} into this file at position C{offset}. Extending the
|
||||
file past its original end is expected. Unlike python's normal
|
||||
C{write()} methods, this method cannot do a partial write: it must
|
||||
write all of C{data} or else return an error.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{writefile}, and if present, performs the write operation on the
|
||||
python file-like object found there. The attribute is named
|
||||
differently from C{readfile} to make it easy to implement read-only
|
||||
(or write-only) files, but if both attributes are present, they should
|
||||
refer to the same file.
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param data: data to write into the file.
|
||||
@type data: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
"""
|
||||
writefile = getattr(self, 'writefile', None)
|
||||
if writefile is None:
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
# in append mode, don't care about seeking
|
||||
if (self.__flags & os.O_APPEND) == 0:
|
||||
if self.__tell is None:
|
||||
self.__tell = writefile.tell()
|
||||
if offset != self.__tell:
|
||||
writefile.seek(offset)
|
||||
self.__tell = offset
|
||||
writefile.write(data)
|
||||
writefile.flush()
|
||||
except IOError, e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
if self.__tell is not None:
|
||||
self.__tell += len(data)
|
||||
return SFTP_OK
|
||||
|
||||
def stat(self):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object referring to this open file, or an
|
||||
error code. This is equivalent to L{SFTPServerInterface.stat}, except
|
||||
it's called on an open file instead of a path.
|
||||
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, attr):
|
||||
"""
|
||||
Change the attributes of this file. The C{attr} object will contain
|
||||
only those fields provided by the client in its request, so you should
|
||||
check for the presence of fields before using them.
|
||||
|
||||
@param attr: the attributes to change on this file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _set_files(self, files):
|
||||
"""
|
||||
Used by the SFTP server code to cache a directory listing. (In
|
||||
the SFTP protocol, listing a directory is a multi-stage process
|
||||
requiring a temporary handle.)
|
||||
"""
|
||||
self.__files = files
|
||||
|
||||
def _get_next_files(self):
|
||||
"""
|
||||
Used by the SFTP server code to retreive a cached directory
|
||||
listing.
|
||||
"""
|
||||
fnlist = self.__files[:16]
|
||||
self.__files = self.__files[16:]
|
||||
return fnlist
|
||||
|
||||
def _get_name(self):
|
||||
return self.__name
|
||||
|
||||
def _set_name(self, name):
|
||||
self.__name = name
|
||||
|
||||
|
||||
from paramiko.sftp_server import SFTPServer
|
||||
@ -1,441 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Server-mode SFTP support.
|
||||
"""
|
||||
|
||||
import os
|
||||
import errno
|
||||
|
||||
from Crypto.Hash import MD5, SHA
|
||||
from paramiko.common import *
|
||||
from paramiko.server import SubsystemHandler
|
||||
from paramiko.sftp import *
|
||||
from paramiko.sftp_si import *
|
||||
from paramiko.sftp_attr import *
|
||||
|
||||
|
||||
# known hash algorithms for the "check-file" extension
|
||||
_hash_class = {
|
||||
'sha1': SHA,
|
||||
'md5': MD5,
|
||||
}
|
||||
|
||||
|
||||
class SFTPServer (BaseSFTP, SubsystemHandler):
|
||||
"""
|
||||
Server-side SFTP subsystem support. Since this is a L{SubsystemHandler},
|
||||
it can be (and is meant to be) set as the handler for C{"sftp"} requests.
|
||||
Use L{Transport.set_subsystem_handler} to activate this class.
|
||||
"""
|
||||
|
||||
def __init__(self, channel, name, server, sftp_si=SFTPServerInterface, *largs, **kwargs):
|
||||
"""
|
||||
The constructor for SFTPServer is meant to be called from within the
|
||||
L{Transport} as a subsystem handler. C{server} and any additional
|
||||
parameters or keyword parameters are passed from the original call to
|
||||
L{Transport.set_subsystem_handler}.
|
||||
|
||||
@param channel: channel passed from the L{Transport}.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param server: the server object associated with this channel and
|
||||
subsystem
|
||||
@type server: L{ServerInterface}
|
||||
@param sftp_si: a subclass of L{SFTPServerInterface} to use for handling
|
||||
individual requests.
|
||||
@type sftp_si: class
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
SubsystemHandler.__init__(self, channel, name, server)
|
||||
transport = channel.get_transport()
|
||||
self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
|
||||
self.ultra_debug = transport.get_hexdump()
|
||||
self.next_handle = 1
|
||||
# map of handle-string to SFTPHandle for files & folders:
|
||||
self.file_table = { }
|
||||
self.folder_table = { }
|
||||
self.server = sftp_si(server, *largs, **kwargs)
|
||||
|
||||
def _log(self, level, msg):
|
||||
if issubclass(type(msg), list):
|
||||
for m in msg:
|
||||
super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + m)
|
||||
else:
|
||||
super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + msg)
|
||||
|
||||
def start_subsystem(self, name, transport, channel):
|
||||
self.sock = channel
|
||||
self._log(DEBUG, 'Started sftp server on channel %s' % repr(channel))
|
||||
self._send_server_version()
|
||||
self.server.session_started()
|
||||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError:
|
||||
self._log(DEBUG, 'EOF -- end of session')
|
||||
return
|
||||
except Exception, e:
|
||||
self._log(DEBUG, 'Exception on channel: ' + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
return
|
||||
msg = Message(data)
|
||||
request_number = msg.get_int()
|
||||
try:
|
||||
self._process(t, request_number, msg)
|
||||
except Exception, e:
|
||||
self._log(DEBUG, 'Exception in server processing: ' + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
# send some kind of failure message, at least
|
||||
try:
|
||||
self._send_status(request_number, SFTP_FAILURE)
|
||||
except:
|
||||
pass
|
||||
|
||||
def finish_subsystem(self):
|
||||
self.server.session_ended()
|
||||
super(SFTPServer, self).finish_subsystem()
|
||||
# close any file handles that were left open (so we can return them to the OS quickly)
|
||||
for f in self.file_table.itervalues():
|
||||
f.close()
|
||||
for f in self.folder_table.itervalues():
|
||||
f.close()
|
||||
self.file_table = {}
|
||||
self.folder_table = {}
|
||||
|
||||
def convert_errno(e):
|
||||
"""
|
||||
Convert an errno value (as from an C{OSError} or C{IOError}) into a
|
||||
standard SFTP result code. This is a convenience function for trapping
|
||||
exceptions in server code and returning an appropriate result.
|
||||
|
||||
@param e: an errno code, as from C{OSError.errno}.
|
||||
@type e: int
|
||||
@return: an SFTP error code like L{SFTP_NO_SUCH_FILE}.
|
||||
@rtype: int
|
||||
"""
|
||||
if e == errno.EACCES:
|
||||
# permission denied
|
||||
return SFTP_PERMISSION_DENIED
|
||||
elif (e == errno.ENOENT) or (e == errno.ENOTDIR):
|
||||
# no such file
|
||||
return SFTP_NO_SUCH_FILE
|
||||
else:
|
||||
return SFTP_FAILURE
|
||||
convert_errno = staticmethod(convert_errno)
|
||||
|
||||
def set_file_attr(filename, attr):
|
||||
"""
|
||||
Change a file's attributes on the local filesystem. The contents of
|
||||
C{attr} are used to change the permissions, owner, group ownership,
|
||||
and/or modification & access time of the file, depending on which
|
||||
attributes are present in C{attr}.
|
||||
|
||||
This is meant to be a handy helper function for translating SFTP file
|
||||
requests into local file operations.
|
||||
|
||||
@param filename: name of the file to alter (should usually be an
|
||||
absolute path).
|
||||
@type filename: str
|
||||
@param attr: attributes to change.
|
||||
@type attr: L{SFTPAttributes}
|
||||
"""
|
||||
if sys.platform != 'win32':
|
||||
# mode operations are meaningless on win32
|
||||
if attr._flags & attr.FLAG_PERMISSIONS:
|
||||
os.chmod(filename, attr.st_mode)
|
||||
if attr._flags & attr.FLAG_UIDGID:
|
||||
os.chown(filename, attr.st_uid, attr.st_gid)
|
||||
if attr._flags & attr.FLAG_AMTIME:
|
||||
os.utime(filename, (attr.st_atime, attr.st_mtime))
|
||||
if attr._flags & attr.FLAG_SIZE:
|
||||
open(filename, 'w+').truncate(attr.st_size)
|
||||
set_file_attr = staticmethod(set_file_attr)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _response(self, request_number, t, *arg):
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
for item in arg:
|
||||
if type(item) is int:
|
||||
msg.add_int(item)
|
||||
elif type(item) is long:
|
||||
msg.add_int64(item)
|
||||
elif type(item) is str:
|
||||
msg.add_string(item)
|
||||
elif type(item) is SFTPAttributes:
|
||||
item._pack(msg)
|
||||
else:
|
||||
raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
|
||||
self._send_packet(t, str(msg))
|
||||
|
||||
def _send_handle_response(self, request_number, handle, folder=False):
|
||||
if not issubclass(type(handle), SFTPHandle):
|
||||
# must be error code
|
||||
self._send_status(request_number, handle)
|
||||
return
|
||||
handle._set_name('hx%d' % self.next_handle)
|
||||
self.next_handle += 1
|
||||
if folder:
|
||||
self.folder_table[handle._get_name()] = handle
|
||||
else:
|
||||
self.file_table[handle._get_name()] = handle
|
||||
self._response(request_number, CMD_HANDLE, handle._get_name())
|
||||
|
||||
def _send_status(self, request_number, code, desc=None):
|
||||
if desc is None:
|
||||
try:
|
||||
desc = SFTP_DESC[code]
|
||||
except IndexError:
|
||||
desc = 'Unknown'
|
||||
# some clients expect a "langauge" tag at the end (but don't mind it being blank)
|
||||
self._response(request_number, CMD_STATUS, code, desc, '')
|
||||
|
||||
def _open_folder(self, request_number, path):
|
||||
resp = self.server.list_folder(path)
|
||||
if issubclass(type(resp), list):
|
||||
# got an actual list of filenames in the folder
|
||||
folder = SFTPHandle()
|
||||
folder._set_files(resp)
|
||||
self._send_handle_response(request_number, folder, True)
|
||||
return
|
||||
# must be an error code
|
||||
self._send_status(request_number, resp)
|
||||
|
||||
def _read_folder(self, request_number, folder):
|
||||
flist = folder._get_next_files()
|
||||
if len(flist) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
return
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
msg.add_int(len(flist))
|
||||
for attr in flist:
|
||||
msg.add_string(attr.filename)
|
||||
msg.add_string(str(attr))
|
||||
attr._pack(msg)
|
||||
self._send_packet(CMD_NAME, str(msg))
|
||||
|
||||
def _check_file(self, request_number, msg):
|
||||
# this extension actually comes from v6 protocol, but since it's an
|
||||
# extension, i feel like we can reasonably support it backported.
|
||||
# it's very useful for verifying uploaded files or checking for
|
||||
# rsync-like differences between local and remote files.
|
||||
handle = msg.get_string()
|
||||
alg_list = msg.get_list()
|
||||
start = msg.get_int64()
|
||||
length = msg.get_int64()
|
||||
block_size = msg.get_int()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
f = self.file_table[handle]
|
||||
for x in alg_list:
|
||||
if x in _hash_class:
|
||||
algname = x
|
||||
alg = _hash_class[x]
|
||||
break
|
||||
else:
|
||||
self._send_status(request_number, SFTP_FAILURE, 'No supported hash types found')
|
||||
return
|
||||
if length == 0:
|
||||
st = f.stat()
|
||||
if not issubclass(type(st), SFTPAttributes):
|
||||
self._send_status(request_number, st, 'Unable to stat file')
|
||||
return
|
||||
length = st.st_size - start
|
||||
if block_size == 0:
|
||||
block_size = length
|
||||
if block_size < 256:
|
||||
self._send_status(request_number, SFTP_FAILURE, 'Block size too small')
|
||||
return
|
||||
|
||||
sum_out = ''
|
||||
offset = start
|
||||
while offset < start + length:
|
||||
blocklen = min(block_size, start + length - offset)
|
||||
# don't try to read more than about 64KB at a time
|
||||
chunklen = min(blocklen, 65536)
|
||||
count = 0
|
||||
hash_obj = alg.new()
|
||||
while count < blocklen:
|
||||
data = f.read(offset, chunklen)
|
||||
if not type(data) is str:
|
||||
self._send_status(request_number, data, 'Unable to hash file')
|
||||
return
|
||||
hash_obj.update(data)
|
||||
count += len(data)
|
||||
offset += count
|
||||
sum_out += hash_obj.digest()
|
||||
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
msg.add_string('check-file')
|
||||
msg.add_string(algname)
|
||||
msg.add_bytes(sum_out)
|
||||
self._send_packet(CMD_EXTENDED_REPLY, str(msg))
|
||||
|
||||
def _convert_pflags(self, pflags):
|
||||
"convert SFTP-style open() flags to python's os.open() flags"
|
||||
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
|
||||
flags = os.O_RDWR
|
||||
elif pflags & SFTP_FLAG_WRITE:
|
||||
flags = os.O_WRONLY
|
||||
else:
|
||||
flags = os.O_RDONLY
|
||||
if pflags & SFTP_FLAG_APPEND:
|
||||
flags |= os.O_APPEND
|
||||
if pflags & SFTP_FLAG_CREATE:
|
||||
flags |= os.O_CREAT
|
||||
if pflags & SFTP_FLAG_TRUNC:
|
||||
flags |= os.O_TRUNC
|
||||
if pflags & SFTP_FLAG_EXCL:
|
||||
flags |= os.O_EXCL
|
||||
return flags
|
||||
|
||||
def _process(self, t, request_number, msg):
|
||||
self._log(DEBUG, 'Request: %s' % CMD_NAMES[t])
|
||||
if t == CMD_OPEN:
|
||||
path = msg.get_string()
|
||||
flags = self._convert_pflags(msg.get_int())
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_handle_response(request_number, self.server.open(path, flags, attr))
|
||||
elif t == CMD_CLOSE:
|
||||
handle = msg.get_string()
|
||||
if handle in self.folder_table:
|
||||
del self.folder_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
if handle in self.file_table:
|
||||
self.file_table[handle].close()
|
||||
del self.file_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
elif t == CMD_READ:
|
||||
handle = msg.get_string()
|
||||
offset = msg.get_int64()
|
||||
length = msg.get_int()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
data = self.file_table[handle].read(offset, length)
|
||||
if type(data) is str:
|
||||
if len(data) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
else:
|
||||
self._response(request_number, CMD_DATA, data)
|
||||
else:
|
||||
self._send_status(request_number, data)
|
||||
elif t == CMD_WRITE:
|
||||
handle = msg.get_string()
|
||||
offset = msg.get_int64()
|
||||
data = msg.get_string()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].write(offset, data))
|
||||
elif t == CMD_REMOVE:
|
||||
path = msg.get_string()
|
||||
self._send_status(request_number, self.server.remove(path))
|
||||
elif t == CMD_RENAME:
|
||||
oldpath = msg.get_string()
|
||||
newpath = msg.get_string()
|
||||
self._send_status(request_number, self.server.rename(oldpath, newpath))
|
||||
elif t == CMD_MKDIR:
|
||||
path = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.mkdir(path, attr))
|
||||
elif t == CMD_RMDIR:
|
||||
path = msg.get_string()
|
||||
self._send_status(request_number, self.server.rmdir(path))
|
||||
elif t == CMD_OPENDIR:
|
||||
path = msg.get_string()
|
||||
self._open_folder(request_number, path)
|
||||
return
|
||||
elif t == CMD_READDIR:
|
||||
handle = msg.get_string()
|
||||
if handle not in self.folder_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
folder = self.folder_table[handle]
|
||||
self._read_folder(request_number, folder)
|
||||
elif t == CMD_STAT:
|
||||
path = msg.get_string()
|
||||
resp = self.server.stat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_LSTAT:
|
||||
path = msg.get_string()
|
||||
resp = self.server.lstat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_FSTAT:
|
||||
handle = msg.get_string()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
resp = self.file_table[handle].stat()
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SETSTAT:
|
||||
path = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.chattr(path, attr))
|
||||
elif t == CMD_FSETSTAT:
|
||||
handle = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
if handle not in self.file_table:
|
||||
self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].chattr(attr))
|
||||
elif t == CMD_READLINK:
|
||||
path = msg.get_string()
|
||||
resp = self.server.readlink(path)
|
||||
if type(resp) is str:
|
||||
self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes())
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SYMLINK:
|
||||
# the sftp 2 draft is incorrect here! path always follows target_path
|
||||
target_path = msg.get_string()
|
||||
path = msg.get_string()
|
||||
self._send_status(request_number, self.server.symlink(target_path, path))
|
||||
elif t == CMD_REALPATH:
|
||||
path = msg.get_string()
|
||||
rpath = self.server.canonicalize(path)
|
||||
self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes())
|
||||
elif t == CMD_EXTENDED:
|
||||
tag = msg.get_string()
|
||||
if tag == 'check-file':
|
||||
self._check_file(request_number, msg)
|
||||
else:
|
||||
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
|
||||
else:
|
||||
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
|
||||
|
||||
|
||||
from paramiko.sftp_handle import SFTPHandle
|
||||
@ -1,307 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SFTPServerInterface} is an interface to override for SFTP server support.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
|
||||
|
||||
class SFTPServerInterface (object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
when using the L{SFTPServer} subsystem to provide an SFTP server.
|
||||
|
||||
Methods on this class are called from the SFTP session's thread, so you can
|
||||
block as long as necessary without affecting other sessions (even other
|
||||
SFTP sessions). However, raising an exception will usually cause the SFTP
|
||||
session to abruptly end, so you will usually want to catch exceptions and
|
||||
return an appropriate error code.
|
||||
|
||||
All paths are in string form instead of unicode because not all SFTP
|
||||
clients & servers obey the requirement that paths be encoded in UTF-8.
|
||||
"""
|
||||
|
||||
def __init__ (self, server, *largs, **kwargs):
|
||||
"""
|
||||
Create a new SFTPServerInterface object. This method does nothing by
|
||||
default and is meant to be overridden by subclasses.
|
||||
|
||||
@param server: the server object associated with this channel and
|
||||
SFTP subsystem
|
||||
@type server: L{ServerInterface}
|
||||
"""
|
||||
super(SFTPServerInterface, self).__init__(*largs, **kwargs)
|
||||
|
||||
def session_started(self):
|
||||
"""
|
||||
The SFTP server session has just started. This method is meant to be
|
||||
overridden to perform any necessary setup before handling callbacks
|
||||
from SFTP operations.
|
||||
"""
|
||||
pass
|
||||
|
||||
def session_ended(self):
|
||||
"""
|
||||
The SFTP server session has just ended, either cleanly or via an
|
||||
exception. This method is meant to be overridden to perform any
|
||||
necessary cleanup before this C{SFTPServerInterface} object is
|
||||
destroyed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def open(self, path, flags, attr):
|
||||
"""
|
||||
Open a file on the server and create a handle for future operations
|
||||
on that file. On success, a new object subclassed from L{SFTPHandle}
|
||||
should be returned. This handle will be used for future operations
|
||||
on the file (read, write, etc). On failure, an error code such as
|
||||
L{SFTP_PERMISSION_DENIED} should be returned.
|
||||
|
||||
C{flags} contains the requested mode for opening (read-only,
|
||||
write-append, etc) as a bitset of flags from the C{os} module:
|
||||
- C{os.O_RDONLY}
|
||||
- C{os.O_WRONLY}
|
||||
- C{os.O_RDWR}
|
||||
- C{os.O_APPEND}
|
||||
- C{os.O_CREAT}
|
||||
- C{os.O_TRUNC}
|
||||
- C{os.O_EXCL}
|
||||
(One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always
|
||||
be set.)
|
||||
|
||||
The C{attr} object contains requested attributes of the file if it
|
||||
has to be created. Some or all attribute fields may be missing if
|
||||
the client didn't specify them.
|
||||
|
||||
@note: The SFTP protocol defines all files to be in "binary" mode.
|
||||
There is no equivalent to python's "text" mode.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to be opened.
|
||||
@type path: str
|
||||
@param flags: flags or'd together from the C{os} module indicating the
|
||||
requested mode for opening the file.
|
||||
@type flags: int
|
||||
@param attr: requested attributes of the file if it is newly created.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: a new L{SFTPHandle} I{or error code}.
|
||||
@rtype L{SFTPHandle}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def list_folder(self, path):
|
||||
"""
|
||||
Return a list of files within a given folder. The C{path} will use
|
||||
posix notation (C{"/"} separates folder names) and may be an absolute
|
||||
or relative path.
|
||||
|
||||
The list of files is expected to be a list of L{SFTPAttributes}
|
||||
objects, which are similar in structure to the objects returned by
|
||||
C{os.stat}. In addition, each object should have its C{filename}
|
||||
field filled in, since this is important to a directory listing and
|
||||
not normally present in C{os.stat} results. The method
|
||||
L{SFTPAttributes.from_stat} will usually do what you want.
|
||||
|
||||
In case of an error, you should return one of the C{SFTP_*} error
|
||||
codes, such as L{SFTP_PERMISSION_DENIED}.
|
||||
|
||||
@param path: the requested path (relative or absolute) to be listed.
|
||||
@type path: str
|
||||
@return: a list of the files in the given folder, using
|
||||
L{SFTPAttributes} objects.
|
||||
@rtype: list of L{SFTPAttributes} I{or error code}
|
||||
|
||||
@note: You should normalize the given C{path} first (see the
|
||||
C{os.path} module) and check appropriate permissions before returning
|
||||
the list of files. Be careful of malicious clients attempting to use
|
||||
relative paths to escape restricted folders, if you're doing a direct
|
||||
translation from the SFTP server path to your local filesystem.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def stat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should follow them. (L{lstat} is the corresponding
|
||||
call that doesn't follow symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def lstat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should I{not} follow them -- instead, you should
|
||||
return data on the symlink or alias itself. (L{stat} is the
|
||||
corresponding call that follows symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def remove(self, path):
|
||||
"""
|
||||
Delete a file, if possible.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to delete.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename (or move) a file. The SFTP specification implies that this
|
||||
method can be used to move an existing file into a different folder,
|
||||
and since there's no other (easy) way to move files via SFTP, it's
|
||||
probably a good idea to implement "move" in this method too, even for
|
||||
files that cross disk partition boundaries, if at all possible.
|
||||
|
||||
@note: You should return an error if a file with the same name as
|
||||
C{newpath} already exists. (The rename operation should be
|
||||
non-desctructive.)
|
||||
|
||||
@param oldpath: the requested path (relative or absolute) of the
|
||||
existing file.
|
||||
@type oldpath: str
|
||||
@param newpath: the requested new path of the file.
|
||||
@type newpath: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def mkdir(self, path, attr):
|
||||
"""
|
||||
Create a new directory with the given attributes. The C{attr}
|
||||
object may be considered a "hint" and ignored.
|
||||
|
||||
The C{attr} object will contain only those fields provided by the
|
||||
client in its request, so you should use C{hasattr} to check for
|
||||
the presense of fields before using them. In some cases, the C{attr}
|
||||
object may be completely empty.
|
||||
|
||||
@param path: requested path (relative or absolute) of the new
|
||||
folder.
|
||||
@type path: str
|
||||
@param attr: requested attributes of the new folder.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove a directory if it exists. The C{path} should refer to an
|
||||
existing, empty folder -- otherwise this method should return an
|
||||
error.
|
||||
|
||||
@param path: requested path (relative or absolute) of the folder
|
||||
to remove.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, path, attr):
|
||||
"""
|
||||
Change the attributes of a file. The C{attr} object will contain
|
||||
only those fields provided by the client in its request, so you
|
||||
should check for the presence of fields before using them.
|
||||
|
||||
@param path: requested path (relative or absolute) of the file to
|
||||
change.
|
||||
@type path: str
|
||||
@param attr: requested attributes to change on the file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def canonicalize(self, path):
|
||||
"""
|
||||
Return the canonical form of a path on the server. For example,
|
||||
if the server's home folder is C{/home/foo}, the path
|
||||
C{"../betty"} would be canonicalized to C{"/home/betty"}. Note
|
||||
the obvious security issues: if you're serving files only from a
|
||||
specific folder, you probably don't want this method to reveal path
|
||||
names outside that folder.
|
||||
|
||||
You may find the python methods in C{os.path} useful, especially
|
||||
C{os.path.normpath} and C{os.path.realpath}.
|
||||
|
||||
The default implementation returns C{os.path.normpath('/' + path)}.
|
||||
"""
|
||||
if os.path.isabs(path):
|
||||
out = os.path.normpath(path)
|
||||
else:
|
||||
out = os.path.normpath('/' + path)
|
||||
if sys.platform == 'win32':
|
||||
# on windows, normalize backslashes to sftp/posix format
|
||||
out = out.replace('\\', '/')
|
||||
return out
|
||||
|
||||
def readlink(self, path):
|
||||
"""
|
||||
Return the target of a symbolic link (or shortcut) on the server.
|
||||
If the specified path doesn't refer to a symbolic link, an error
|
||||
should be returned.
|
||||
|
||||
@param path: path (relative or absolute) of the symbolic link.
|
||||
@type path: str
|
||||
@return: the target path of the symbolic link, or an error code like
|
||||
L{SFTP_NO_SUCH_FILE}.
|
||||
@rtype: str I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def symlink(self, target_path, path):
|
||||
"""
|
||||
Create a symbolic link on the server, as new pathname C{path},
|
||||
with C{target_path} as the target of the link.
|
||||
|
||||
@param target_path: path (relative or absolute) of the target for
|
||||
this new symbolic link.
|
||||
@type target_path: str
|
||||
@param path: path (relative or absolute) of the symbolic link to
|
||||
create.
|
||||
@type path: str
|
||||
@return: an error code like C{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
@ -1,112 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Exceptions defined by paramiko.
|
||||
"""
|
||||
|
||||
|
||||
class SSHException (Exception):
|
||||
"""
|
||||
Exception raised by failures in SSH2 protocol negotiation or logic errors.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticationException (SSHException):
|
||||
"""
|
||||
Exception raised when authentication failed for some reason. It may be
|
||||
possible to retry with different credentials. (Other classes specify more
|
||||
specific reasons.)
|
||||
|
||||
@since: 1.6
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PasswordRequiredException (AuthenticationException):
|
||||
"""
|
||||
Exception raised when a password is needed to unlock a private key file.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BadAuthenticationType (AuthenticationException):
|
||||
"""
|
||||
Exception raised when an authentication type (like password) is used, but
|
||||
the server isn't allowing that type. (It may only allow public-key, for
|
||||
example.)
|
||||
|
||||
@ivar allowed_types: list of allowed authentication types provided by the
|
||||
server (possible values are: C{"none"}, C{"password"}, and
|
||||
C{"publickey"}).
|
||||
@type allowed_types: list
|
||||
|
||||
@since: 1.1
|
||||
"""
|
||||
allowed_types = []
|
||||
|
||||
def __init__(self, explanation, types):
|
||||
AuthenticationException.__init__(self, explanation)
|
||||
self.allowed_types = types
|
||||
|
||||
def __str__(self):
|
||||
return SSHException.__str__(self) + ' (allowed_types=%r)' % self.allowed_types
|
||||
|
||||
|
||||
class PartialAuthentication (AuthenticationException):
|
||||
"""
|
||||
An internal exception thrown in the case of partial authentication.
|
||||
"""
|
||||
allowed_types = []
|
||||
|
||||
def __init__(self, types):
|
||||
AuthenticationException.__init__(self, 'partial authentication')
|
||||
self.allowed_types = types
|
||||
|
||||
|
||||
class ChannelException (SSHException):
|
||||
"""
|
||||
Exception raised when an attempt to open a new L{Channel} fails.
|
||||
|
||||
@ivar code: the error code returned by the server
|
||||
@type code: int
|
||||
|
||||
@since: 1.6
|
||||
"""
|
||||
def __init__(self, code, text):
|
||||
SSHException.__init__(self, text)
|
||||
self.code = code
|
||||
|
||||
|
||||
class BadHostKeyException (SSHException):
|
||||
"""
|
||||
The host key given by the SSH server did not match what we were expecting.
|
||||
|
||||
@ivar hostname: the hostname of the SSH server
|
||||
@type hostname: str
|
||||
@ivar key: the host key presented by the server
|
||||
@type key: L{PKey}
|
||||
@ivar expected_key: the host key expected
|
||||
@type expected_key: L{PKey}
|
||||
|
||||
@since: 1.6
|
||||
"""
|
||||
def __init__(self, hostname, got_key, expected_key):
|
||||
SSHException.__init__(self, 'Host key for server %s does not match!' % hostname)
|
||||
self.hostname = hostname
|
||||
self.key = got_key
|
||||
self.expected_key = expected_key
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,299 +0,0 @@
|
||||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Useful functions used by the rest of paramiko.
|
||||
"""
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
import array
|
||||
from binascii import hexlify, unhexlify
|
||||
import sys
|
||||
import struct
|
||||
import traceback
|
||||
import threading
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.config import SSHConfig
|
||||
|
||||
|
||||
# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it
|
||||
if sys.version_info < (2,3):
|
||||
class enumerate:
|
||||
def __init__ (self, sequence):
|
||||
self.sequence = sequence
|
||||
def __iter__ (self):
|
||||
count = 0
|
||||
for item in self.sequence:
|
||||
yield (count, item)
|
||||
count += 1
|
||||
|
||||
|
||||
def inflate_long(s, always_positive=False):
|
||||
"turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"
|
||||
out = 0L
|
||||
negative = 0
|
||||
if not always_positive and (len(s) > 0) and (ord(s[0]) >= 0x80):
|
||||
negative = 1
|
||||
if len(s) % 4:
|
||||
filler = '\x00'
|
||||
if negative:
|
||||
filler = '\xff'
|
||||
s = filler * (4 - len(s) % 4) + s
|
||||
for i in range(0, len(s), 4):
|
||||
out = (out << 32) + struct.unpack('>I', s[i:i+4])[0]
|
||||
if negative:
|
||||
out -= (1L << (8 * len(s)))
|
||||
return out
|
||||
|
||||
def deflate_long(n, add_sign_padding=True):
|
||||
"turns a long-int into a normalized byte string (adapted from Crypto.Util.number)"
|
||||
# after much testing, this algorithm was deemed to be the fastest
|
||||
s = ''
|
||||
n = long(n)
|
||||
while (n != 0) and (n != -1):
|
||||
s = struct.pack('>I', n & 0xffffffffL) + s
|
||||
n = n >> 32
|
||||
# strip off leading zeros, FFs
|
||||
for i in enumerate(s):
|
||||
if (n == 0) and (i[1] != '\000'):
|
||||
break
|
||||
if (n == -1) and (i[1] != '\xff'):
|
||||
break
|
||||
else:
|
||||
# degenerate case, n was either 0 or -1
|
||||
i = (0,)
|
||||
if n == 0:
|
||||
s = '\000'
|
||||
else:
|
||||
s = '\xff'
|
||||
s = s[i[0]:]
|
||||
if add_sign_padding:
|
||||
if (n == 0) and (ord(s[0]) >= 0x80):
|
||||
s = '\x00' + s
|
||||
if (n == -1) and (ord(s[0]) < 0x80):
|
||||
s = '\xff' + s
|
||||
return s
|
||||
|
||||
def format_binary_weird(data):
|
||||
out = ''
|
||||
for i in enumerate(data):
|
||||
out += '%02X' % ord(i[1])
|
||||
if i[0] % 2:
|
||||
out += ' '
|
||||
if i[0] % 16 == 15:
|
||||
out += '\n'
|
||||
return out
|
||||
|
||||
def format_binary(data, prefix=''):
|
||||
x = 0
|
||||
out = []
|
||||
while len(data) > x + 16:
|
||||
out.append(format_binary_line(data[x:x+16]))
|
||||
x += 16
|
||||
if x < len(data):
|
||||
out.append(format_binary_line(data[x:]))
|
||||
return [prefix + x for x in out]
|
||||
|
||||
def format_binary_line(data):
|
||||
left = ' '.join(['%02X' % ord(c) for c in data])
|
||||
right = ''.join([('.%c..' % c)[(ord(c)+63)//95] for c in data])
|
||||
return '%-50s %s' % (left, right)
|
||||
|
||||
def hexify(s):
|
||||
return hexlify(s).upper()
|
||||
|
||||
def unhexify(s):
|
||||
return unhexlify(s)
|
||||
|
||||
def safe_string(s):
|
||||
out = ''
|
||||
for c in s:
|
||||
if (ord(c) >= 32) and (ord(c) <= 127):
|
||||
out += c
|
||||
else:
|
||||
out += '%%%02X' % ord(c)
|
||||
return out
|
||||
|
||||
# ''.join([['%%%02X' % ord(c), c][(ord(c) >= 32) and (ord(c) <= 127)] for c in s])
|
||||
|
||||
def bit_length(n):
|
||||
norm = deflate_long(n, 0)
|
||||
hbyte = ord(norm[0])
|
||||
if hbyte == 0:
|
||||
return 1
|
||||
bitlen = len(norm) * 8
|
||||
while not (hbyte & 0x80):
|
||||
hbyte <<= 1
|
||||
bitlen -= 1
|
||||
return bitlen
|
||||
|
||||
def tb_strings():
|
||||
return ''.join(traceback.format_exception(*sys.exc_info())).split('\n')
|
||||
|
||||
def generate_key_bytes(hashclass, salt, key, nbytes):
|
||||
"""
|
||||
Given a password, passphrase, or other human-source key, scramble it
|
||||
through a secure hash into some keyworthy bytes. This specific algorithm
|
||||
is used for encrypting/decrypting private key files.
|
||||
|
||||
@param hashclass: class from L{Crypto.Hash} that can be used as a secure
|
||||
hashing function (like C{MD5} or C{SHA}).
|
||||
@type hashclass: L{Crypto.Hash}
|
||||
@param salt: data to salt the hash with.
|
||||
@type salt: string
|
||||
@param key: human-entered password or passphrase.
|
||||
@type key: string
|
||||
@param nbytes: number of bytes to generate.
|
||||
@type nbytes: int
|
||||
@return: key data
|
||||
@rtype: string
|
||||
"""
|
||||
keydata = ''
|
||||
digest = ''
|
||||
if len(salt) > 8:
|
||||
salt = salt[:8]
|
||||
while nbytes > 0:
|
||||
hash_obj = hashclass.new()
|
||||
if len(digest) > 0:
|
||||
hash_obj.update(digest)
|
||||
hash_obj.update(key)
|
||||
hash_obj.update(salt)
|
||||
digest = hash_obj.digest()
|
||||
size = min(nbytes, len(digest))
|
||||
keydata += digest[:size]
|
||||
nbytes -= size
|
||||
return keydata
|
||||
|
||||
def load_host_keys(filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by openssh, and
|
||||
return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}.
|
||||
The hostname may be an IP address or DNS name. The keytype will be either
|
||||
C{"ssh-rsa"} or C{"ssh-dss"}.
|
||||
|
||||
This type of file unfortunately doesn't exist on Windows, but on posix,
|
||||
it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}.
|
||||
|
||||
Since 1.5.3, this is just a wrapper around L{HostKeys}.
|
||||
|
||||
@param filename: name of the file to read host keys from
|
||||
@type filename: str
|
||||
@return: dict of host keys, indexed by hostname and then keytype
|
||||
@rtype: dict(hostname, dict(keytype, L{PKey <paramiko.pkey.PKey>}))
|
||||
"""
|
||||
from paramiko.hostkeys import HostKeys
|
||||
return HostKeys(filename)
|
||||
|
||||
def parse_ssh_config(file_obj):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around L{SSHConfig}.
|
||||
"""
|
||||
config = SSHConfig()
|
||||
config.parse(file_obj)
|
||||
return config
|
||||
|
||||
def lookup_ssh_host_config(hostname, config):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around L{SSHConfig}.
|
||||
"""
|
||||
return config.lookup(hostname)
|
||||
|
||||
def mod_inverse(x, m):
|
||||
# it's crazy how small python can make this function.
|
||||
u1, u2, u3 = 1, 0, m
|
||||
v1, v2, v3 = 0, 1, x
|
||||
|
||||
while v3 > 0:
|
||||
q = u3 // v3
|
||||
u1, v1 = v1, u1 - v1 * q
|
||||
u2, v2 = v2, u2 - v2 * q
|
||||
u3, v3 = v3, u3 - v3 * q
|
||||
if u2 < 0:
|
||||
u2 += m
|
||||
return u2
|
||||
|
||||
_g_thread_ids = {}
|
||||
_g_thread_counter = 0
|
||||
_g_thread_lock = threading.Lock()
|
||||
def get_thread_id():
|
||||
global _g_thread_ids, _g_thread_counter, _g_thread_lock
|
||||
tid = id(threading.currentThread())
|
||||
try:
|
||||
return _g_thread_ids[tid]
|
||||
except KeyError:
|
||||
_g_thread_lock.acquire()
|
||||
try:
|
||||
_g_thread_counter += 1
|
||||
ret = _g_thread_ids[tid] = _g_thread_counter
|
||||
finally:
|
||||
_g_thread_lock.release()
|
||||
return ret
|
||||
|
||||
def log_to_file(filename, level=DEBUG):
|
||||
"send paramiko logs to a logfile, if they're not already going somewhere"
|
||||
l = logging.getLogger("paramiko")
|
||||
if len(l.handlers) > 0:
|
||||
return
|
||||
l.setLevel(level)
|
||||
f = open(filename, 'w')
|
||||
lh = logging.StreamHandler(f)
|
||||
lh.setFormatter(logging.Formatter('%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d %(name)s: %(message)s',
|
||||
'%Y%m%d-%H:%M:%S'))
|
||||
l.addHandler(lh)
|
||||
|
||||
# make only one filter object, so it doesn't get applied more than once
|
||||
class PFilter (object):
|
||||
def filter(self, record):
|
||||
record._threadid = get_thread_id()
|
||||
return True
|
||||
_pfilter = PFilter()
|
||||
|
||||
def get_logger(name):
|
||||
l = logging.getLogger(name)
|
||||
l.addFilter(_pfilter)
|
||||
return l
|
||||
|
||||
|
||||
class Counter (object):
|
||||
"""Stateful counter for CTR mode crypto"""
|
||||
def __init__(self, nbits, initial_value=1L, overflow=0L):
|
||||
self.blocksize = nbits / 8
|
||||
self.overflow = overflow
|
||||
# start with value - 1 so we don't have to store intermediate values when counting
|
||||
# could the iv be 0?
|
||||
if initial_value == 0:
|
||||
self.value = array.array('c', '\xFF' * self.blocksize)
|
||||
else:
|
||||
x = deflate_long(initial_value - 1, add_sign_padding=False)
|
||||
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||
|
||||
def __call__(self):
|
||||
"""Increament the counter and return the new value"""
|
||||
i = self.blocksize - 1
|
||||
while i > -1:
|
||||
c = self.value[i] = chr((ord(self.value[i]) + 1) % 256)
|
||||
if c != '\x00':
|
||||
return self.value.tostring()
|
||||
i -= 1
|
||||
# counter reset
|
||||
x = deflate_long(self.overflow, add_sign_padding=False)
|
||||
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||
return self.value.tostring()
|
||||
|
||||
def new(cls, nbits, initial_value=1L, overflow=0L):
|
||||
return cls(nbits, initial_value=initial_value, overflow=overflow)
|
||||
new = classmethod(new)
|
||||
@ -1,143 +0,0 @@
|
||||
# Copyright (C) 2005 John Arbash-Meinel <john@arbash-meinel.com>
|
||||
# Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
# Apache License, Version 2.0 (the "License"); you may not use this
|
||||
# file except in compliance with the License. Citrix Systems, Inc.
|
||||
# reserves all rights not expressly granted by the License.
|
||||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Automatically generated by addcopyright.py at 04/03/2012
|
||||
# Modified up by: Todd Whiteman <ToddW@ActiveState.com>
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Functions for communicating with Pageant, the basic windows ssh agent program.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import mmap
|
||||
import array
|
||||
|
||||
# ctypes is part of standard library since Python 2.5
|
||||
_has_win32all = False
|
||||
_has_ctypes = False
|
||||
try:
|
||||
# win32gui is preferred over win32ui to avoid MFC dependencies
|
||||
import win32gui
|
||||
_has_win32all = True
|
||||
except ImportError:
|
||||
try:
|
||||
import ctypes
|
||||
_has_ctypes = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
_AGENT_COPYDATA_ID = 0x804e50ba
|
||||
_AGENT_MAX_MSGLEN = 8192
|
||||
# Note: The WM_COPYDATA value is pulled from win32con, as a workaround
|
||||
win32con_WM_COPYDATA = 74
|
||||
|
||||
|
||||
def _get_pageant_window_object():
|
||||
if _has_win32all:
|
||||
try:
|
||||
hwnd = win32gui.FindWindow('Pageant', 'Pageant')
|
||||
return hwnd
|
||||
except win32gui.error:
|
||||
pass
|
||||
elif _has_ctypes:
|
||||
# Return 0 if there is no Pageant window.
|
||||
return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant')
|
||||
return None
|
||||
|
||||
|
||||
def can_talk_to_agent():
|
||||
"""
|
||||
Check to see if there is a "Pageant" agent we can talk to.
|
||||
|
||||
This checks both if we have the required libraries (win32all or ctypes)
|
||||
and if there is a Pageant currently running.
|
||||
"""
|
||||
if (_has_win32all or _has_ctypes) and _get_pageant_window_object():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _query_pageant(msg):
|
||||
hwnd = _get_pageant_window_object()
|
||||
if not hwnd:
|
||||
# Raise a failure to connect exception, pageant isn't running anymore!
|
||||
return None
|
||||
|
||||
# Write our pageant request string into the file (pageant will read this to determine what to do)
|
||||
filename = tempfile.mktemp('.pag')
|
||||
map_filename = os.path.basename(filename)
|
||||
|
||||
f = open(filename, 'w+b')
|
||||
f.write(msg )
|
||||
# Ensure the rest of the file is empty, otherwise pageant will read this
|
||||
f.write('\0' * (_AGENT_MAX_MSGLEN - len(msg)))
|
||||
# Create the shared file map that pageant will use to read from
|
||||
pymap = mmap.mmap(f.fileno(), _AGENT_MAX_MSGLEN, tagname=map_filename, access=mmap.ACCESS_WRITE)
|
||||
try:
|
||||
# Create an array buffer containing the mapped filename
|
||||
char_buffer = array.array("c", map_filename + '\0')
|
||||
char_buffer_address, char_buffer_size = char_buffer.buffer_info()
|
||||
# Create a string to use for the SendMessage function call
|
||||
cds = struct.pack("LLP", _AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address)
|
||||
|
||||
if _has_win32all:
|
||||
# win32gui.SendMessage should also allow the same pattern as
|
||||
# ctypes, but let's keep it like this for now...
|
||||
response = win32gui.SendMessage(hwnd, win32con_WM_COPYDATA, len(cds), cds)
|
||||
elif _has_ctypes:
|
||||
_buf = array.array('B', cds)
|
||||
_addr, _size = _buf.buffer_info()
|
||||
response = ctypes.windll.user32.SendMessageA(hwnd, win32con_WM_COPYDATA, _size, _addr)
|
||||
else:
|
||||
response = 0
|
||||
|
||||
if response > 0:
|
||||
datalen = pymap.read(4)
|
||||
retlen = struct.unpack('>I', datalen)[0]
|
||||
return datalen + pymap.read(retlen)
|
||||
return None
|
||||
finally:
|
||||
pymap.close()
|
||||
f.close()
|
||||
# Remove the file, it was temporary only
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
class PageantConnection (object):
|
||||
"""
|
||||
Mock "connection" to an agent which roughly approximates the behavior of
|
||||
a unix local-domain socket (as used by Agent). Requests are sent to the
|
||||
pageant daemon via special Windows magick, and responses are buffered back
|
||||
for subsequent reads.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._response = None
|
||||
|
||||
def send(self, data):
|
||||
self._response = _query_pageant(data)
|
||||
|
||||
def recv(self, n):
|
||||
if self._response is None:
|
||||
return ''
|
||||
ret = self._response[:n]
|
||||
self._response = self._response[n:]
|
||||
if self._response == '':
|
||||
self._response = None
|
||||
return ret
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
@ -7485,7 +7485,7 @@ div.panel.ui-dialog div.list-view div.fixed-header {
|
||||
color: #FFFFFF;
|
||||
background: url(../images/buttons.png) no-repeat -457px -503px;
|
||||
font-size: 11px;
|
||||
padding: 6px 24px 6px 9px;
|
||||
padding: 6px 17px 6px 9px;
|
||||
/*+text-shadow:0px 1px 1px #395065;*/
|
||||
-moz-text-shadow: 0px 1px 1px #395065;
|
||||
-webkit-text-shadow: 0px 1px 1px #395065;
|
||||
|
||||
@ -804,6 +804,9 @@
|
||||
success: function(json) {
|
||||
var item = json.updateuserresponse.user;
|
||||
args.response.success({data:item});
|
||||
},
|
||||
error: function(data) {
|
||||
args.response.error(parseXMLHttpResponse(data));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user