/* * tclsample.c -- * * This file implements a Tcl interface to the secure hashing * algorithm functions in sha1.c * * Copyright (c) 1999 Scriptics Corporation. * Copyright (c) 2003 ActiveState Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ /* * Modified from tclmd5.c by Dave Dykstra, [email protected], 4/22/97 */ #include <tcl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "sample.h" #include "sampleUuid.h" #define TCL_READ_CHUNK_SIZE 4096 static const unsigned char itoa64f[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,"; static int numcontexts = 0; typedef struct { SHA1_CTX ctx; Tcl_Size totalRead; } sha1CmdContext; static sha1CmdContext *sha1CmdContexts = NULL; static int Sha1_Cmd(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); #define DIGESTSIZE 20 /* *---------------------------------------------------------------------- * * Sha1 -- * * Implements the new Tcl "sha1" command. * * Results: * A standard Tcl result * * Side effects: * None. * *---------------------------------------------------------------------- */ static int Sha1_Cmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter */ int objc, /* Number of arguments */ Tcl_Obj *const objv[] /* Argument strings */ ) { /* * The default base is hex */ int log2base = 4; int a; Tcl_Obj *stringObj = NULL; Tcl_Channel chan = NULL; Tcl_Channel copychan = NULL; int mode; int contextnum = 0; #define sha1Context (sha1CmdContexts[contextnum].ctx) char *bufPtr; Tcl_WideInt maxbytes = 0; int doinit = 1; int dofinal = 1; Tcl_Obj *descriptorObj = NULL; Tcl_Size totalRead = 0, n; int i, j, mask, bits, offset; (void)dummy; /* * For binary representation + null char */ char buf[129]; unsigned char digest[DIGESTSIZE]; static const char *const options[] = { "-chan", "-copychan", "-final", "-init", "-log2base", "-maxbytes", "-string", "-update", NULL }; enum ShaOpts { SHAOPT_CHAN, SHAOPT_COPY, SHAOPT_FINAL, SHAOPT_INIT, SHAOPT_LOG, SHAOPT_MAXB, SHAOPT_STRING, SHAOPT_UPDATE }; for (a = 1; a < objc; a++) { int index; if (Tcl_GetIndexFromObjStruct(interp, objv[a], options, sizeof(char *), "option", 0, &index) != TCL_OK) { return TCL_ERROR; } /* * Everything except -init takes an argument... */ if ((index != SHAOPT_INIT) && (++a >= objc)) { goto wrongArgs; } switch ((enum ShaOpts) index) { case SHAOPT_INIT: for (contextnum = 1; contextnum < numcontexts; contextnum++) { if (sha1CmdContexts[contextnum].totalRead == -1) { break; } } if (contextnum == numcontexts) { /* * Allocate a new context. */ numcontexts++; sha1CmdContexts = (sha1CmdContext *)ckrealloc((void *)sha1CmdContexts, numcontexts * sizeof(sha1CmdContext)); } sha1CmdContexts[contextnum].totalRead = 0; SHA1Init(&sha1Context); snprintf(buf, sizeof(buf), "sha1%d", contextnum); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; case SHAOPT_CHAN: chan = Tcl_GetChannel(interp, Tcl_GetString(objv[a]), &mode); if (chan == NULL) { return TCL_ERROR; } if ((mode & TCL_READABLE) == 0) { Tcl_AppendResult(interp, "chan \"", Tcl_GetString(objv[a]), "\" wasn't opened for reading", (char *)NULL); return TCL_ERROR; } continue; case SHAOPT_COPY: copychan = Tcl_GetChannel(interp, Tcl_GetString(objv[a]), &mode); if (copychan == NULL) { return TCL_ERROR; } if ((mode & TCL_WRITABLE) == 0) { Tcl_AppendResult(interp, "copychan \"", Tcl_GetString(objv[a]), "\" wasn't opened for writing", (char *)NULL); return TCL_ERROR; } continue; case SHAOPT_FINAL: descriptorObj = objv[a]; doinit = 0; continue; case SHAOPT_LOG: if (Tcl_GetIntFromObj(interp, objv[a], &log2base) != TCL_OK) { return TCL_ERROR; } else if ((log2base < 1) || (log2base > 6)) { Tcl_AppendResult(interp, "parameter to -log2base \"", Tcl_GetString(objv[a]), "\" must be integer in range 1...6", (char *)NULL); return TCL_ERROR; } continue; case SHAOPT_MAXB: if (Tcl_GetWideIntFromObj(interp, objv[a], &maxbytes) != TCL_OK) { return TCL_ERROR; } continue; case SHAOPT_STRING: stringObj = objv[a]; continue; case SHAOPT_UPDATE: descriptorObj = objv[a]; doinit = 0; dofinal = 0; continue; } } if (descriptorObj != NULL) { if ((sscanf(Tcl_GetString(descriptorObj), "sha1%d", &contextnum) != 1) || (contextnum >= numcontexts) || (sha1CmdContexts[contextnum].totalRead == -1)) { Tcl_AppendResult(interp, "invalid sha1 descriptor \"", Tcl_GetString(descriptorObj), "\"", (char *)NULL); return TCL_ERROR; } } if (doinit) { SHA1Init(&sha1Context); } if (stringObj != NULL) { char *string; if (chan != NULL) { goto wrongArgs; } string = Tcl_GetStringFromObj(stringObj, &totalRead); SHA1Update(&sha1Context, (unsigned char *) string, totalRead); } else if (chan != NULL) { bufPtr = (char *)ckalloc(TCL_READ_CHUNK_SIZE); totalRead = 0; while ((n = Tcl_Read(chan, bufPtr, maxbytes == 0 ? TCL_READ_CHUNK_SIZE : (TCL_READ_CHUNK_SIZE < maxbytes ? TCL_READ_CHUNK_SIZE : maxbytes))) != 0) { if (n == -1) { ckfree(bufPtr); Tcl_AppendResult(interp, Tcl_GetString(objv[0]), ": ", Tcl_GetChannelName(chan), Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } totalRead += n; SHA1Update(&sha1Context, (unsigned char *)bufPtr, n); if (copychan != NULL) { n = Tcl_Write(copychan, bufPtr, n); if (n == -1) { ckfree(bufPtr); Tcl_AppendResult(interp, Tcl_GetString(objv[0]), ": ", Tcl_GetChannelName(copychan), Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } } if ((maxbytes > 0) && (maxbytes <= n)) { break; } maxbytes -= n; } ckfree(bufPtr); } else if (descriptorObj == NULL) { goto wrongArgs; } if (!dofinal) { sha1CmdContexts[contextnum].totalRead += totalRead; Tcl_SetObjResult(interp, Tcl_NewWideIntObj(totalRead)); return TCL_OK; } if (stringObj == NULL) { totalRead += sha1CmdContexts[contextnum].totalRead; Tcl_SetObjResult(interp, Tcl_NewWideIntObj(totalRead)); } SHA1Final(&sha1Context, digest); /* * Take the 20 byte array and print it in the requested base * e.g. log2base=1 => binary, log2base=4 => hex */ n = log2base; i = j = bits = 0; /* * if 160 bits doesn't divide exactly by n then the first character of * the output represents the residual bits. e.g for n=6 (base 64) the * first character can only take the values 0..f */ offset = (DIGESTSIZE * 8) % n; if (offset > 0) { offset = n - offset; } mask = (2 << (n-1)) - 1; while (1) { bits <<= n; if (offset <= n) { if (i == DIGESTSIZE) { break; } bits += (digest[i++] << (n - offset)); offset += 8; } offset -= n; buf[j++] = itoa64f[(bits>>8)&mask]; } buf[j++] = itoa64f[(bits>>8)&mask]; buf[j++] = '\0'; Tcl_AppendResult(interp, buf, (char *)NULL); if (contextnum > 0) { sha1CmdContexts[contextnum].totalRead = -1; } return TCL_OK; wrongArgs: Tcl_AppendResult(interp, "wrong # args: should be either:\n", " ", Tcl_GetString(objv[0]), " ?-log2base log2base? -string string\n", " or\n", " ", Tcl_GetString(objv[0]), " ?-log2base log2base? ?-copychan chanID? -chan chanID\n", " or\n", " ", Tcl_GetString(objv[0]), " -init (returns descriptor)\n", " ", Tcl_GetString(objv[0]), " -update descriptor ?-maxbytes n? ?-copychan chanID? -chan chanID\n", " (any number of -update calls, returns number of bytes read)\n", " ", Tcl_GetString(objv[0]), " ?-log2base log2base? -final descriptor\n", " The default log2base is 4 (hex)", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Sample_Init -- * * Initialize the new package. The string "Sample" in the * function name must match the PACKAGE declaration at the top of * configure.ac. * * Results: * A standard Tcl result * * Side effects: * The Sample package is created. * One new command "sha1" is added to the Tcl interpreter. * *---------------------------------------------------------------------- */ #ifndef STRINGIFY # define STRINGIFY(x) STRINGIFY1(x) # define STRINGIFY1(x) #x #endif #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ DLLEXPORT int Sample_Init( Tcl_Interp* interp) /* Tcl interpreter */ { Tcl_CmdInfo info; /* * Support any TCL version from 8.5.0 to 9.x.x. * The upper bound "10" is exclusive */ if (Tcl_InitStubs(interp, "8.5-10", 0) == NULL) { return TCL_ERROR; } if (Tcl_GetCommandInfo(interp, "::tcl::build-info", &info)) { Tcl_CreateObjCommand(interp, "::sample::build-info", info.objProc, (void *)( PACKAGE_VERSION "+" STRINGIFY(SAMPLE_VERSION_UUID) #if defined(__clang__) && defined(__clang_major__) ".clang-" STRINGIFY(__clang_major__) #if __clang_minor__ < 10 "0" #endif STRINGIFY(__clang_minor__) #endif #if defined(__cplusplus) && !defined(__OBJC__) ".cplusplus" #endif #ifndef NDEBUG ".debug" #endif #if !defined(__clang__) && !defined(__INTEL_COMPILER) && defined(__GNUC__) ".gcc-" STRINGIFY(__GNUC__) #if __GNUC_MINOR__ < 10 "0" #endif STRINGIFY(__GNUC_MINOR__) #endif #ifdef __INTEL_COMPILER ".icc-" STRINGIFY(__INTEL_COMPILER) #endif #ifdef TCL_MEM_DEBUG ".memdebug" #endif #if defined(_MSC_VER) ".msvc-" STRINGIFY(_MSC_VER) #endif #ifdef USE_NMAKE ".nmake" #endif #ifndef TCL_CFG_OPTIMIZED ".no-optimize" #endif #ifdef __OBJC__ ".objective-c" #if defined(__cplusplus) "plusplus" #endif #endif #ifdef TCL_CFG_PROFILED ".profile" #endif #ifdef PURIFY ".purify" #endif #ifdef STATIC_BUILD ".static" #endif ), NULL); } /* Provide the current package */ if (Tcl_PkgProvideEx(interp, PACKAGE_NAME, PACKAGE_VERSION, NULL) != TCL_OK) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "sha1", (Tcl_ObjCmdProc *)Sha1_Cmd, NULL, NULL); numcontexts = 1; sha1CmdContexts = (sha1CmdContext *)ckalloc(sizeof(sha1CmdContext)); sha1CmdContexts[0].totalRead = 0; return TCL_OK; } #ifdef __cplusplus } #endif /* __cplusplus */