зеркало из https://github.com/github/putty.git
Move some ssh.c declarations into header files.
ssh.c has been an unmanageably huge monolith of a source file for too long, and it's finally time I started breaking it up into smaller pieces. The first step is to move some declarations - basic types like packets and packet queues, standard constants, enums, and the coroutine system - into headers where other files can see them.
This commit is contained in:
Родитель
8b98fea4ae
Коммит
ba7571291a
180
ssh.c
180
ssh.c
|
@ -15,6 +15,7 @@
|
|||
#include "storage.h"
|
||||
#include "marshal.h"
|
||||
#include "ssh.h"
|
||||
#include "sshcr.h"
|
||||
#ifndef NO_GSSAPI
|
||||
#include "sshgssc.h"
|
||||
#include "sshgss.h"
|
||||
|
@ -25,26 +26,6 @@
|
|||
#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Packet type contexts, so that ssh2_pkt_type can correctly decode
|
||||
* the ambiguous type numbers back into the correct type strings.
|
||||
*/
|
||||
typedef enum {
|
||||
SSH2_PKTCTX_NOKEX,
|
||||
SSH2_PKTCTX_DHGROUP,
|
||||
SSH2_PKTCTX_DHGEX,
|
||||
SSH2_PKTCTX_ECDHKEX,
|
||||
SSH2_PKTCTX_GSSKEX,
|
||||
SSH2_PKTCTX_RSAKEX
|
||||
} Pkt_KCtx;
|
||||
typedef enum {
|
||||
SSH2_PKTCTX_NOAUTH,
|
||||
SSH2_PKTCTX_PUBLICKEY,
|
||||
SSH2_PKTCTX_PASSWORD,
|
||||
SSH2_PKTCTX_GSSAPI,
|
||||
SSH2_PKTCTX_KBDINTER
|
||||
} Pkt_ACtx;
|
||||
|
||||
static const char *const ssh2_disconnect_reasons[] = {
|
||||
NULL,
|
||||
"host not allowed to connect",
|
||||
|
@ -200,7 +181,7 @@ static unsigned long rekey_mins(int rekey_time, unsigned long def)
|
|||
#define translate(x) if (type == x) return #x
|
||||
#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x
|
||||
#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x
|
||||
static const char *ssh1_pkt_type(int type)
|
||||
const char *ssh1_pkt_type(int type)
|
||||
{
|
||||
translate(SSH1_MSG_DISCONNECT);
|
||||
translate(SSH1_SMSG_PUBLIC_KEY);
|
||||
|
@ -245,8 +226,7 @@ static const char *ssh1_pkt_type(int type)
|
|||
translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
|
||||
return "unknown";
|
||||
}
|
||||
static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx,
|
||||
int type)
|
||||
const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
|
||||
{
|
||||
translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
|
||||
translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
|
||||
|
@ -313,57 +293,6 @@ enum {
|
|||
PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM,
|
||||
};
|
||||
|
||||
/*
|
||||
* Coroutine mechanics for the sillier bits of the code. If these
|
||||
* macros look impenetrable to you, you might find it helpful to
|
||||
* read
|
||||
*
|
||||
* https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
|
||||
*
|
||||
* which explains the theory behind these macros.
|
||||
*
|
||||
* In particular, if you are getting `case expression not constant'
|
||||
* errors when building with MS Visual Studio, this is because MS's
|
||||
* Edit and Continue debugging feature causes their compiler to
|
||||
* violate ANSI C. To disable Edit and Continue debugging:
|
||||
*
|
||||
* - right-click ssh.c in the FileView
|
||||
* - click Settings
|
||||
* - select the C/C++ tab and the General category
|
||||
* - under `Debug info:', select anything _other_ than `Program
|
||||
* Database for Edit and Continue'.
|
||||
*/
|
||||
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
|
||||
#define crBeginState crBegin(s->crLine)
|
||||
#define crStateP(t, v) \
|
||||
struct t *s; \
|
||||
if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \
|
||||
s = (v);
|
||||
#define crState(t) crStateP(t, ssh->t)
|
||||
#define crFinish(z) } *crLine = 0; return (z); }
|
||||
#define crFinishV } *crLine = 0; return; }
|
||||
#define crFinishFree(z) } sfree(s); return (z); }
|
||||
#define crFinishFreeV } sfree(s); return; }
|
||||
#define crReturn(z) \
|
||||
do {\
|
||||
*crLine =__LINE__; return (z); case __LINE__:;\
|
||||
} while (0)
|
||||
#define crReturnV \
|
||||
do {\
|
||||
*crLine=__LINE__; return; case __LINE__:;\
|
||||
} while (0)
|
||||
#define crStop(z) do{ *crLine = 0; return (z); }while(0)
|
||||
#define crStopV do{ *crLine = 0; return; }while(0)
|
||||
#define crWaitUntil(c) do { crReturn(0); } while (!(c))
|
||||
#define crWaitUntilV(c) do { crReturnV; } while (!(c))
|
||||
#define crMaybeWaitUntil(c) do { while (!(c)) crReturn(0); } while (0)
|
||||
#define crMaybeWaitUntilV(c) do { while (!(c)) crReturnV; } while (0)
|
||||
|
||||
typedef struct PktIn PktIn;
|
||||
typedef struct PktOut PktOut;
|
||||
|
||||
static struct PktOut *ssh1_pkt_init(int pkt_type);
|
||||
static struct PktOut *ssh2_pkt_init(int pkt_type);
|
||||
static void ssh_pkt_ensure(struct PktOut *, int length);
|
||||
static void ssh_pkt_adddata(struct PktOut *, const void *data, int len);
|
||||
static ptrlen ssh2_pkt_construct(Ssh, struct PktOut *);
|
||||
|
@ -386,47 +315,6 @@ static void ssh1_login_input(Ssh ssh);
|
|||
static void ssh2_userauth_input(Ssh ssh);
|
||||
static void ssh2_connection_input(Ssh ssh);
|
||||
|
||||
/*
|
||||
* Buffer management constants. There are several of these for
|
||||
* various different purposes:
|
||||
*
|
||||
* - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
|
||||
* on a local data stream before we throttle the whole SSH
|
||||
* connection (in SSH-1 only). Throttling the whole connection is
|
||||
* pretty drastic so we set this high in the hope it won't
|
||||
* happen very often.
|
||||
*
|
||||
* - SSH_MAX_BACKLOG is the amount of backlog that must build up
|
||||
* on the SSH connection itself before we defensively throttle
|
||||
* _all_ local data streams. This is pretty drastic too (though
|
||||
* thankfully unlikely in SSH-2 since the window mechanism should
|
||||
* ensure that the server never has any need to throttle its end
|
||||
* of the connection), so we set this high as well.
|
||||
*
|
||||
* - OUR_V2_WINSIZE is the default window size we present on SSH-2
|
||||
* channels.
|
||||
*
|
||||
* - OUR_V2_BIGWIN is the window size we advertise for the only
|
||||
* channel in a simple connection. It must be <= INT_MAX.
|
||||
*
|
||||
* - OUR_V2_MAXPKT is the official "maximum packet size" we send
|
||||
* to the remote side. This actually has nothing to do with the
|
||||
* size of the _packet_, but is instead a limit on the amount
|
||||
* of data we're willing to receive in a single SSH2 channel
|
||||
* data message.
|
||||
*
|
||||
* - OUR_V2_PACKETLIMIT is actually the maximum size of SSH
|
||||
* _packet_ we're prepared to cope with. It must be a multiple
|
||||
* of the cipher block size, and must be at least 35000.
|
||||
*/
|
||||
|
||||
#define SSH1_BUFFER_LIMIT 32768
|
||||
#define SSH_MAX_BACKLOG 32768
|
||||
#define OUR_V2_WINSIZE 16384
|
||||
#define OUR_V2_BIGWIN 0x7fffffff
|
||||
#define OUR_V2_MAXPKT 0x4000UL
|
||||
#define OUR_V2_PACKETLIMIT 0x9000UL
|
||||
|
||||
struct ssh_signkey_with_user_pref_id {
|
||||
const ssh_keyalg *alg;
|
||||
int id;
|
||||
|
@ -670,40 +558,6 @@ struct ssh_portfwd {
|
|||
((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \
|
||||
sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
|
||||
|
||||
typedef struct PacketQueueNode PacketQueueNode;
|
||||
struct PacketQueueNode {
|
||||
PacketQueueNode *next, *prev;
|
||||
};
|
||||
|
||||
struct PktIn {
|
||||
int refcount;
|
||||
int type;
|
||||
unsigned long sequence; /* SSH-2 incoming sequence number */
|
||||
long encrypted_len; /* for SSH-2 total-size counting */
|
||||
PacketQueueNode qnode; /* for linking this packet on to a queue */
|
||||
BinarySource_IMPLEMENTATION;
|
||||
};
|
||||
|
||||
struct PktOut {
|
||||
long prefix; /* bytes up to and including type field */
|
||||
long length; /* total bytes, including prefix */
|
||||
int type;
|
||||
long forcepad; /* SSH-2: force padding to at least this length */
|
||||
unsigned char *data; /* allocated storage */
|
||||
long maxlen; /* amount of storage allocated for `data' */
|
||||
long encrypted_len; /* for SSH-2 total-size counting */
|
||||
|
||||
/* Extra metadata used in SSH packet logging mode, allowing us to
|
||||
* log in the packet header line that the packet came from a
|
||||
* connection-sharing downstream and what if anything unusual was
|
||||
* done to it. The additional_log_text field is expected to be a
|
||||
* static string - it will not be freed. */
|
||||
unsigned downstream_id;
|
||||
const char *additional_log_text;
|
||||
|
||||
BinarySink_IMPLEMENTATION;
|
||||
};
|
||||
|
||||
static void ssh1_protocol_setup(Ssh ssh);
|
||||
static void ssh2_protocol_setup(Ssh ssh);
|
||||
static void ssh2_bare_connection_protocol_setup(Ssh ssh);
|
||||
|
@ -724,18 +578,13 @@ static PktOut *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx,
|
|||
const char *authtype);
|
||||
#endif
|
||||
static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin);
|
||||
static void ssh_unref_packet(PktIn *pkt);
|
||||
|
||||
struct PacketQueue {
|
||||
PacketQueueNode end;
|
||||
};
|
||||
|
||||
static void pq_init(struct PacketQueue *pq)
|
||||
void pq_init(struct PacketQueue *pq)
|
||||
{
|
||||
pq->end.next = pq->end.prev = &pq->end;
|
||||
}
|
||||
|
||||
static void pq_push(struct PacketQueue *pq, PktIn *pkt)
|
||||
void pq_push(struct PacketQueue *pq, PktIn *pkt)
|
||||
{
|
||||
PacketQueueNode *node = &pkt->qnode;
|
||||
assert(!node->next);
|
||||
|
@ -746,7 +595,7 @@ static void pq_push(struct PacketQueue *pq, PktIn *pkt)
|
|||
node->prev->next = node;
|
||||
}
|
||||
|
||||
static void pq_push_front(struct PacketQueue *pq, PktIn *pkt)
|
||||
void pq_push_front(struct PacketQueue *pq, PktIn *pkt)
|
||||
{
|
||||
PacketQueueNode *node = &pkt->qnode;
|
||||
assert(!node->next);
|
||||
|
@ -757,14 +606,14 @@ static void pq_push_front(struct PacketQueue *pq, PktIn *pkt)
|
|||
node->prev->next = node;
|
||||
}
|
||||
|
||||
static PktIn *pq_peek(struct PacketQueue *pq)
|
||||
PktIn *pq_peek(struct PacketQueue *pq)
|
||||
{
|
||||
if (pq->end.next == &pq->end)
|
||||
return NULL;
|
||||
return FROMFIELD(pq->end.next, PktIn, qnode);
|
||||
}
|
||||
|
||||
static PktIn *pq_pop(struct PacketQueue *pq)
|
||||
PktIn *pq_pop(struct PacketQueue *pq)
|
||||
{
|
||||
PacketQueueNode *node = pq->end.next;
|
||||
if (node == &pq->end)
|
||||
|
@ -777,15 +626,14 @@ static PktIn *pq_pop(struct PacketQueue *pq)
|
|||
return FROMFIELD(node, PktIn, qnode);
|
||||
}
|
||||
|
||||
static void pq_clear(struct PacketQueue *pq)
|
||||
void pq_clear(struct PacketQueue *pq)
|
||||
{
|
||||
PktIn *pkt;
|
||||
while ((pkt = pq_pop(pq)) != NULL)
|
||||
ssh_unref_packet(pkt);
|
||||
}
|
||||
|
||||
static int pq_empty_on_to_front_of(struct PacketQueue *src,
|
||||
struct PacketQueue *dest)
|
||||
int pq_empty_on_to_front_of(struct PacketQueue *src, struct PacketQueue *dest)
|
||||
{
|
||||
struct PacketQueueNode *srcfirst, *srclast;
|
||||
|
||||
|
@ -1373,13 +1221,13 @@ static void c_write_str(Ssh ssh, const char *buf)
|
|||
c_write(ssh, buf, strlen(buf));
|
||||
}
|
||||
|
||||
static void ssh_unref_packet(PktIn *pkt)
|
||||
void ssh_unref_packet(PktIn *pkt)
|
||||
{
|
||||
if (--pkt->refcount <= 0)
|
||||
sfree(pkt);
|
||||
}
|
||||
|
||||
static void ssh_free_pktout(PktOut *pkt)
|
||||
void ssh_free_pktout(PktOut *pkt)
|
||||
{
|
||||
sfree(pkt->data);
|
||||
sfree(pkt);
|
||||
|
@ -2347,7 +2195,7 @@ static void ssh_pkt_BinarySink_write(BinarySink *bs,
|
|||
ssh_pkt_adddata(pkt, data, len);
|
||||
}
|
||||
|
||||
static PktOut *ssh1_pkt_init(int pkt_type)
|
||||
PktOut *ssh1_pkt_init(int pkt_type)
|
||||
{
|
||||
PktOut *pkt = ssh_new_packet();
|
||||
pkt->length = 4 + 8; /* space for length + max padding */
|
||||
|
@ -2359,7 +2207,7 @@ static PktOut *ssh1_pkt_init(int pkt_type)
|
|||
return pkt;
|
||||
}
|
||||
|
||||
static PktOut *ssh2_pkt_init(int pkt_type)
|
||||
PktOut *ssh2_pkt_init(int pkt_type)
|
||||
{
|
||||
PktOut *pkt = ssh_new_packet();
|
||||
pkt->length = 5; /* space for packet length + padding length */
|
||||
|
|
115
ssh.h
115
ssh.h
|
@ -22,6 +22,118 @@ void sshfwd_x11_sharing_handover(struct ssh_channel *c,
|
|||
const void *initial_data, int initial_len);
|
||||
void sshfwd_x11_is_local(struct ssh_channel *c);
|
||||
|
||||
/*
|
||||
* Buffer management constants. There are several of these for
|
||||
* various different purposes:
|
||||
*
|
||||
* - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
|
||||
* on a local data stream before we throttle the whole SSH
|
||||
* connection (in SSH-1 only). Throttling the whole connection is
|
||||
* pretty drastic so we set this high in the hope it won't
|
||||
* happen very often.
|
||||
*
|
||||
* - SSH_MAX_BACKLOG is the amount of backlog that must build up
|
||||
* on the SSH connection itself before we defensively throttle
|
||||
* _all_ local data streams. This is pretty drastic too (though
|
||||
* thankfully unlikely in SSH-2 since the window mechanism should
|
||||
* ensure that the server never has any need to throttle its end
|
||||
* of the connection), so we set this high as well.
|
||||
*
|
||||
* - OUR_V2_WINSIZE is the default window size we present on SSH-2
|
||||
* channels.
|
||||
*
|
||||
* - OUR_V2_BIGWIN is the window size we advertise for the only
|
||||
* channel in a simple connection. It must be <= INT_MAX.
|
||||
*
|
||||
* - OUR_V2_MAXPKT is the official "maximum packet size" we send
|
||||
* to the remote side. This actually has nothing to do with the
|
||||
* size of the _packet_, but is instead a limit on the amount
|
||||
* of data we're willing to receive in a single SSH2 channel
|
||||
* data message.
|
||||
*
|
||||
* - OUR_V2_PACKETLIMIT is actually the maximum size of SSH
|
||||
* _packet_ we're prepared to cope with. It must be a multiple
|
||||
* of the cipher block size, and must be at least 35000.
|
||||
*/
|
||||
|
||||
#define SSH1_BUFFER_LIMIT 32768
|
||||
#define SSH_MAX_BACKLOG 32768
|
||||
#define OUR_V2_WINSIZE 16384
|
||||
#define OUR_V2_BIGWIN 0x7fffffff
|
||||
#define OUR_V2_MAXPKT 0x4000UL
|
||||
#define OUR_V2_PACKETLIMIT 0x9000UL
|
||||
|
||||
typedef struct PacketQueueNode PacketQueueNode;
|
||||
struct PacketQueueNode {
|
||||
PacketQueueNode *next, *prev;
|
||||
};
|
||||
|
||||
typedef struct PktIn {
|
||||
int refcount;
|
||||
int type;
|
||||
unsigned long sequence; /* SSH-2 incoming sequence number */
|
||||
long encrypted_len; /* for SSH-2 total-size counting */
|
||||
PacketQueueNode qnode; /* for linking this packet on to a queue */
|
||||
BinarySource_IMPLEMENTATION;
|
||||
} PktIn;
|
||||
|
||||
typedef struct PktOut {
|
||||
long prefix; /* bytes up to and including type field */
|
||||
long length; /* total bytes, including prefix */
|
||||
int type;
|
||||
long forcepad; /* SSH-2: force padding to at least this length */
|
||||
unsigned char *data; /* allocated storage */
|
||||
long maxlen; /* amount of storage allocated for `data' */
|
||||
long encrypted_len; /* for SSH-2 total-size counting */
|
||||
|
||||
/* Extra metadata used in SSH packet logging mode, allowing us to
|
||||
* log in the packet header line that the packet came from a
|
||||
* connection-sharing downstream and what if anything unusual was
|
||||
* done to it. The additional_log_text field is expected to be a
|
||||
* static string - it will not be freed. */
|
||||
unsigned downstream_id;
|
||||
const char *additional_log_text;
|
||||
|
||||
BinarySink_IMPLEMENTATION;
|
||||
} PktOut;
|
||||
|
||||
typedef struct PacketQueue {
|
||||
PacketQueueNode end;
|
||||
} PacketQueue;
|
||||
|
||||
void pq_init(struct PacketQueue *pq);
|
||||
void pq_push(struct PacketQueue *pq, PktIn *pkt);
|
||||
void pq_push_front(struct PacketQueue *pq, PktIn *pkt);
|
||||
PktIn *pq_peek(struct PacketQueue *pq);
|
||||
PktIn *pq_pop(struct PacketQueue *pq);
|
||||
void pq_clear(struct PacketQueue *pq);
|
||||
int pq_empty_on_to_front_of(struct PacketQueue *src, struct PacketQueue *dest);
|
||||
|
||||
/*
|
||||
* Packet type contexts, so that ssh2_pkt_type can correctly decode
|
||||
* the ambiguous type numbers back into the correct type strings.
|
||||
*/
|
||||
typedef enum {
|
||||
SSH2_PKTCTX_NOKEX,
|
||||
SSH2_PKTCTX_DHGROUP,
|
||||
SSH2_PKTCTX_DHGEX,
|
||||
SSH2_PKTCTX_ECDHKEX,
|
||||
SSH2_PKTCTX_GSSKEX,
|
||||
SSH2_PKTCTX_RSAKEX
|
||||
} Pkt_KCtx;
|
||||
typedef enum {
|
||||
SSH2_PKTCTX_NOAUTH,
|
||||
SSH2_PKTCTX_PUBLICKEY,
|
||||
SSH2_PKTCTX_PASSWORD,
|
||||
SSH2_PKTCTX_GSSAPI,
|
||||
SSH2_PKTCTX_KBDINTER
|
||||
} Pkt_ACtx;
|
||||
|
||||
PktOut *ssh1_pkt_init(int pkt_type);
|
||||
PktOut *ssh2_pkt_init(int pkt_type);
|
||||
void ssh_unref_packet(PktIn *pkt);
|
||||
void ssh_free_pktout(PktOut *pkt);
|
||||
|
||||
extern Socket ssh_connection_sharing_init(
|
||||
const char *host, int port, Conf *conf, Ssh ssh, Plug sshplug,
|
||||
void **state);
|
||||
|
@ -1048,6 +1160,9 @@ void platform_ssh_share_cleanup(const char *name);
|
|||
|
||||
#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */
|
||||
|
||||
const char *ssh1_pkt_type(int type);
|
||||
const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type);
|
||||
|
||||
/*
|
||||
* Need this to warn about support for the original SSH-2 keyfile
|
||||
* format.
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Coroutine mechanics used in PuTTY's SSH code.
|
||||
*/
|
||||
|
||||
#ifndef PUTTY_SSHCR_H
|
||||
#define PUTTY_SSHCR_H
|
||||
|
||||
/*
|
||||
* If these macros look impenetrable to you, you might find it helpful
|
||||
* to read
|
||||
*
|
||||
* https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
|
||||
*
|
||||
* which explains the theory behind these macros.
|
||||
*
|
||||
* In particular, if you are getting `case expression not constant'
|
||||
* errors when building with MS Visual Studio, this is because MS's
|
||||
* Edit and Continue debugging feature causes their compiler to
|
||||
* violate ANSI C. To disable Edit and Continue debugging:
|
||||
*
|
||||
* - right-click ssh.c in the FileView
|
||||
* - click Settings
|
||||
* - select the C/C++ tab and the General category
|
||||
* - under `Debug info:', select anything _other_ than `Program
|
||||
* Database for Edit and Continue'.
|
||||
*/
|
||||
|
||||
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
|
||||
#define crBeginState crBegin(s->crLine)
|
||||
#define crStateP(t, v) \
|
||||
struct t *s; \
|
||||
if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \
|
||||
s = (v);
|
||||
#define crState(t) crStateP(t, ssh->t)
|
||||
#define crFinish(z) } *crLine = 0; return (z); }
|
||||
#define crFinishV } *crLine = 0; return; }
|
||||
#define crFinishFree(z) } sfree(s); return (z); }
|
||||
#define crFinishFreeV } sfree(s); return; }
|
||||
#define crReturn(z) \
|
||||
do {\
|
||||
*crLine =__LINE__; return (z); case __LINE__:;\
|
||||
} while (0)
|
||||
#define crReturnV \
|
||||
do {\
|
||||
*crLine=__LINE__; return; case __LINE__:;\
|
||||
} while (0)
|
||||
#define crStop(z) do{ *crLine = 0; return (z); }while(0)
|
||||
#define crStopV do{ *crLine = 0; return; }while(0)
|
||||
#define crWaitUntil(c) do { crReturn(0); } while (!(c))
|
||||
#define crWaitUntilV(c) do { crReturnV; } while (!(c))
|
||||
#define crMaybeWaitUntil(c) do { while (!(c)) crReturn(0); } while (0)
|
||||
#define crMaybeWaitUntilV(c) do { while (!(c)) crReturnV; } while (0)
|
||||
|
||||
#endif /* PUTTY_SSHCR_H */
|
Загрузка…
Ссылка в новой задаче