mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-05 15:59:45 +00:00
1307 lines
33 KiB
C
1307 lines
33 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* The contents of this file are subject to the Mozilla Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1994-2000 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* terms of the GNU General Public License Version 2 or later (the
|
|
* "GPL"), in which case the provisions of the GPL are applicable
|
|
* instead of those above. If you wish to allow use of your
|
|
* version of this file only under the terms of the GPL and not to
|
|
* allow others to use your version of this file under the MPL,
|
|
* indicate your decision by deleting the provisions above and
|
|
* replace them with the notice and other provisions required by
|
|
* the GPL. If you do not delete the provisions above, a recipient
|
|
* may use your version of this file under either the MPL or the
|
|
* GPL.
|
|
*/
|
|
#include "serv.h"
|
|
#include "secport.h"
|
|
#include "collectn.h"
|
|
#include "resource.h"
|
|
#include "connect.h"
|
|
#include "plstr.h"
|
|
#include "prprf.h"
|
|
#include "base64.h"
|
|
#include "textgen.h"
|
|
#include "protocolf.h"
|
|
#include "p12res.h"
|
|
#include "ctrlconn.h"
|
|
#include "newproto.h"
|
|
#include "messages.h"
|
|
|
|
#ifdef XP_UNIX
|
|
#include "private/pprio.h"
|
|
#include <unistd.h>
|
|
|
|
#define GET_LOCK_FILE_PATH(b) \
|
|
sprintf(b, "/tmp/.nsmc-%d-lock", (int)geteuid())
|
|
#define GET_CONTROL_SOCK_PATH(b) \
|
|
sprintf(b, "/tmp/.nsmc-%d", (int)geteuid())
|
|
|
|
PRFileDesc *lockfile = NULL;
|
|
|
|
#endif
|
|
|
|
#define DEBUG_QUEUES
|
|
|
|
extern SSMCollection * connections;
|
|
|
|
/* Dumps a (potentially binary) buffer using SSM_DEBUG.
|
|
(We could have used the version in ssltrace.c, but that's
|
|
specifically tailored to SSLTRACE. Sigh. */
|
|
#define DUMPBUF_LINESIZE 8
|
|
void
|
|
SSM_DumpBuffer(char *buf, PRIntn len)
|
|
{
|
|
char hexbuf[DUMPBUF_LINESIZE*3+1];
|
|
char chrbuf[DUMPBUF_LINESIZE+1];
|
|
static const char *hex = "0123456789abcdef";
|
|
PRIntn i = 0, l = 0;
|
|
char ch, *c, *h;
|
|
|
|
hexbuf[DUMPBUF_LINESIZE*3] = '\0';
|
|
chrbuf[DUMPBUF_LINESIZE] = '\0';
|
|
(void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
|
|
(void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
|
|
h = hexbuf;
|
|
c = chrbuf;
|
|
|
|
while (i < len)
|
|
{
|
|
ch = buf[i];
|
|
|
|
if (l == DUMPBUF_LINESIZE)
|
|
{
|
|
SSM_DEBUG("%s%s\n", hexbuf, chrbuf);
|
|
(void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
|
|
(void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
|
|
h = hexbuf;
|
|
c = chrbuf;
|
|
l = 0;
|
|
}
|
|
|
|
/* Convert a character to hex. */
|
|
*h++ = hex[(ch >> 4) & 0xf];
|
|
*h++ = hex[ch & 0xf];
|
|
h++;
|
|
|
|
/* Put the character (if it's printable) into the character buffer. */
|
|
if ((ch >= 0x20) && (ch <= 0x7e))
|
|
*c++ = ch;
|
|
else
|
|
*c++ = '.';
|
|
i++; l++;
|
|
}
|
|
SSM_DEBUG("%s%s\n", hexbuf, chrbuf);
|
|
}
|
|
|
|
PRIntn SSM_ReadThisMany(PRFileDesc * sockID, void * buffer, PRIntn thisMany)
|
|
{
|
|
int got = 0;
|
|
PRIntn total = 0;
|
|
|
|
while (total < thisMany) {
|
|
got = PR_Recv(sockID, (void *)((char *)buffer + total), thisMany-total,
|
|
0, PR_INTERVAL_NO_TIMEOUT);
|
|
if (got <= 0 ) break;
|
|
total += got;
|
|
}
|
|
|
|
/* Clear the PR error if we got EOF in the final read,
|
|
as opposed to -1 bytes. */
|
|
if (got == 0)
|
|
PR_SetError(0, 0);
|
|
|
|
return total;
|
|
}
|
|
|
|
PRIntn SSM_WriteThisMany(PRFileDesc * sockID, void * buffer, PRIntn thisMany)
|
|
{
|
|
PRIntn total = 0;
|
|
|
|
while (total < thisMany) {
|
|
PRIntn got;
|
|
got = PR_Send(sockID, (void *)((char *)buffer+total), thisMany-total,
|
|
0, PR_INTERVAL_NO_TIMEOUT);
|
|
if (got < 0) break;
|
|
total += got;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
|
|
SECItem * SSM_ConstructMessage(PRUintn size)
|
|
{
|
|
SECItem * ptr = NULL;
|
|
|
|
#if 0
|
|
SSM_DEBUG("allocating message of size %ld.\n", (long) size);
|
|
#endif
|
|
|
|
ptr = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
|
|
if (!ptr) {
|
|
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
|
|
goto loser;
|
|
}
|
|
ptr->len = size;
|
|
if (size > 0) {
|
|
ptr->data = (unsigned char *)PORT_ZAlloc(size);
|
|
if (!ptr->data) {
|
|
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
|
|
goto loser;
|
|
}
|
|
}
|
|
return ptr;
|
|
loser:
|
|
SSM_FreeMessage(ptr);
|
|
return NULL;
|
|
}
|
|
|
|
void SSM_FreeMessage(SECItem * msg)
|
|
{
|
|
if (!msg) return;
|
|
if (msg->data)
|
|
PORT_Free(msg->data);
|
|
PORT_Free(msg);
|
|
}
|
|
|
|
void
|
|
ssm_DrainAndDestroyQueue(SSMCollection **coll)
|
|
{
|
|
SSMCollection *v;
|
|
PRIntn type, len;
|
|
char *data;
|
|
|
|
if ((coll == NULL) || (*coll == NULL))
|
|
return;
|
|
|
|
v = *coll;
|
|
|
|
/* Drain the collection. Don't block; presumably this is
|
|
being done at destruction time. */
|
|
while(SSM_RecvQMessage(v, SSM_PRIORITY_ANY,
|
|
&type, &len, &data,
|
|
PR_FALSE) == PR_SUCCESS)
|
|
{
|
|
if (data)
|
|
PR_Free(data);
|
|
}
|
|
|
|
#if 0
|
|
/* NotifyAll on the lock and yield time. */
|
|
/* ### mwelch - Causing problems on Linux, and shouldn't even
|
|
be here since we don't have the queue locked,
|
|
so I'm removing it. */
|
|
PR_NotifyAll(v->lock);
|
|
#endif
|
|
/* Destroy the collection. */
|
|
SSM_DestroyCollection(v);
|
|
|
|
/* Zero out the original member. */
|
|
*coll = NULL;
|
|
}
|
|
|
|
void
|
|
ssm_DrainAndDestroyChildCollection(SSMCollection **coll)
|
|
{
|
|
SSMCollection *v;
|
|
SSMConnection *conn;
|
|
|
|
if ((coll == NULL) || (*coll == NULL))
|
|
return;
|
|
|
|
v = *coll;
|
|
|
|
/* Drain the collection. Don't block; presumably this is
|
|
being done at destruction time. */
|
|
while(SSM_Dequeue(v, SSM_PRIORITY_ANY, (void **) &conn, PR_FALSE) == PR_SUCCESS) {;}
|
|
|
|
/* Destroy the collection. */
|
|
SSM_DestroyCollection(v);
|
|
|
|
/* Zero out the original member. */
|
|
*coll = NULL;
|
|
}
|
|
|
|
static SSMHashTable *ssmThreads = NULL;
|
|
|
|
void SSM_ConnectionThreadName(char *buf)
|
|
{
|
|
PRThread * threadptr = PR_GetCurrentThread();
|
|
char *name;
|
|
|
|
buf[0] = '\0'; /* in case we fail */
|
|
|
|
if (!ssmThreads) return;
|
|
|
|
if (SSM_HashFind(ssmThreads, (SSMHashKey) threadptr, (void **) &name)
|
|
!= SSM_SUCCESS)
|
|
return;
|
|
|
|
if (name)
|
|
PL_strcpy(buf, name);
|
|
}
|
|
|
|
static SSMCollection *logSockets = NULL;
|
|
static PRMonitor *logSocketLock = NULL;
|
|
|
|
/* Add the filedesc to a list of sockets to receive log output */
|
|
SSMStatus
|
|
SSM_AddLogSocket(PRFileDesc *fd)
|
|
{
|
|
SSMStatus rv = SSM_SUCCESS;
|
|
|
|
PR_ASSERT(logSockets);
|
|
PR_ASSERT(logSocketLock);
|
|
|
|
PR_EnterMonitor(logSocketLock);
|
|
rv = SSM_Enqueue(logSockets, SSM_PRIORITY_NORMAL, fd);
|
|
PR_ExitMonitor(logSocketLock);
|
|
|
|
if (rv != SSM_SUCCESS)
|
|
SSM_DEBUG("SSM_AddLogSocket returned error %d.\n", rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#include "macglue.h"
|
|
#endif
|
|
|
|
/* Called by a newly created thread, this associates a name with the thread. */
|
|
void
|
|
SSM_RegisterThread(char *threadName, SSMResource *ptr)
|
|
{
|
|
SSMStatus rv = SSM_SUCCESS;
|
|
PRThread *thr = PR_GetCurrentThread();
|
|
char *value;
|
|
|
|
#ifdef XP_MAC
|
|
/*
|
|
Each thread, when registering itself using one of the two functions
|
|
below, assigns itself as private data at index (thdIndex). This is
|
|
necessary because when a thread exits, we want it to delete itself
|
|
from the list of threads we need to kill at exit time.
|
|
*/
|
|
{
|
|
PRUintn threadIndex = GetThreadIndex();
|
|
if (threadIndex > 0)
|
|
PR_SetThreadPrivate(threadIndex, PR_GetCurrentThread());
|
|
}
|
|
#endif
|
|
|
|
if (ptr)
|
|
{
|
|
PR_ASSERT(SSM_IsAKindOf(ptr, SSM_RESTYPE_RESOURCE));
|
|
/* Increment the thread count for this resource. */
|
|
SSM_LockResource(ptr);
|
|
ptr->m_threadCount++;
|
|
SSM_UnlockResource(ptr);
|
|
}
|
|
if (threadName)
|
|
{
|
|
if (ptr)
|
|
value = PR_smprintf("%s %p", threadName, ptr);
|
|
else
|
|
value = PL_strdup(threadName);
|
|
}
|
|
else
|
|
value = PR_smprintf("unknown %p/%p", thr, ptr);
|
|
|
|
if ((!ssmThreads) &&
|
|
((rv = SSM_HashCreate(&ssmThreads)) != SSM_SUCCESS))
|
|
return;
|
|
|
|
if (!value) return;
|
|
|
|
SSM_HashInsert(ssmThreads, (SSMHashKey) thr, value);
|
|
}
|
|
|
|
/* Called by a newly created thread, this associates a name with the thread. */
|
|
void
|
|
SSM_RegisterNewThread(char *threadName, SSMResource *ptr)
|
|
{
|
|
SSMStatus rv = SSM_SUCCESS;
|
|
PRThread *thr = PR_GetCurrentThread();
|
|
char *value;
|
|
|
|
if (threadName)
|
|
{
|
|
if (ptr)
|
|
value = PR_smprintf("%s %p", threadName, ptr);
|
|
else
|
|
value = PL_strdup(threadName);
|
|
}
|
|
else
|
|
value = PR_smprintf("unknown %p/%p", thr, ptr);
|
|
|
|
if ((!ssmThreads) &&
|
|
((rv = SSM_HashCreate(&ssmThreads)) != SSM_SUCCESS))
|
|
return;
|
|
|
|
if (!value) return;
|
|
|
|
SSM_HashInsert(ssmThreads, (SSMHashKey) thr, value);
|
|
}
|
|
|
|
void SSM_Debug(SSMResource *conn, char *msg)
|
|
{
|
|
#ifdef DEBUG
|
|
char name[256];
|
|
|
|
SSM_ConnectionThreadName(name);
|
|
printf("%s: %s", name, msg);
|
|
fflush(stdout);
|
|
PR_Free(msg);
|
|
#endif
|
|
}
|
|
|
|
void SSM_DebugP(char *fmt, ...)
|
|
{
|
|
#if defined(DEBUG) && !defined(XP_MAC)
|
|
char *tmp = NULL, *tmp2 = NULL;
|
|
char *timeStamp = NULL;
|
|
PRFileDesc *sock = NULL;
|
|
PRIntn len;
|
|
PRIntn numWritten;
|
|
char name[256];
|
|
va_list argp;
|
|
PRIntn count, i;
|
|
PRUint32 rv = 0;
|
|
PRExplodedTime exploded;
|
|
|
|
/* protect against another socket add */
|
|
PR_EnterMonitor(logSocketLock);
|
|
|
|
if (!logSockets)
|
|
goto done;
|
|
|
|
count = SSM_Count(logSockets);
|
|
if (count == 0)
|
|
goto done;
|
|
|
|
va_start(argp, fmt);
|
|
tmp = PR_vsmprintf(fmt, argp);
|
|
va_end(argp);
|
|
if (!tmp)
|
|
goto done;
|
|
|
|
PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
|
|
timeStamp = PR_smprintf("%02i:%02i:%02i.%03i", exploded.tm_hour,
|
|
exploded.tm_min, exploded.tm_sec,
|
|
exploded.tm_usec/1000);
|
|
SSM_ConnectionThreadName(name);
|
|
|
|
tmp2 = PR_smprintf("[%s] %s: %s", timeStamp, name, tmp);
|
|
if (!tmp2)
|
|
goto done;
|
|
|
|
len = strlen(tmp2);
|
|
count = SSM_Count(logSockets);
|
|
|
|
/* count backwards in case we get an error */
|
|
for(i=(count-1);i>=0;i--)
|
|
{
|
|
numWritten = 0;
|
|
sock = (PRFileDesc *) SSM_At(logSockets, i);
|
|
if (sock)
|
|
numWritten = PR_Write(sock, tmp2, len);
|
|
if (numWritten < len)
|
|
{
|
|
SSM_Remove(logSockets, sock);
|
|
/*PR_Close(sock);*/
|
|
/*PR_Shutdown(sock, PR_SHUTDOWN_BOTH);*/
|
|
rv = 0;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (tmp)
|
|
PR_smprintf_free(tmp);
|
|
if (tmp2)
|
|
PR_smprintf_free(tmp2);
|
|
if (timeStamp)
|
|
PR_smprintf_free(timeStamp);
|
|
PR_ExitMonitor(logSocketLock);
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
SSM_InitLogging(void)
|
|
{
|
|
char *suppressConsole = NULL;
|
|
char *debugFile = NULL;
|
|
|
|
/* Create the condition variable we use to control logging. */
|
|
if (!(logSocketLock = PR_NewMonitor()))
|
|
goto loser;
|
|
if (!(logSockets = SSM_NewCollection()))
|
|
goto loser;
|
|
|
|
suppressConsole = PR_GetEnv(SSM_ENV_SUPPRESS_CONSOLE);
|
|
debugFile = PR_GetEnv(SSM_ENV_LOG_FILE);
|
|
|
|
if (!suppressConsole)
|
|
{
|
|
PRFileDesc *stdfd = PR_GetSpecialFD(PR_StandardOutput);
|
|
if (!stdfd)
|
|
stdfd = PR_GetSpecialFD(PR_StandardError);
|
|
if (stdfd)
|
|
SSM_AddLogSocket(stdfd);
|
|
}
|
|
|
|
if (debugFile)
|
|
{
|
|
PRFileDesc *fd = PR_Open(debugFile,
|
|
PR_WRONLY | PR_APPEND |
|
|
PR_CREATE_FILE | PR_SYNC,
|
|
0644);
|
|
if (fd)
|
|
SSM_AddLogSocket(fd);
|
|
}
|
|
|
|
SSM_DEBUG("Started logging.\n");
|
|
|
|
return;
|
|
loser:
|
|
fprintf(stderr, "Can't initialize logging! Exiting.\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
SSMStatus SSM_SendQMessage(SSMCollection *q,
|
|
PRIntn priority,
|
|
PRIntn type, PRIntn length, char *data,
|
|
PRBool doBlock)
|
|
{
|
|
SECItem *n = NULL;
|
|
unsigned char *c = NULL;
|
|
SSMStatus rv = PR_FAILURE;
|
|
|
|
PR_ASSERT(priority != SSM_PRIORITY_ANY);
|
|
|
|
#ifdef DEBUG_QUEUES
|
|
SSM_DEBUG("SendQMessage on %lx: prio %d, type %lx, len %d\n",
|
|
(unsigned long) q, priority, (unsigned long) type, length);
|
|
#endif
|
|
|
|
/* Create a new message. */
|
|
n = (SECItem *) PORT_ZAlloc(sizeof(SECItem));
|
|
if (!n)
|
|
goto loser;
|
|
|
|
if (data && length > 0) {
|
|
c = (unsigned char *) PORT_ZAlloc(length);
|
|
if (!c)
|
|
goto loser;
|
|
|
|
memcpy(c, data, length);
|
|
}
|
|
|
|
n->type = (SECItemType) type;
|
|
n->len = length;
|
|
n->data = c;
|
|
|
|
/* ### mwelch We'll want to put a throttle here when we
|
|
start really securing Cartman. It's currently
|
|
possible for someone to make us run out of
|
|
memory very quickly by flooding us with data. */
|
|
|
|
/* Put the msg in the queue. */
|
|
SSM_Enqueue(q, priority, n);
|
|
return PR_SUCCESS;
|
|
|
|
loser:
|
|
if (n) PORT_Free(n);
|
|
if (c) PORT_Free(c);
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSM_RecvQMessage(SSMCollection *q,
|
|
PRIntn priority,
|
|
PRIntn *type, PRIntn *length, char **data,
|
|
PRBool doBlock)
|
|
{
|
|
SECItem *p;
|
|
void *v;
|
|
|
|
#ifdef DEBUG_QUEUES
|
|
SSM_DEBUG("RecvQMessage on %lx: %s read at prio %d\n",
|
|
(unsigned long) q,
|
|
(doBlock ? "blocking" : "nonblocking"),
|
|
priority);
|
|
#endif
|
|
|
|
/* Remove the item at the head of the queue. */
|
|
SSM_Dequeue(q, priority, &v, doBlock);
|
|
p = (SECItem *) v;
|
|
|
|
if (!p)
|
|
return PR_FAILURE;
|
|
|
|
/* Strip out the items. */
|
|
*data = (char *)p->data;
|
|
*length = p->len;
|
|
*type = p->type;
|
|
|
|
#ifdef DEBUG_QUEUES
|
|
SSM_DEBUG("RecvQMessage on %lx: type %lx, len %d\n",
|
|
(unsigned long) q, (unsigned long) *type,
|
|
*length);
|
|
#endif
|
|
|
|
/* Free the containing SECItem. */
|
|
PORT_Free(p);
|
|
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
SSMStatus
|
|
SSM_SECItemToSSMString(void ** ssmstring, SECItem * data)
|
|
{
|
|
SSMString * result = NULL;
|
|
|
|
if (!ssmstring || !data || data->len <= 0 )
|
|
goto loser;
|
|
|
|
*ssmstring = NULL; /* in case we fail */
|
|
result = (SSMString *) SSMPORT_ZAlloc(sizeof(data->len) +
|
|
SSMSTRING_PADDED_LENGTH(data->len));
|
|
if (!result)
|
|
goto loser;
|
|
memcpy((char *)(&(result->m_data)), data->data, data->len);
|
|
result->m_length = data->len;
|
|
*ssmstring = result;
|
|
return PR_SUCCESS;
|
|
|
|
loser:
|
|
if (result)
|
|
|
|
PR_Free(result);
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
SSMStatus
|
|
SSM_CopyCMTItem(CMTItem * dest, CMTItem * source)
|
|
{
|
|
SSMStatus rv = SSM_FAILURE;
|
|
|
|
if (!dest || !source)
|
|
goto loser;
|
|
|
|
dest->len = source->len;
|
|
if (!dest->len)
|
|
goto done;
|
|
|
|
dest->data = (unsigned char *) PR_Malloc(dest->len);
|
|
if (!dest->data)
|
|
goto loser;
|
|
memcpy(dest->data, source->data, dest->len);
|
|
done:
|
|
rv = SSM_SUCCESS;
|
|
loser:
|
|
return rv;
|
|
}
|
|
|
|
|
|
PRFileDesc * SSM_OpenControlPort(void)
|
|
{
|
|
PRFileDesc * datasocket = NULL;
|
|
PRNetAddr servaddr;
|
|
SSMStatus status;
|
|
PRSocketOptionData sockOpData;
|
|
#ifdef XP_UNIX
|
|
char lock_name_buf[32];
|
|
#endif
|
|
|
|
/* disable Nagle algorithm delay for control sockets */
|
|
sockOpData.option = PR_SockOpt_NoDelay;
|
|
sockOpData.value.no_delay = PR_TRUE;
|
|
|
|
#ifdef XP_UNIX
|
|
GET_LOCK_FILE_PATH(lock_name_buf);
|
|
lockfile = PR_Open(lock_name_buf, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
|
|
0600);
|
|
if (!lockfile)
|
|
goto loser;
|
|
|
|
status = PR_TLockFile(lockfile);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
|
|
datasocket = PR_Socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (!datasocket)
|
|
goto loser;
|
|
servaddr.local.family = AF_UNIX;
|
|
GET_CONTROL_SOCK_PATH(servaddr.local.path);
|
|
PR_Delete(servaddr.local.path);
|
|
#else
|
|
datasocket = PR_NewTCPSocket();
|
|
if (!datasocket)
|
|
goto loser;
|
|
|
|
status = PR_SetSocketOption(datasocket, &sockOpData);
|
|
if (status != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* Bind to PSM port on loopback address: connections from non-localhosts
|
|
* will be disallowed
|
|
*/
|
|
status = PR_InitializeNetAddr(PR_IpAddrLoopback, (PRUint16) PSM_PORT,
|
|
&servaddr);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
#endif
|
|
|
|
status = PR_Bind(datasocket, &servaddr);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
status = PR_Listen(datasocket, MAX_CONNECTIONS);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
|
|
#ifdef XP_UNIX
|
|
SSM_DEBUG("Ready for client connection on port %s.\n", servaddr.local.path);
|
|
#else
|
|
SSM_DEBUG("Ready for client connection on port %d.\n", PSM_PORT);
|
|
#endif
|
|
|
|
return datasocket;
|
|
loser:
|
|
return NULL;
|
|
}
|
|
|
|
/* Make sure that the peer is on the same machine that we are */
|
|
PRBool
|
|
SSM_SocketPeerCheck(PRFileDesc *sock, PRBool isCtrl)
|
|
{
|
|
PRBool rv = PR_FALSE;
|
|
SSMStatus st = PR_SUCCESS;
|
|
PRNetAddr theirAddr;
|
|
char localaddr[] = {0x7F,0x00,0x00,0x01};/* 127.0.0.1 in network order */
|
|
|
|
#if defined(XP_UNIX) || defined(XP_MAC)
|
|
#ifdef XP_UNIX
|
|
if (isCtrl)
|
|
{
|
|
/* unix domain socket == same machine */
|
|
/* ### mwelch Well, the only time this isn't true is when
|
|
one wants to join many unix machines in a cluster, but
|
|
even in that case the cluster should be treated as a
|
|
single machine anyway. */
|
|
#endif
|
|
/*
|
|
GetPeerName isn't implemented on the Mac, so say yes and hope/pray for now
|
|
### mwelch - Need to implement GetPeerName in Mac NSPR before Mac Cartman RTM
|
|
*/
|
|
rv = PR_TRUE;
|
|
goto done;
|
|
#ifdef XP_UNIX
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
st = PR_GetPeerName(sock, &theirAddr);
|
|
if (st != PR_SUCCESS)
|
|
goto done; /* default is to fail */
|
|
|
|
/* Compare against localhost IP */
|
|
if (!memcmp(&(theirAddr.inet.ip), localaddr, 4))
|
|
rv = PR_TRUE;
|
|
|
|
if (rv != PR_TRUE)
|
|
SSM_DEBUG("Failed IP address check!\n");
|
|
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
PRFileDesc * SSM_OpenPort(void)
|
|
{
|
|
PRFileDesc * datasocket = NULL;
|
|
PRNetAddr servaddr;
|
|
SSMStatus status;
|
|
PRSocketOptionData sockOpData;
|
|
|
|
/* disable Nagle algorithm delay for data sockets */
|
|
sockOpData.option = PR_SockOpt_NoDelay;
|
|
sockOpData.value.no_delay = PR_TRUE;
|
|
|
|
datasocket = PR_NewTCPSocket();
|
|
if (!datasocket)
|
|
goto loser;
|
|
status = PR_SetSocketOption(datasocket, &sockOpData);
|
|
if (status != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* Bind to PSM port on loopback address: connections from non-localhosts
|
|
* will be disallowed
|
|
*/
|
|
status = PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &servaddr);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
|
|
status = PR_Bind(datasocket, &servaddr);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
status = PR_Listen(datasocket, MAX_CONNECTIONS);
|
|
if (status != PR_SUCCESS)
|
|
goto loser;
|
|
|
|
return datasocket;
|
|
loser:
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
/* Whee. Base 64 decoding. Have to do it for basic authentication. */
|
|
#define XX 127
|
|
/*
|
|
* Table for decoding base64. We have this everywhere else, why not here.
|
|
*/
|
|
static char index64[256] = {
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
|
|
52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
|
|
XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
|
15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
|
|
XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
|
41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
|
|
};
|
|
|
|
#ifdef XP_OS2_HACK
|
|
/*DSR102196 - the OS/2 Visual Age compiler croaks when it tries*/
|
|
/*to optomize this macro (/O+ on CSD4) */
|
|
char CHAR64(int c)
|
|
{
|
|
unsigned char index;
|
|
char rc;
|
|
|
|
index = (unsigned char) c;
|
|
rc = index64[index];
|
|
return rc;
|
|
}
|
|
#else /*normal code...*/
|
|
#define CHAR64(c) (index64[(unsigned char)(c)])
|
|
#endif
|
|
|
|
/* Decode a base 64 string in place. */
|
|
void decode_base64_string(char *str)
|
|
{
|
|
char *s = str;
|
|
char *d = str;
|
|
char c[4];
|
|
PRIntn i;
|
|
PRBool done = PR_FALSE;
|
|
|
|
while((!done) && (*s))
|
|
{
|
|
/* Process 4 bytes at a time. */
|
|
for(i=0;i<4;i++)
|
|
c[i] = 0;
|
|
|
|
for(i=0;i<4;i++)
|
|
{
|
|
if ((IS_WHITESPACE(*s)) || (IS_EOL(*s)))
|
|
{
|
|
/* End here, because we're out of bytes. */
|
|
done = PR_TRUE;
|
|
}
|
|
else
|
|
c[i] = *s++;
|
|
}
|
|
|
|
/* Make sure we can at least decode one character. */
|
|
if ((!done) && ((c[0] == '=') || (c[1] == '=')))
|
|
done = PR_TRUE; /* don't even have enough for one character, leave */
|
|
|
|
if (!done)
|
|
{
|
|
/* Decode the first character. */
|
|
c[0] = CHAR64(c[0]);
|
|
c[1] = CHAR64(c[1]);
|
|
*d++ = ((c[0]<<2) | ((c[1] & 0x30)>>4));
|
|
|
|
/* If we have data for a second character, decode that. */
|
|
if (c[2] != '=')
|
|
{
|
|
c[2] = CHAR64(c[2]);
|
|
*d++ = (((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2));
|
|
|
|
/* If we have data for a third character, decode that. */
|
|
if (c[3] != '=')
|
|
{
|
|
c[3] = CHAR64(c[3]);
|
|
*d++ = (((c[2] & 0x03) << 6) | c[3]);
|
|
}
|
|
else
|
|
done = PR_TRUE;
|
|
}
|
|
else
|
|
done = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
/* Terminate the string. */
|
|
*d = '\0';
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
char *
|
|
SSM_StandaloneGetPasswdCallback(PK11SlotInfo *slot, PRBool retry,
|
|
void *arg)
|
|
{
|
|
char *result = NULL;
|
|
char *env = NULL;
|
|
SECItem theItem = {siBuffer, NULL, 0};
|
|
|
|
if (retry)
|
|
{
|
|
/* Don't spin forever, just bail */
|
|
SSM_DEBUG("Static password you provided was not accepted. "
|
|
"Cancelling request.\n");
|
|
return NULL;
|
|
}
|
|
|
|
env = PR_GetEnv(SSM_ENV_STATIC_PASSWORD);
|
|
if (!env)
|
|
goto done;
|
|
|
|
ATOB_ConvertAsciiToItem(&theItem, env);
|
|
if (theItem.data == NULL)
|
|
goto done;
|
|
|
|
result = (char *) PR_CALLOC(theItem.len + 1);
|
|
if (!result)
|
|
goto done;
|
|
|
|
strncpy(result, (char *) theItem.data, theItem.len);
|
|
|
|
done:
|
|
if (theItem.data != NULL)
|
|
SECITEM_FreeItem(&theItem, PR_FALSE);
|
|
if (result)
|
|
SSM_DEBUG("Responding to password request with a password.\n");
|
|
else
|
|
SSM_DEBUG("Responding to password callback with NULL password.\n");
|
|
|
|
return result;
|
|
}
|
|
|
|
PRBool
|
|
SSM_StandaloneVerifyPasswdCallback(PK11SlotInfo * slot, void * arg)
|
|
{
|
|
/* yeah, sure, trust me, we're the ones who are sposta be logged in */
|
|
return PR_TRUE;
|
|
}
|
|
#endif
|
|
|
|
SSMStatus
|
|
SSM_CloseSocketWithLinger(PRFileDesc *sock)
|
|
{
|
|
PRSocketOptionData opt;
|
|
SSMStatus rv = PR_SUCCESS;
|
|
|
|
/*
|
|
Set linger for 15 minutes.
|
|
### mwelch This time should really depend on what has been written
|
|
to the socket. For example, if we've just written 5 megabytes of data
|
|
down a 14.4 wire, we should wait considerably longer than 15 minutes.
|
|
The point here is that there's no way to determine a constant value
|
|
ahead of time. So, 15 minutes. (Note that generally only client sockets
|
|
are closed in this way, so we hopefully will avoid major trouble.)
|
|
*/
|
|
opt.option = PR_SockOpt_Linger;
|
|
opt.value.linger.polarity = PR_TRUE;
|
|
opt.value.linger.linger = PR_SecondsToInterval(60*15);
|
|
rv = PR_SetSocketOption(sock, &opt);
|
|
|
|
/* Now close the socket. */
|
|
if (rv == PR_SUCCESS)
|
|
rv = PR_Close(sock);
|
|
|
|
return rv;
|
|
}
|
|
|
|
char*
|
|
SSM_GetCharFromKey(const char *key, const char *locale)
|
|
{
|
|
SSMTextGenContext *textGenCxt = NULL;
|
|
char *asciiString = NULL;
|
|
SSMStatus rv;
|
|
|
|
rv = SSMTextGen_NewContext(NULL, NULL, NULL, NULL, &textGenCxt);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
rv = SSM_FindUTF8StringInBundles(textGenCxt, key,
|
|
&asciiString);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
SSMTextGen_DestroyContext(textGenCxt);
|
|
return asciiString;
|
|
loser:
|
|
if (textGenCxt != NULL) {
|
|
SSMTextGen_DestroyContext(textGenCxt);
|
|
}
|
|
if (asciiString != NULL) {
|
|
PR_Free(asciiString);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SSMStatus SSM_RequestFilePathFromUser(SSMResource *res,
|
|
const char *promptKey,
|
|
const char *fileRegEx,
|
|
PRBool getExistingFile)
|
|
{
|
|
SECItem *filePathRequest = NULL;
|
|
char *asciiPrompt = NULL;
|
|
SSMStatus rv;
|
|
FilePathRequest request;
|
|
|
|
filePathRequest = SSM_ZNEW(SECItem);
|
|
if (filePathRequest == NULL) {
|
|
return SSM_FAILURE;
|
|
}
|
|
asciiPrompt = SSM_GetCharFromKey(promptKey, "ISO_8859-1");
|
|
if (asciiPrompt == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
request.resID = res->m_id;
|
|
request.prompt = asciiPrompt;
|
|
request.getExistingFile = (CMBool) getExistingFile;
|
|
request.fileRegEx = fileRegEx;
|
|
|
|
if (CMT_EncodeMessage(FilePathRequestTemplate, (CMTItem*)filePathRequest, (void*)&request) != CMTSuccess) {
|
|
goto loser;
|
|
}
|
|
filePathRequest->type = (SECItemType) (SSM_EVENT_MESSAGE | SSM_FILE_PATH_EVENT);
|
|
SSM_LockResource(res);
|
|
rv = SSM_SendQMessage(res->m_connection->m_controlOutQ,
|
|
20,
|
|
filePathRequest->type,
|
|
filePathRequest->len,
|
|
(char*)filePathRequest->data,
|
|
PR_TRUE);
|
|
SECITEM_FreeItem(filePathRequest, PR_TRUE);
|
|
|
|
SSM_WaitResource(res, PR_INTERVAL_NO_TIMEOUT);
|
|
SSM_UnlockResource(res);
|
|
return (res->m_fileName == NULL) ? SSM_FAILURE : SSM_SUCCESS;
|
|
|
|
loser:
|
|
if (filePathRequest != NULL) {
|
|
SECITEM_FreeItem(filePathRequest, PR_TRUE);
|
|
}
|
|
return SSM_FAILURE;
|
|
}
|
|
|
|
void SSM_HandleFilePathReply(SSMControlConnection *ctrl,
|
|
SECItem *message)
|
|
{
|
|
SSMStatus rv;
|
|
SSMResource *res;
|
|
FilePathReply reply;
|
|
|
|
if (CMT_DecodeMessage(FilePathReplyTemplate, &reply, (CMTItem*)message) != CMTSuccess) {
|
|
return;
|
|
}
|
|
|
|
rv = SSMControlConnection_GetResource(ctrl, reply.resID, &res);
|
|
if (rv != PR_SUCCESS || res == NULL) {
|
|
return;
|
|
}
|
|
if (reply.filePath != NULL) {
|
|
if (reply.filePath[0] == '\0') {
|
|
res->m_fileName = NULL;
|
|
} else {
|
|
res->m_fileName = PL_strdup(reply.filePath);
|
|
}
|
|
PR_Free(reply.filePath);
|
|
} else {
|
|
res->m_fileName = NULL;
|
|
res->m_buttonType = SSM_BUTTON_CANCEL;
|
|
}
|
|
SSM_LockResource(res);
|
|
SSM_NotifyResource(res);
|
|
SSM_UnlockResource(res);
|
|
SSM_FreeResource(res);
|
|
}
|
|
|
|
void SSM_HandleUserPromptReply(SSMControlConnection *ctrl,
|
|
SECItem *msg)
|
|
{
|
|
SSMStatus rv;
|
|
SSMResource *res;
|
|
PromptReply reply;
|
|
|
|
if (CMT_DecodeMessage(PromptReplyTemplate, &reply, (CMTItem*)msg) != CMTSuccess) {
|
|
return;
|
|
}
|
|
|
|
rv = SSMControlConnection_GetResource(ctrl, reply.resID, &res);
|
|
if (rv != PR_SUCCESS || res == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* XXX sjlee: if the password length was zero, it would have been
|
|
* translated as a null pointer through transport. We need to restore it
|
|
* to an empty string. We can do that by looking up the cancel value of
|
|
* the reply.
|
|
*/
|
|
if ((reply.promptReply == NULL) && !reply.cancel) {
|
|
reply.promptReply = "";
|
|
}
|
|
SSM_HandlePromptReply(res, reply.promptReply);
|
|
}
|
|
|
|
#ifdef TIMEBOMB
|
|
|
|
SECItem * SSMTimebomb_GetMessage(SSMControlConnection * control)
|
|
{
|
|
SECItem * message = NULL;
|
|
PRInt32 read, type, len;
|
|
char * buffer = NULL;
|
|
SSMStatus rv = PR_FAILURE;
|
|
|
|
buffer = (char *)PORT_ZAlloc(sizeof(type)+ sizeof(len));
|
|
if (!buffer)
|
|
goto loser;
|
|
|
|
SSM_DEBUG("waiting for new message from socket.\n");
|
|
read = SSM_ReadThisMany(control->m_socket,(void *)buffer,sizeof(type)+sizeof(len));
|
|
if (read != sizeof(type)+sizeof(len))
|
|
{
|
|
SSM_DEBUG("Bytes read (%ld) != bytes expected (%ld). (hdr)\n",
|
|
(long) read,
|
|
(long) (sizeof(type)+sizeof(len)));
|
|
rv = PR_FAILURE;
|
|
goto loser;
|
|
}
|
|
message = SSM_ConstructMessage(PR_ntohl(((unsigned long *)buffer)[1]));
|
|
if (!message || !message->data)
|
|
{
|
|
SSM_DEBUG("Missing %s.\n",(!message) ? "message" : "message data");
|
|
rv = PR_OUT_OF_MEMORY_ERROR;
|
|
goto loser;
|
|
}
|
|
message->type = PR_ntohl(((unsigned long *)buffer)[0]);
|
|
SSM_DEBUG("reading %ld more from socket.\n", message->len);
|
|
read = SSM_ReadThisMany(control->m_socket, (void *)message->data, message->len);
|
|
if ((unsigned int) read != message->len)
|
|
{
|
|
SSM_DEBUG("Bytes read (%ld) != bytes expected (%ld). (msg)\n",
|
|
(long) read,
|
|
(long) message->len);
|
|
rv = PR_GetError();
|
|
if (rv == PR_SUCCESS) rv = PR_FAILURE;
|
|
goto loser;
|
|
}
|
|
if (buffer)
|
|
PR_Free(buffer);
|
|
return message;
|
|
loser:
|
|
if (buffer)
|
|
PR_Free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
SSMStatus SSMTimebomb_SendMessage(SSMControlConnection * control,
|
|
SECItem * message)
|
|
{
|
|
PRInt32 tmp, sent;
|
|
|
|
if (!message)
|
|
goto loser;
|
|
/* now send message.
|
|
* I want to do it here to keep timebomb code close together -jane */
|
|
tmp = PR_htonl(message->type);
|
|
sent = SSM_WriteThisMany(control->m_socket, &tmp, sizeof(tmp));
|
|
if (sent != sizeof(tmp))
|
|
goto loser;
|
|
tmp = PR_htonl(message->len);
|
|
sent = SSM_WriteThisMany(control->m_socket, &tmp, sizeof(tmp));
|
|
if (sent != sizeof(tmp))
|
|
goto loser;
|
|
sent = SSM_WriteThisMany(control->m_socket, message->data, message->len);
|
|
if (sent != message->len)
|
|
goto loser;
|
|
return SSM_SUCCESS;
|
|
loser:
|
|
SSM_DEBUG("timebomb: can't send error message!\n");
|
|
return SSM_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
void SSM_DestroyAttrValue(SSMAttributeValue *value, PRBool freeit)
|
|
{
|
|
if ((value->type == SSM_STRING_ATTRIBUTE) && (value->u.string.data))
|
|
PR_Free(value->u.string.data);
|
|
value->type = (SSMResourceAttrType) 0;
|
|
if (freeit)
|
|
PR_Free(value);
|
|
}
|
|
|
|
CMTStatus SSM_SSMStringToString(char ** string,
|
|
int *len,
|
|
SSMString * ssmString)
|
|
{
|
|
char * str = NULL;
|
|
int realLen;
|
|
CMTStatus rv = CMTSuccess;
|
|
|
|
if (!ssmString || !string ) {
|
|
rv = (CMTStatus) PR_INVALID_ARGUMENT_ERROR;
|
|
goto loser;
|
|
}
|
|
|
|
/* in case we fail */
|
|
*string = NULL;
|
|
if (len) *len = 0;
|
|
|
|
/* Convert from net byte order */
|
|
realLen = SSMPR_ntohl(ssmString->m_length);
|
|
|
|
str = (char *)PR_CALLOC(realLen+1); /* add 1 byte for end 0 */
|
|
if (!str) {
|
|
rv = (CMTStatus) PR_OUT_OF_MEMORY_ERROR;
|
|
goto loser;
|
|
}
|
|
|
|
memcpy(str, (char *) &(ssmString->m_data), realLen);
|
|
/* str[realLen]=0; */
|
|
|
|
if (len) *len = realLen;
|
|
*string = str;
|
|
return rv;
|
|
|
|
loser:
|
|
if (str)
|
|
PR_Free(str);
|
|
if (string && *string) {
|
|
PR_Free(*string);
|
|
*string = NULL;
|
|
}
|
|
if (rv == CMTSuccess)
|
|
rv = CMTFailure;
|
|
return rv;
|
|
}
|
|
|
|
|
|
CMTStatus SSM_StringToSSMString(SSMString ** ssmString, int length,
|
|
char * string)
|
|
{
|
|
SSMPRUint32 len;
|
|
SSMString *result = NULL;
|
|
CMTStatus rv = CMTSuccess;
|
|
|
|
if (!string || !ssmString) {
|
|
rv = (CMTStatus) PR_INVALID_ARGUMENT_ERROR;
|
|
goto loser;
|
|
}
|
|
|
|
*ssmString = NULL; /* in case we fail */
|
|
|
|
if (length) len = length;
|
|
else len = strlen(string);
|
|
if (len <= 0) {
|
|
rv = (CMTStatus) PR_INVALID_ARGUMENT_ERROR;
|
|
goto loser;
|
|
}
|
|
result = (SSMString *) PR_CALLOC(sizeof(PRUint32) +
|
|
SSMSTRING_PADDED_LENGTH(len));
|
|
if (!result) {
|
|
rv = (CMTStatus) PR_OUT_OF_MEMORY_ERROR;
|
|
goto loser;
|
|
}
|
|
|
|
result->m_length = SSMPR_htonl(len);
|
|
memcpy((char *) (&(result->m_data)), string, len);
|
|
|
|
*ssmString = result;
|
|
goto done;
|
|
|
|
loser:
|
|
if (result)
|
|
PR_Free(result);
|
|
*ssmString = NULL;
|
|
if (rv == CMTSuccess)
|
|
rv = CMTFailure;
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
#ifdef XP_UNIX
|
|
void SSM_ReleaseLockFile()
|
|
{
|
|
if (lockfile) {
|
|
char filePath[64];
|
|
|
|
GET_LOCK_FILE_PATH(filePath);
|
|
SSM_DEBUG("Releasing and deleting the file %s\n", filePath);
|
|
PR_UnlockFile(lockfile);
|
|
PR_Close(lockfile);
|
|
lockfile = NULL;
|
|
PR_Delete(filePath);
|
|
GET_CONTROL_SOCK_PATH(filePath);
|
|
SSM_DEBUG("Deleting %s. (The control connection socket)\n", filePath);
|
|
PR_Delete(filePath);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int SSM_strncasecmp(const char *s1, const char *s2, size_t count)
|
|
{
|
|
char *myS1 = NULL, *myS2 = NULL;
|
|
int rv, len, i;
|
|
|
|
myS1 = strdup(s1);
|
|
myS2 = strdup(s2);
|
|
len = strlen(myS1);
|
|
for (i=0;i<len;i++) {
|
|
myS1[i] = tolower(myS1[i]);
|
|
}
|
|
len = strlen(myS2);
|
|
for (i=0;i<len;i++) {
|
|
myS2[i] = tolower(myS2[i]);
|
|
}
|
|
rv = strncmp(myS1,myS2,count);
|
|
free(myS1);
|
|
free(myS2);
|
|
return rv;
|
|
}
|