diff --git a/CGI.c b/CGI.c index 1b852e5..2152521 100644 --- a/CGI.c +++ b/CGI.c @@ -2,14 +2,14 @@ char *CGIReadDocument(char *RetStr, STREAM *S) { -const char *ptr; + const char *ptr; -ptr=getenv("CONTENT_LENGTH"); -if (StrValid(ptr)) -{ - S->Size=atoi(ptr); -} + ptr=getenv("CONTENT_LENGTH"); + if (StrValid(ptr)) + { + S->Size=atoi(ptr); + } -RetStr=STREAMReadDocument(RetStr, S); -return(RetStr); + RetStr=STREAMReadDocument(RetStr, S); + return(RetStr); } diff --git a/CHANGELOG b/CHANGELOG index a550f99..ce6ecd2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,13 @@ +v5.24 (2024-08-20) + * Move namespace/container code to Containers.c and make general improvments + * Exit out of TerminalReadText if escape or ctrl-c entered + * Added 'l' option (list files on remote server) to SSH streams + * CHANGED ARGUMENTS TO SSHConnect AND SSHOpen + * ssh streams can now accept config options bind=
and config= + * Rotate logfiles even if not open by app + v5.23 (2024-07-26) - * Improve attribution of base64 and hashing functions + * Improve attribution of base64 and hashing functions * fix -net/nonet (flag meaning was inverted) * ProcessNoNewPrivs now handles 'already set' and is no longer static. * Call setsid before setting controlling tty in PtySpawnFunction diff --git a/ConnectionChain.c b/ConnectionChain.c index 2bbc1cc..8bb12eb 100644 --- a/ConnectionChain.c +++ b/ConnectionChain.c @@ -489,7 +489,7 @@ static STREAM *ConnectHopSSHSpawnHelper(const char *ProxyURL, const char *Fmt, c LocalPort=(rand() % (0xFFFF - 9000)) +9000; if (strncmp(Fmt,"stdin:",6)==0) Tempstr=FormatStr(Tempstr, Fmt, RemoteHost, RemotePort); else Tempstr=FormatStr(Tempstr, Fmt, LocalPort, RemoteHost, RemotePort); - tmpS=SSHConnect(SshHost, SshPort, SshUser, SshPassword, Tempstr, 0); + tmpS=SSHConnect(SshHost, SshPort, SshUser, SshPassword, Tempstr, ""); if (tmpS) { Tempstr=FormatStr(Tempstr, "%d", LocalPort); diff --git a/Container.c b/Container.c new file mode 100644 index 0000000..ec6851f --- /dev/null +++ b/Container.c @@ -0,0 +1,470 @@ +#include "Container.h" +#include "libUseful.h" +#include + +#ifdef HAVE_UNSHARE + #define _GNU_SOURCE + #include +#endif + +static void InitSigHandler(int sig) +{ +} + + +static void ContainerInitProcess(int tunfd, int linkfd, pid_t Child, int RemoveRootDir) +{ + struct sigaction sa; + + //this process is init, the child will carry on execution + //if (chroot(".") == -1) RaiseError(ERRFLAG_ERRNO, "chroot", "failed to chroot to curr directory"); + ProcessSetTitle("init"); + + memset(&sa,0,sizeof(sa)); + sa.sa_handler=InitSigHandler; + sa.sa_flags=SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa,NULL); + + + /* + FileSystemUnMount("/proc","rmdir"); + if (RemoveRootDir) FileSystemUnMount("/","recurse,rmdir"); + else + { + FileSystemUnMount("/","subdirs,rmdir"); + FileSystemUnMount("/","recurse"); + } + */ + + //must do proc after the fork so that CLONE_NEWPID takes effect + mkdir("/proc",0755); + FileSystemMount("","/proc","proc",""); + + while (waitpid(-1,NULL,0) != -1); +} + + +static int ContainerJoinNamespace(const char *Namespace, int type) +{ + char *Tempstr=NULL; + struct stat Stat; + glob_t Glob; + int i, fd, result=FALSE; + +#ifdef HAVE_UNSHARE +#ifdef HAVE_SETNS + stat(Namespace,&Stat); + if (S_ISDIR(Stat.st_mode)) + { + Tempstr=MCopyStr(Tempstr,Namespace,"/*",NULL); + glob(Tempstr,0,0,&Glob); + if (Glob.gl_pathc ==0) RaiseError(ERRFLAG_ERRNO, "namespaces", "namespace dir %s empty", Tempstr); + for (i=0; i < Glob.gl_pathc; i++) + { + fd=open(Glob.gl_pathv[i],O_RDONLY); + if (fd > -1) + { + result=TRUE; + setns(fd, type); + close(fd); + } + else RaiseError(ERRFLAG_ERRNO, "namespaces", "couldn't open namespace %s", Glob.gl_pathv[i]); + } + } + else + { + fd=open(Namespace,O_RDONLY); + if (fd > -1) + { + result=TRUE; + setns(fd, type); + close(fd); + } + else RaiseError(ERRFLAG_ERRNO, "namespaces", "couldn't open namespace %s", Namespace); + } +#else + RaiseError(0, "namespaces", "setns unavailable"); +#endif + RaiseError(0, "namespaces", "setns unavailable"); +#endif + + Destroy(Tempstr); + return(result); +} + + + +static void ContainerFilesys(const char *Config, const char *Dir, int Flags) +{ + pid_t pid; + char *Tempstr=NULL, *Name=NULL, *Value=NULL; + char *ROMounts=NULL, *RWMounts=NULL; + char *Links=NULL, *PLinks=NULL, *FileClones=NULL; + const char *ptr, *tptr; + struct stat Stat; + + + ptr=GetNameValuePair(Config,"\\S","=",&Name,&Value); + while (ptr) + { + if (strcasecmp(Name,"+mnt")==0) ROMounts=MCatStr(ROMounts,",",Value,NULL); + else if (strcasecmp(Name,"+mnt")==0) ROMounts=MCatStr(ROMounts,",",Value,NULL); + else if (strcasecmp(Name,"mnt")==0) ROMounts=CopyStr(ROMounts,Value); + else if (strcasecmp(Name,"+wmnt")==0) RWMounts=MCatStr(RWMounts,",",Value,NULL); + else if (strcasecmp(Name,"wmnt")==0) RWMounts=CopyStr(RWMounts,Value); + else if (strcasecmp(Name,"+link")==0) Links=MCatStr(Links,",",Value,NULL); + else if (strcasecmp(Name,"link")==0) Links=CopyStr(Links,Value); + else if (strcasecmp(Name,"+plink")==0) PLinks=MCatStr(PLinks,",",Value,NULL); + else if (strcasecmp(Name,"plink")==0) PLinks=CopyStr(PLinks,Value); + else if (strcasecmp(Name,"pclone")==0) FileClones=CopyStr(FileClones,Value); + ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); + } + + pid=getpid(); + + if (StrValid(Dir)) Tempstr=FormatStr(Tempstr,Dir,pid); + else Tempstr=FormatStr(Tempstr,"%d.container",pid); + + mkdir(Tempstr,0755); + if (Flags & PROC_ISOCUBE) FileSystemMount("",Tempstr,"tmpfs",""); + if (chdir(Tempstr) !=0) RaiseError(ERRFLAG_ERRNO, "ContainerFilesys", "failed to chdir to %s", Tempstr); + + //always make a tmp directory + mkdir("tmp",0777); + + ptr=GetToken(ROMounts,",",&Value,GETTOKEN_QUOTES); + while (ptr) + { + FileSystemMount(Value,"","bind","ro perms=755"); + ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); + } + + ptr=GetToken(RWMounts,",",&Value,GETTOKEN_QUOTES); + while (ptr) + { + FileSystemMount(Value,"","bind","perms=777"); + ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); + } + + ptr=GetToken(Links,",",&Value,GETTOKEN_QUOTES); + while (ptr) + { + if (link(Value,GetBasename(Value)) !=0) + ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); + } + + ptr=GetToken(PLinks,",",&Value,GETTOKEN_QUOTES); + while (ptr) + { + tptr=Value; + if (*tptr=='/') tptr++; + MakeDirPath(tptr,0755); + if (link(Value, tptr) != 0) RaiseError(ERRFLAG_ERRNO, "ContainerFilesys", "Failed to link Value tptr."); + ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); + } + + ptr=GetToken(FileClones,",",&Value,GETTOKEN_QUOTES); + while (ptr) + { + tptr=Value; + if (*tptr=='/') tptr++; + MakeDirPath(tptr,0755); + stat(Value, &Stat); + if (S_ISCHR(Stat.st_mode) || S_ISBLK(Stat.st_mode)) mknod(tptr, Stat.st_mode, Stat.st_rdev); + else + { + FileCopy(Value, tptr); + chmod(tptr, Stat.st_mode); + } + ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); + } + + + Destroy(Name); + Destroy(Value); + Destroy(Tempstr); + Destroy(ROMounts); + Destroy(RWMounts); + Destroy(Links); + Destroy(PLinks); + Destroy(FileClones); +} + + +static void ContainerNamespace(const char *Namespace, const char *HostName, int Flags) +{ + int val, result; + +#ifdef HAVE_UNSHARE + +#ifdef CLONE_NEWNET + if (StrValid(Namespace)) ContainerJoinNamespace(Namespace, CLONE_NEWNET); + else if (Flags & PROC_CONTAINER_NET) unshare(CLONE_NEWNET); +#endif + + if (Flags & PROC_CONTAINER_FS) + { + //do these all individually because any one of them might be rejected +#ifdef CLONE_NEWIPC +// if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWIPC); +// else unshare(CLONE_NEWIPC); +#endif + +#ifdef CLONE_FS + if (StrValid(Namespace)) ContainerJoinNamespace(Namespace, CLONE_FS); + else unshare(CLONE_FS); +#endif + +#ifdef CLONE_NEWNS + if (StrValid(Namespace)) ContainerJoinNamespace(Namespace, CLONE_NEWNS); + else unshare(CLONE_NEWNS); +#endif + } + +#else + RaiseError(0, "namespaces", "containers/unshare unavailable"); +#endif +} + + +static void ContainerSetHostname(const char *Namespace, const char *HostName) +{ +int val, result; + +#ifdef HAVE_UNSHARE +#ifdef CLONE_NEWUTS + if (StrValid(Namespace)) ContainerJoinNamespace(Namespace, CLONE_NEWUTS); + else + { + unshare(CLONE_NEWUTS); + val=StrLen(HostName); + if (val != 0) result=sethostname(HostName, val); + else result=sethostname("container", 9); + if (result != 0) RaiseError(ERRFLAG_ERRNO, "ContainerNamespace", "Failed to sethostname for container."); + } +#endif +#else + RaiseError(0, "namespaces", "containers/unshare unavailable"); +#endif +} + + + +static void ContainerSetEnvs(const char *Envs) +{ + char *Name=NULL, *Value=NULL; + const char *ptr; + +#ifdef HAVE_CLEARENV + clearenv(); +#endif + + setenv("LD_LIBRARY_PATH","/lib:/usr/lib",TRUE); + + ptr=GetNameValuePair(Envs, ",","=", &Name, &Value); + while (ptr) + { + setenv(Name, Value, TRUE); + ptr=GetNameValuePair(ptr, ",","=", &Name, &Value); + } + Destroy(Name); + Destroy(Value); +} + + +//if we are even unsharing our PIDS namespace, then we will need a +//new 'init' process to look after pids in the namespace +//(this is mostly just to reap exited/zombie processes) +static pid_t ContainerLaunchInit(int Flags, const char *Dir) +{ + pid_t child, parent; + + //as we are going to create an init for a namespace it needs to be session leader + //setsid(); + + //fork off a process that will be our 'init' process + child=fork(); + if (child == 0) + { + setsid(); + child=fork(); + if (child !=0) + { + if ((! (Flags & PROC_ISOCUBE)) && StrValid(Dir)) ContainerInitProcess(-1, -1, child, FALSE); + else ContainerInitProcess(-1, -1, child, TRUE); + //ContainerInitProcess should never return, but we'll have this here anyway + _exit(0); + } + } + else _exit(0); + + return(child); +} + + + + +static int ContainerUnsharePID(int Flags, const char *Namespace, const char *Dir) +{ + pid_t pid, init_pid=0; + +#ifdef HAVE_UNSHARE +#ifdef CLONE_NEWPID + + + // NEWPID requires NEWNS which creates a new mount namespace, because we need to remount /proc + // within the new PID container + if (StrValid(Namespace)) ContainerJoinNamespace(Namespace, CLONE_NEWPID | CLONE_NEWNS); + else unshare(CLONE_NEWPID | CLONE_NEWNS); + + //if we are given a namespace we assume there is already an init for it + //otherwise launch and init from the NEWPID process + if (! StrValid(Namespace)) + { + init_pid=ContainerLaunchInit(Flags, Dir); + setpgid(init_pid, init_pid); + } + + return(TRUE); + +#endif +#else + RaiseError(0, "namespaces", "containers/unshare unavailable"); +#endif + return(FALSE); +} + + +static int ContainerParseConfig(const char *Config, char **HostName, char **Dir, char **Namespace, char **ChRoot, char **Envs) +{ + char *Tempstr=NULL, *Name=NULL, *Value=NULL; + const char *ptr; + int Flags=0; + + ptr=GetNameValuePair(Config,"\\S","=",&Name,&Value); + while (ptr) + { + if (strcasecmp(Name,"hostname")==0) *HostName=CopyStr(*HostName, Value); + else if (strcasecmp(Name,"dir")==0) *Dir=CopyStr(*Dir, Value); + else if (strcasecmp(Name,"nonet")==0) Flags |= PROC_CONTAINER_NET; + else if (strcasecmp(Name,"-net")==0) Flags |= PROC_CONTAINER_NET; + else if (strcasecmp(Name,"+net")==0) Flags &= ~PROC_CONTAINER_NET; + else if (strcasecmp(Name,"-pid")==0) Flags |= PROC_CONTAINER_PID; + else if (strcasecmp(Name,"nopid")==0) Flags |= PROC_CONTAINER_PID; + //else if (strcasecmp(Name,"jailsetup")==0) SetupScript=CopyStr(SetupScript, Value); + else if ( + (strcasecmp(Name,"ns")==0) || + (strcasecmp(Name,"namespace")==0) + ) + { + *Namespace=CopyStr(*Namespace, Value); + Flags |= PROC_CONTAINER_FS; + } + else if (strcasecmp(Name,"container")==0) + { + if (StrValid(Value)) *ChRoot=CopyStr(*ChRoot, Value); + Flags |= PROC_CONTAINER_FS; + } + else if (strcasecmp(Name,"container-net")==0) + { + if (StrValid(Value)) *ChRoot=CopyStr(*ChRoot, Value); + Flags |= PROC_CONTAINER_FS | PROC_CONTAINER_NET; + } + else if (strcasecmp(Name,"isocube")==0) + { + if (StrValid(Value)) *ChRoot=CopyStr(*ChRoot, Value); + Flags |= PROC_ISOCUBE | PROC_CONTAINER_FS; + } + else if (strcasecmp(Name,"setenv")==0) + { + Tempstr=QuoteCharsInStr(Tempstr, Value, ","); + *Envs=MCatStr(*Envs, Tempstr, ",",NULL); + } + + ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); + } + + Destroy(Tempstr); + Destroy(Name); + Destroy(Value); + + return(Flags); +} + + +int ContainerApplyConfig(const char *Config) +{ + char *HostName=NULL, *SetupScript=NULL, *Namespace=NULL, *Envs=NULL; + char *Dir=NULL, *ChRoot=NULL; + char *Name=NULL, *Value=NULL; + char *Tempstr=NULL; + const char *ptr; + int Flags=0; + int result=TRUE; + pid_t child; + + + Flags=ContainerParseConfig(Config, &HostName, &Dir, &Namespace, &ChRoot, &Envs); + + if (Flags) + { + if (Flags & PROC_CONTAINER_FS) + { + if (! StrValid(ChRoot)) + { + ChRoot=CopyStr(ChRoot, Dir); + Dir=CopyStr(Dir,""); + } + ContainerFilesys(Config, ChRoot, Flags); + + //we do not call CredsStoreOnFork here becausee it's assumed that we want to take the creds store with us, as + //these forks are in order to change aspects of our program, rather than spawn a new process + + if (StrValid(SetupScript)) + { + if (system(SetupScript) < 1) RaiseError(ERRFLAG_ERRNO, "ContainerApplyConfig", "failed to exec %s", SetupScript); + } + } + + + if (Flags & PROC_CONTAINER_PID) ContainerUnsharePID(Flags, Namespace, Dir); + if (StrValid(HostName)) ContainerSetHostname(Namespace, HostName); + + //ContainerNamespace(Namespace, HostName, Flags); + + + ContainerSetEnvs(Envs); + + if (Flags & PROC_CONTAINER_FS) + { + if (chroot(".") == -1) + { + RaiseError(ERRFLAG_ERRNO, "ContainerApplyConfig", "failed to chroot to curr directory"); + result=FALSE; + } + } + + if (result) + { + LibUsefulSetupAtExit(); + LibUsefulFlags |= LU_CONTAINER; + + if (StrValid(Dir)) + { + if (chdir(Dir) !=0) RaiseError(ERRFLAG_ERRNO, "ContainerApplyConfig", "failed to chdir to %s", Dir); + } + } + } + + Destroy(Tempstr); + Destroy(SetupScript); + Destroy(HostName); + Destroy(Namespace); + Destroy(Name); + Destroy(Value); + Destroy(ChRoot); + Destroy(Dir); + + return(result); +} + diff --git a/Container.h b/Container.h new file mode 100644 index 0000000..396c8e9 --- /dev/null +++ b/Container.h @@ -0,0 +1,23 @@ +/* +Copyright (c) 2015 Colum Paget +* SPDX-License-Identifier: GPL-3.0 +*/ + + +#ifndef LIBUSEFUL_CONTAINER_H +#define LIBUSEFUL_CONTAINER_H + +#define _GNU_SOURCE +#include +#include + +//this module relates to namespaces/containers. Much of this is pretty linux specific, and would be called +//via 'ProcessApplyConfig' rather than calling this function directly. + +int ContainerApplyConfig(const char *Config); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/FileSystem.c b/FileSystem.c index a6f0f29..0016df0 100644 --- a/FileSystem.c +++ b/FileSystem.c @@ -135,7 +135,7 @@ int FileMoveToDir(const char *FilePath, const char *Dir) char *Tempstr=NULL; struct stat Stat; int result, size; - int RetVal=FALSE; + int RetVal=FALSE; Tempstr=MCopyStr(Tempstr, Dir, "/", GetBasename(FilePath), NULL); MakeDirPath(Tempstr, 0700); diff --git a/List.h b/List.h index 7d944f9..40997b6 100644 --- a/List.h +++ b/List.h @@ -8,11 +8,41 @@ Copyright (c) 2015 Colum Paget /* -This module provides double linked lists and 'maps'. Maps are hashed arrays of linked lists, so they're very good for storing large numbers of items that can be looked up by a key or name +This module provides double linked lists and 'maps'. Maps are hashed arrays of linked lists, so they're very good for storing large numbers of items that can be looked up by a key or name. + +Items are stored as (void *) pointers, and can be tagged with a name by using 'ListAddNamedItem'. + +When the list is destroyed by the 'ListDestroy' function, a Destructor function can be passed in as the second argument which is used to destroy items stored in the list. If the items in the list aren't unique copies, but are pointers to things that exist outside of the list, then pass NULL as the destructor. + +For example: + +ListNode *MyList, *Curr; + +MyList=ListCreate(); +ListAdd(MyList, CopyStr(NULL, "item 1")); +ListAdd(MyList, CopyStr(NULL, "item 2")); + +Curr=ListGetNext(MyList); +while (Curr) +{ +printf("%s\n", (const char *) Curr->Item); +Curr=ListGetNext(Curr); +} + +ListDestroy(MyList, Destroy); + + +Here we add two strings to the list, and use 'CopyStr' to make unique copies of them. These are then destroyed/freed using the standard 'Destroy' function, which is passed in as the second argument of 'ListDestroy'. + */ + + + + + // Functions passsed to 'ListCreate' or 'MapCreate' or set against a ListNode item #define LIST_FLAG_DELETE 1 //internally used flag #define LIST_FLAG_CASE 2 //when doing searches with 'ListFindNamedItem' etc, use case diff --git a/Log.c b/Log.c index 1fb85bf..457064c 100644 --- a/Log.c +++ b/Log.c @@ -169,12 +169,21 @@ STREAM *LogFileInternalDoRotate(TLogFile *LogFile) int i; if (! LogFile) return(NULL); - if (! LogFile->S) return(NULL); + + //we can rotate even if logfile is closed + //if (! LogFile->S) return(NULL); + + //don't attempt to rotate things that aren't files if (CompareStr(LogFile->Path,"SYSLOG")==0) return(LogFile->S); if (CompareStr(LogFile->Path,"STDOUT")==0) return(LogFile->S); if (CompareStr(LogFile->Path,"STDERR")==0) return(LogFile->S); + + //don't attempt to rotate if we are a child process forked off, + //otherwise we can get into chaos with processes not knowing which + //version of the file they should be logging to if (getpid() != ParentPID) return(LogFile->S); + //if we have a max size specified, then consider rotating if (LogFile->MaxSize > 0) { if (LogFile->S) fstat(LogFile->S->out_fd,&FStat); @@ -193,16 +202,17 @@ STREAM *LogFileInternalDoRotate(TLogFile *LogFile) if (LogFile->S) { - //turn off append only sw we can do reanmae + //turn off append only sw we can do rename if (LogFile->Flags & LOGFILE_HARDEN) STREAMSetFlags(LogFile->S, 0, STREAM_APPENDONLY); } if (PrevPath) rename(LogFile->Path,PrevPath); if (LogFile->S) { - //turn off append only sw we can do reanmae + //turn off append only sw we can do rename if (LogFile->Flags & LOGFILE_HARDEN) STREAMSetFlags(LogFile->S, STREAM_IMMUTABLE, 0); } + //close and reopen to get a fresh, empty logfile if (LogFile->S) STREAMClose(LogFile->S); LogFile->S=LogFileOpen(LogFile->Path, LogFile->Flags); } @@ -211,6 +221,7 @@ STREAM *LogFileInternalDoRotate(TLogFile *LogFile) DestroyString(PrevPath); DestroyString(Tempstr); DestroyString(Path); + return(LogFile->S); } diff --git a/Makefile b/Makefile index 29ed4f5..7e373e9 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -VERSION = 5.23 +VERSION = 5.24 MAJOR=5 LIBFILE=libUseful.so.$(VERSION) SONAME=libUseful.so.$(MAJOR) @@ -9,7 +9,7 @@ LIBS = -lz -lssl -lcrypto -lc -lc -lc -lc prefix=/usr/local sysconfdir=${prefix}/etc FLAGS=$(LDFLAGS) $(CPPFLAGS) $(CFLAGS) -fPIC -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DHAVE_LIBC=1 -DHAVE_GET_CURR_DIR=1 -DHAVE_PTSNAME_R=1 -DHAVE_CLEARENV=1 -DHAVE_SETRESUID=1 -DHAVE_INITGROUPS=1 -DHAVE_POLL=1 -DHAVE_MLOCK=1 -DHAVE_MLOCKALL=1 -DHAVE_MUNLOCKALL=1 -DHAVE_MADVISE=1 -DHAVE_MKOSTEMP=1 -DHAVE_MOUNT=1 -DHAVE_UMOUNT=1 -DHAVE_UMOUNT2=1 -DHAVE_GETENTROPY=1 -DHAVE_PRCTL=1 -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_SENDFILE=1 -DUSE_INET6=1 -DHAVE_LIBC=1 -DHAVE_XATTR=1 -DHAVE_LIBC=1 -DHAVE_UNSHARE=1 -DHAVE_LIBC=1 -DHAVE_SETNS=1 -DHAVE_LIBCRYPTO=1 -DHAVE_LIBSSL=1 -DHAVE_EVP_MD_CTX_NEW=1 -DHAVE_EVP_MD_CTX_FREE=1 -DHAVE_EVP_BF_CBC=1 -DHAVE_EVP_RC2_CBC=1 -DHAVE_EVP_RC4=1 -DHAVE_EVP_DES_CBC=1 -DHAVE_EVP_DESX_CBC=1 -DHAVE_EVP_CAST5_CBC=1 -DHAVE_EVP_AES_128_CBC=1 -DHAVE_EVP_AES_256_CBC=1 -DHAVE_X509_CHECK_HOST=1 -DHAVE_DECL_OPENSSL_ADD_ALL_ALGORITHMS=1 -DHAVE_OPENSSL_ADD_ALL_ALGORITHMS=1 -DHAVE_DECL_SSL_SET_TLSEXT_HOST_NAME=1 -DHAVE_SSL_SET_TLSEXT_HOST_NAME=1 -DHAVE_LIBZ=1 -DVERSION=\"$(VERSION)\" -DSYSCONFDIR=\"$(sysconfdir)\" -OBJ=StrLenCache.o String.o Array.o List.o IPAddress.o Socket.o Server.o UnixSocket.o Stream.o StreamAuth.o Errors.o Unicode.o TerminalKeys.o Terminal.o TerminalWidget.o TerminalMenu.o TerminalChoice.o TerminalBar.o TerminalProgress.o TerminalTheme.o FileSystem.o GeneralFunctions.o DataProcessing.o Pty.o Log.o HttpUtil.o HttpChunkedTransfer.o Http.o Gemini.o Smtp.o Inet.o Expect.o base32.o base64.o crc32.o md5c.o sha1.o sha2.o whirlpool.o jh_ref.o HashCRC32.o HashMD5.o HashSHA.o HashJH.o HashWhirlpool.o HashOpenSSL.o Hash.o HMAC.o Ssh.o Compression.o OAuth.o LibSettings.o Vars.o Time.o Markup.o SpawnPrograms.o Tokenizer.o StringList.o PatternMatch.o URL.o DataParser.o ConnectionChain.o OpenSSL.o Process.o Encodings.o RawData.o SecureMem.o CommandLineParser.o SysInfo.o Entropy.o Users.o UnitsOfMeasure.o HttpServer.o WebSocket.o ContentType.o PasswordFile.o OTP.o CGI.o +OBJ=StrLenCache.o String.o Array.o List.o IPAddress.o Socket.o Server.o UnixSocket.o Stream.o StreamAuth.o Errors.o Unicode.o TerminalKeys.o Terminal.o TerminalWidget.o TerminalMenu.o TerminalChoice.o TerminalBar.o TerminalProgress.o TerminalTheme.o FileSystem.o GeneralFunctions.o DataProcessing.o Pty.o Log.o HttpUtil.o HttpChunkedTransfer.o Http.o Gemini.o Smtp.o Inet.o Expect.o base32.o base64.o crc32.o md5c.o sha1.o sha2.o whirlpool.o jh_ref.o HashCRC32.o HashMD5.o HashSHA.o HashJH.o HashWhirlpool.o HashOpenSSL.o Hash.o HMAC.o Ssh.o Compression.o OAuth.o LibSettings.o Vars.o Time.o Markup.o SpawnPrograms.o Tokenizer.o StringList.o PatternMatch.o URL.o DataParser.o ConnectionChain.o OpenSSL.o Process.o Container.o Encodings.o RawData.o SecureMem.o CommandLineParser.o SysInfo.o Entropy.o Users.o UnitsOfMeasure.o HttpServer.o WebSocket.o ContentType.o PasswordFile.o OTP.o CGI.o all: $(OBJ) @@ -215,6 +215,9 @@ OpenSSL.o: OpenSSL.c OpenSSL.h Process.o: Process.c Process.h $(CC) $(FLAGS) -c Process.c +Container.o: Container.c Container.h + $(CC) $(FLAGS) -c Container.c + Vars.o: Vars.c Vars.h $(CC) $(FLAGS) -c Vars.c diff --git a/Makefile.in b/Makefile.in index 7802540..aa9d9a6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,5 +1,5 @@ CC = @CC@ -VERSION = 5.23 +VERSION = 5.24 MAJOR=5 LIBFILE=libUseful.so.$(VERSION) SONAME=libUseful.so.$(MAJOR) @@ -9,7 +9,7 @@ LIBS = @LIBS@ prefix=@prefix@ sysconfdir=@sysconfdir@ FLAGS=$(LDFLAGS) $(CPPFLAGS) $(CFLAGS) -fPIC @DEFS@ -DVERSION=\"$(VERSION)\" -DSYSCONFDIR=\"$(sysconfdir)\" -OBJ=StrLenCache.o String.o Array.o List.o IPAddress.o Socket.o Server.o UnixSocket.o Stream.o StreamAuth.o Errors.o Unicode.o TerminalKeys.o Terminal.o TerminalWidget.o TerminalMenu.o TerminalChoice.o TerminalBar.o TerminalProgress.o TerminalTheme.o FileSystem.o GeneralFunctions.o DataProcessing.o Pty.o Log.o HttpUtil.o HttpChunkedTransfer.o Http.o Gemini.o Smtp.o Inet.o Expect.o base32.o base64.o crc32.o md5c.o sha1.o sha2.o whirlpool.o jh_ref.o HashCRC32.o HashMD5.o HashSHA.o HashJH.o HashWhirlpool.o HashOpenSSL.o Hash.o HMAC.o Ssh.o Compression.o OAuth.o LibSettings.o Vars.o Time.o Markup.o SpawnPrograms.o Tokenizer.o StringList.o PatternMatch.o URL.o DataParser.o ConnectionChain.o OpenSSL.o Process.o Encodings.o RawData.o SecureMem.o CommandLineParser.o SysInfo.o Entropy.o Users.o UnitsOfMeasure.o HttpServer.o WebSocket.o ContentType.o PasswordFile.o OTP.o CGI.o +OBJ=StrLenCache.o String.o Array.o List.o IPAddress.o Socket.o Server.o UnixSocket.o Stream.o StreamAuth.o Errors.o Unicode.o TerminalKeys.o Terminal.o TerminalWidget.o TerminalMenu.o TerminalChoice.o TerminalBar.o TerminalProgress.o TerminalTheme.o FileSystem.o GeneralFunctions.o DataProcessing.o Pty.o Log.o HttpUtil.o HttpChunkedTransfer.o Http.o Gemini.o Smtp.o Inet.o Expect.o base32.o base64.o crc32.o md5c.o sha1.o sha2.o whirlpool.o jh_ref.o HashCRC32.o HashMD5.o HashSHA.o HashJH.o HashWhirlpool.o HashOpenSSL.o Hash.o HMAC.o Ssh.o Compression.o OAuth.o LibSettings.o Vars.o Time.o Markup.o SpawnPrograms.o Tokenizer.o StringList.o PatternMatch.o URL.o DataParser.o ConnectionChain.o OpenSSL.o Process.o Container.o Encodings.o RawData.o SecureMem.o CommandLineParser.o SysInfo.o Entropy.o Users.o UnitsOfMeasure.o HttpServer.o WebSocket.o ContentType.o PasswordFile.o OTP.o CGI.o all: $(OBJ) @@ -215,6 +215,9 @@ OpenSSL.o: OpenSSL.c OpenSSL.h Process.o: Process.c Process.h $(CC) $(FLAGS) -c Process.c +Container.o: Container.c Container.h + $(CC) $(FLAGS) -c Container.c + Vars.o: Vars.c Vars.h $(CC) $(FLAGS) -c Vars.c diff --git a/Process.c b/Process.c index 9f00f77..c96b5d9 100644 --- a/Process.c +++ b/Process.c @@ -1,8 +1,9 @@ #include "Process.h" +#include "Container.h" + #include "errno.h" #include "includes.h" #include -#include #include #include #include @@ -128,7 +129,7 @@ void ProcessSetControlTTY(int fd) { // TIOCSCTTY doesn't seem to exist under macosx! #ifdef TIOCSCTTY - ioctl(fd,TIOCSCTTY,0); + ioctl(fd,TIOCSCTTY,1); #endif } @@ -280,422 +281,8 @@ void LU_DefaultSignalHandler(int sig) -void InitSigHandler(int sig) -{ -} - - - - -void ProcessContainerInit(int tunfd, int linkfd, pid_t Child, int RemoveRootDir) -{ - ListNode *Connections=NULL; - STREAM *TunS=NULL, *LinkS=NULL, *S; - struct sigaction sa; - - /* //this feature not working yet - if ((linkfd > -1) && (tunfd > -1)) - { - Connections=ListCreate(); - LinkS=STREAMFromFD(linkfd); - STREAMSetFlushType(LinkS, FLUSH_ALWAYS, 0, 0); - if (LinkS) ListAddItem(Connections, LinkS); - - TunS=STREAMFromFD(tunfd); - STREAMSetFlushType(TunS, FLUSH_ALWAYS, 0, 0); - if (TunS) ListAddItem(Connections, TunS); - } - */ - - - //this process is init, the child will carry on executation - if (chroot(".") == -1) RaiseError(ERRFLAG_ERRNO, "chroot", "failed to chroot to curr directory"); - ProcessSetTitle("init"); - - memset(&sa,0,sizeof(sa)); - sa.sa_handler=InitSigHandler; - sa.sa_flags=SA_NOCLDSTOP; - sigaction(SIGCHLD, &sa,NULL); - while (Connections) - { - S=STREAMSelect(Connections, NULL); - if (S==TunS) STREAMSendFile(S, LinkS, BUFSIZ, SENDFILE_KERNEL); - else if (S==LinkS) STREAMSendFile(S, TunS, BUFSIZ, SENDFILE_KERNEL); - if (waitpid(-1, NULL,WNOHANG) == -1) break; - } - - while (waitpid(-1,NULL,0) != -1); - - FileSystemUnMount("/proc","rmdir"); - if (RemoveRootDir) FileSystemUnMount("/","recurse,rmdir"); - else - { - FileSystemUnMount("/","subdirs,rmdir"); - FileSystemUnMount("/","recurse"); - } - - STREAMClose(TunS); - STREAMClose(LinkS); - - _exit(0); -} - - -int JoinNamespace(const char *Namespace, int type) -{ - char *Tempstr=NULL; - struct stat Stat; - glob_t Glob; - int i, fd, result=FALSE; - -#ifdef HAVE_SETNS - stat(Namespace,&Stat); - if (S_ISDIR(Stat.st_mode)) - { - Tempstr=MCopyStr(Tempstr,Namespace,"/*",NULL); - glob(Tempstr,0,0,&Glob); - if (Glob.gl_pathc ==0) RaiseError(ERRFLAG_ERRNO, "namespaces", "namespace dir %s empty", Tempstr); - for (i=0; i < Glob.gl_pathc; i++) - { - fd=open(Glob.gl_pathv[i],O_RDONLY); - if (fd > -1) - { - result=TRUE; - setns(fd, type); - close(fd); - } - else RaiseError(ERRFLAG_ERRNO, "namespaces", "couldn't open namespace %s", Glob.gl_pathv[i]); - } - } - else - { - fd=open(Namespace,O_RDONLY); - if (fd > -1) - { - result=TRUE; - setns(fd, type); - close(fd); - } - else RaiseError(ERRFLAG_ERRNO, "namespaces", "couldn't open namespace %s", Namespace); - } -#else - RaiseError(0, "namespaces", "setns unavailable"); -#endif - - Destroy(Tempstr); - return(result); -} - - - -void ProcessContainerFilesys(const char *Config, const char *Dir, int Flags) -{ - pid_t pid; - char *Tempstr=NULL, *Name=NULL, *Value=NULL; - char *ROMounts=NULL, *RWMounts=NULL; - char *Links=NULL, *PLinks=NULL, *FileClones=NULL; - const char *ptr, *tptr; - struct stat Stat; - - - ptr=GetNameValuePair(Config,"\\S","=",&Name,&Value); - while (ptr) - { - if (strcasecmp(Name,"+mnt")==0) ROMounts=MCatStr(ROMounts,",",Value,NULL); - else if (strcasecmp(Name,"+mnt")==0) ROMounts=MCatStr(ROMounts,",",Value,NULL); - else if (strcasecmp(Name,"mnt")==0) ROMounts=CopyStr(ROMounts,Value); - else if (strcasecmp(Name,"+wmnt")==0) RWMounts=MCatStr(RWMounts,",",Value,NULL); - else if (strcasecmp(Name,"wmnt")==0) RWMounts=CopyStr(RWMounts,Value); - else if (strcasecmp(Name,"+link")==0) Links=MCatStr(Links,",",Value,NULL); - else if (strcasecmp(Name,"link")==0) Links=CopyStr(Links,Value); - else if (strcasecmp(Name,"+plink")==0) PLinks=MCatStr(PLinks,",",Value,NULL); - else if (strcasecmp(Name,"plink")==0) PLinks=CopyStr(PLinks,Value); - else if (strcasecmp(Name,"pclone")==0) FileClones=CopyStr(FileClones,Value); - ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); - } - - pid=getpid(); - - if (StrValid(Dir)) Tempstr=FormatStr(Tempstr,Dir,pid); - else Tempstr=FormatStr(Tempstr,"%d.container",pid); - - mkdir(Tempstr,0755); - if (Flags & PROC_ISOCUBE) FileSystemMount("",Tempstr,"tmpfs",""); - if (chdir(Tempstr) !=0) RaiseError(ERRFLAG_ERRNO, "ProcessContainerFilesys", "failed to chdir to %s", Tempstr); - - //always make a tmp directory - mkdir("tmp",0777); - - ptr=GetToken(ROMounts,",",&Value,GETTOKEN_QUOTES); - while (ptr) - { - FileSystemMount(Value,"","bind","ro perms=755"); - ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); - } - - ptr=GetToken(RWMounts,",",&Value,GETTOKEN_QUOTES); - while (ptr) - { - FileSystemMount(Value,"","bind","perms=777"); - ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); - } - - ptr=GetToken(Links,",",&Value,GETTOKEN_QUOTES); - while (ptr) - { - if (link(Value,GetBasename(Value)) !=0) - ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); - } - - ptr=GetToken(PLinks,",",&Value,GETTOKEN_QUOTES); - while (ptr) - { - tptr=Value; - if (*tptr=='/') tptr++; - MakeDirPath(tptr,0755); - if (link(Value, tptr) != 0) RaiseError(ERRFLAG_ERRNO, "ProcessContainerFilesys", "Failed to link Value tptr."); - ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); - } - - ptr=GetToken(FileClones,",",&Value,GETTOKEN_QUOTES); - while (ptr) - { - tptr=Value; - if (*tptr=='/') tptr++; - MakeDirPath(tptr,0755); - stat(Value, &Stat); - if (S_ISCHR(Stat.st_mode) || S_ISBLK(Stat.st_mode)) mknod(tptr, Stat.st_mode, Stat.st_rdev); - else - { - FileCopy(Value, tptr); - chmod(tptr, Stat.st_mode); - } - ptr=GetToken(ptr,",",&Value,GETTOKEN_QUOTES); - } - - - Destroy(Name); - Destroy(Value); - Destroy(Tempstr); - Destroy(ROMounts); - Destroy(RWMounts); - Destroy(Links); - Destroy(PLinks); - Destroy(FileClones); -} - - -void ProcessContainerNamespace(const char *Namespace, const char *HostName, int Flags) -{ - int val, result; - -#ifdef HAVE_UNSHARE - -#ifdef CLONE_NEWNET - if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWNET); - else if (Flags & PROC_CONTAINER_NET) unshare(CLONE_NEWNET); -#endif - - if (Flags & PROC_CONTAINER) - { - //do these all individually because any one of them might be rejected -#ifdef CLONE_NEWIPC -// if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWIPC); -// else unshare(CLONE_NEWIPC); -#endif - -#ifdef CLONE_NEWUTS - if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWUTS); - else - { - unshare(CLONE_NEWUTS); - val=StrLen(HostName); - if (val != 0) result=sethostname(HostName, val); - else result=sethostname("container", 9); - if (result != 0) RaiseError(ERRFLAG_ERRNO, "ProcessContainerNamespace", "Failed to sethostname for container."); - } -#endif - -#ifdef CLONE_FS - if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_FS); - else unshare(CLONE_FS); -#endif - -#ifdef CLONE_NEWNS - if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWNS); - else unshare(CLONE_NEWNS); -#endif - } - -#endif -} - - - -void ProcessContainerSetEnvs(const char *Envs) -{ - char *Name=NULL, *Value=NULL; - const char *ptr; - -#ifdef HAVE_CLEARENV - clearenv(); -#endif - - setenv("LD_LIBRARY_PATH","/lib:/usr/lib",TRUE); - - ptr=GetNameValuePair(Envs, ",","=", &Name, &Value); - while (ptr) - { - setenv(Name, Value, TRUE); - ptr=GetNameValuePair(ptr, ",","=", &Name, &Value); - } - Destroy(Name); - Destroy(Value); -} - - - -int ProcessContainer(const char *Config) -{ - char *HostName=NULL, *SetupScript=NULL, *Namespace=NULL, *Envs=NULL; - char *Dir=NULL, *ChRoot=NULL; - char *Name=NULL, *Value=NULL; - char *Tempstr=NULL; - const char *ptr; - int Flags=0; - int result=TRUE; - pid_t child; - - ptr=GetNameValuePair(Config,"\\S","=",&Name,&Value); - while (ptr) - { - if (strcasecmp(Name,"hostname")==0) HostName=CopyStr(HostName, Value); - else if (strcasecmp(Name,"dir")==0) Dir=CopyStr(Dir, Value); - else if (strcasecmp(Name,"nonet")==0) Flags |= PROC_CONTAINER_NET; - else if (strcasecmp(Name,"-net")==0) Flags |= PROC_CONTAINER_NET; - else if (strcasecmp(Name,"+net")==0) Flags &= ~PROC_CONTAINER_NET; - else if (strcasecmp(Name,"jailsetup")==0) SetupScript=CopyStr(SetupScript, Value); - else if ( - (strcasecmp(Name,"ns")==0) || - (strcasecmp(Name,"namespace")==0) - ) - { - Namespace=CopyStr(Namespace, Value); - Flags |= PROC_CONTAINER; - } - else if (strcasecmp(Name,"container")==0) - { - if (StrValid(Value)) ChRoot=CopyStr(ChRoot, Value); - Flags |= PROC_CONTAINER; - } - else if (strcasecmp(Name,"container-net")==0) - { - if (StrValid(Value)) ChRoot=CopyStr(ChRoot, Value); - Flags |= PROC_CONTAINER | PROC_CONTAINER_NET; - } - else if (strcasecmp(Name,"isocube")==0) - { - if (StrValid(Value)) ChRoot=CopyStr(ChRoot, Value); - Flags |= PROC_ISOCUBE | PROC_CONTAINER; - } - else if (strcasecmp(Name,"setenv")==0) - { - Tempstr=QuoteCharsInStr(Tempstr, Value, ","); - Envs=MCatStr(Envs, Tempstr, ",",NULL); - } - - - ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); - } - - - if (Flags & PROC_CONTAINER) - { -#ifdef HAVE_UNSHARE -#ifdef CLONE_NEWPID - if (StrValid(Namespace)) JoinNamespace(Namespace, CLONE_NEWPID); - else unshare(CLONE_NEWPID); -#endif -#endif - - if (! StrValid(ChRoot)) - { - ChRoot=CopyStr(ChRoot, Dir); - Dir=CopyStr(Dir,""); - } - ProcessContainerFilesys(Config, ChRoot, Flags); - - //fork again because CLONE_NEWPID only takes effect after another fork, and creates an 'init' process - child=fork(); - if (child==0) - { - //we do not call CredsStoreOnFork here becausee it's assumed that we want to take the creds store with us, as - //these forks are in order to change aspects of our program, rather than spawn a new process - - //must do proc after the fork so that CLONE_NEWPID takes effect - mkdir("proc",0755); - FileSystemMount("","proc","proc",""); - - if (StrValid(SetupScript)) - { - if (system(SetupScript) < 1) RaiseError(ERRFLAG_ERRNO, "ProcessContainer", "failed to exec %s", SetupScript); - } - - - ProcessContainerNamespace(Namespace, HostName, Flags); - - ProcessContainerSetEnvs(Envs); - //if we are given a namespace we assume there is already an init for it - if (! StrValid(Namespace)) - { - //as we are going to create an init for a namespace it needs to be session leader - setsid(); - - //fork again! Honestly. - child=fork(); - if (child !=0) - { - //ProcessContainerInit will never return, it will exit when finished - if ((! (Flags & PROC_ISOCUBE)) &&StrValid(Dir)) ProcessContainerInit(-1, -1, child, FALSE); - else ProcessContainerInit(-1, -1, child, TRUE); - } - } - - if (chroot(".") == -1) - { - RaiseError(ERRFLAG_ERRNO, "ProcessContainer", "failed to chroot to curr directory"); - result=FALSE; - } - if (result) - { - LibUsefulSetupAtExit(); - LibUsefulFlags |= LU_CONTAINER; - - if (StrValid(Dir)) - { - if (chdir(Dir) !=0) RaiseError(ERRFLAG_ERRNO, "ProcessContainer", "failed to chdir to %s", Dir); - } - } - } - //we no longer need the parent thread, as the child thread, now completely in the CLONE_NEWPID jail, is our new thread - else _exit(0); - } - else ProcessContainerNamespace(Namespace, HostName, Flags); - - Destroy(Tempstr); - Destroy(SetupScript); - Destroy(HostName); - Destroy(Namespace); - Destroy(Name); - Destroy(Value); - Destroy(ChRoot); - Destroy(Dir); - - return(result); -} - void ProcessSetRLimit(int Type, const char *Value) { struct rlimit limit; @@ -817,14 +404,15 @@ static int ProcessApplyEarlyConfig(const char *Config) else if (strcasecmp(Name,"trust")==0) Flags |= SPAWN_TRUST_COMMAND; else if (strcasecmp(Name,"noshell")==0) Flags |= SPAWN_NOSHELL; else if (strcasecmp(Name,"arg0")==0) Flags |= SPAWN_ARG0; -//container flags will be parsed again in ContainerInit, so we just se them all to 'PROC_CONTAINER' here - else if (strcasecmp(Name,"container")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"container+net")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"isocube")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"-net")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"nonet")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"ns")==0) Flags |= PROC_CONTAINER; - else if (strcasecmp(Name,"namespace")==0) Flags |= PROC_CONTAINER; + else if (strcasecmp(Name,"container")==0) Flags |= PROC_CONTAINER_FS; + else if (strcasecmp(Name,"container+net")==0) Flags |= PROC_CONTAINER_FS | PROC_CONTAINER_NET; + else if (strcasecmp(Name,"isocube")==0) Flags |= PROC_CONTAINER_FS; + else if (strcasecmp(Name,"-net")==0) Flags |= PROC_CONTAINER_NET; + else if (strcasecmp(Name,"nonet")==0) Flags |= PROC_CONTAINER_NET; + else if (strcasecmp(Name,"nopid")==0) Flags |= PROC_CONTAINER_PID; + else if (strcasecmp(Name,"-pid")==0) Flags |= PROC_CONTAINER_PID; + else if (strcasecmp(Name,"ns")==0) Flags |= PROC_CONTAINER_FS; + else if (strcasecmp(Name,"namespace")==0) Flags |= PROC_CONTAINER_FS; else if (strcasecmp(Name,"mlock")==0) ProcessMemLockAdd(); else if (strcasecmp(Name,"memlock")==0) ProcessMemLockAdd(); else if (strcasecmp(Name,"mem")==0) ProcessSetRLimit(RLIMIT_DATA, Value); @@ -847,9 +435,9 @@ static int ProcessApplyEarlyConfig(const char *Config) ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); } - if (LibUsefulFlags & LU_RESIST_PTRACE) + if (LibUsefulFlags & LU_RESIST_PTRACE) { - if (! ProcessResistPtrace()) Flags |= PROC_SETUP_FAIL; + if (! ProcessResistPtrace()) Flags |= PROC_SETUP_FAIL; } Destroy(Name); @@ -903,7 +491,6 @@ static int ProcessApplyLateConfig(int Flags, const char *Config) { ctty_fd=atoi(Value); Flags |= PROC_CTRL_TTY; - ProcessSetControlTTY(ctty_fd); } ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); @@ -911,9 +498,11 @@ static int ProcessApplyLateConfig(int Flags, const char *Config) if (Flags & PROC_CONTAINER) { - if (! ProcessContainer(Config)) Flags |= PROC_SETUP_FAIL; + if (! ContainerApplyConfig(Config)) Flags |= PROC_SETUP_FAIL; } + if (Flags & PROC_CTRL_TTY) ProcessSetControlTTY(ctty_fd); + //Always do group first, otherwise we'll lose ability to switch user/group if (gid > 0) SwitchGID(gid); @@ -936,6 +525,7 @@ static int ProcessApplyLateConfig(int Flags, const char *Config) } } + if (StrValid(Capabilities)) ProcessSetCapabilities(Capabilities); //if we set any capabilites, we will already have set 'NO_NEW_PRIVS' //so only consider the PROC_NO_NEW_PRIVS flag if we didn't use diff --git a/Process.h b/Process.h index b302944..2bcc8fb 100644 --- a/Process.h +++ b/Process.h @@ -13,26 +13,30 @@ Copyright (c) 2015 Colum Paget //Various functions related to a process -#define PROC_DAEMON 8 //make process a daemon -#define PROC_SETSID 16 //create a new session for this process -#define PROC_CTRL_TTY 32 -#define PROC_CHROOT 64 //chroot this process -#define PROC_JAIL 128 -#define PROC_SIGDEF 256 //set default signal mask for this process -#define PROC_CONTAINER 512 -#define PROC_CONTAINER_NET 1024 -#define PROC_ISOCUBE 2048 -#define PROC_NEWPGROUP 4096 //create new process group for this process +#define PROC_DAEMON 8 // make process a daemon +#define PROC_SETSID 16 // create a new session for this process +#define PROC_CTRL_TTY 32 // set stdin to be a controlling tty +#define PROC_CHROOT 64 // chroot this process before switching user etc. Used to chroot into a full unix system. +#define PROC_NEWPGROUP 128 // create new process group for this process +#define PROC_SIGDEF 256 // set default signal mask for this process +#define PROC_CONTAINER_FS 512 +#define PROC_CONTAINER_NET 1024 // unshare network namespace for this process +#define PROC_CONTAINER_PID 2048 // unshare pids namespace //these must be compatible with PROC_ defines -#define SPAWN_NOSHELL 8192 -#define SPAWN_TRUST_COMMAND 16384 -#define SPAWN_ARG0 32768 +#define SPAWN_NOSHELL 8192 // run the command directly using exec, not from a shell using system +#define SPAWN_TRUST_COMMAND 16384 // don't strip unsafe chars from command +#define SPAWN_ARG0 32768 // deduce arg[0] from the command name -#define PROC_SETUP_FAIL 65536 -#define PROC_SETUP_STRICT 131072 -#define PROC_NO_NEW_PRIVS 262144 //do not allow privilege escalation via setuid or other such methods +#define PROC_SETUP_FAIL 65536 // internal flag if anyting goes wrong, can trigger PROC_SETUP_STRICT +#define PROC_SETUP_STRICT 131072 // if anything goes wrong in ProcessApplyConfig, then abort program +#define PROC_NO_NEW_PRIVS 262144 // do not allow privilege escalation via setuid or other such methods +#define PROC_JAIL 524288 // chroot after everything setup. This jails a process in a directory, not the same as PROC_CHROOT +#define PROC_ISOCUBE 1048576 // chroot into a tmpfs filesystem. Any files process writes will be lost when it exits + + +#define PROC_CONTAINER (PROC_CONTAINER_FS | PROC_CONTAINER_NET | PROC_CONTAINER_PID) #ifdef __cplusplus extern "C" { @@ -45,7 +49,7 @@ void LU_DefaultSignalHandler(int sig); // Beware, this will change its pid. Returns the new pid or '0' on failure pid_t demonize(); -//singply try to close all file descriptors from 3 to 1024, leaving stdin, stdout and stderr alone +//try to close all file descriptors from 3 to 1024, leaving stdin, stdout and stderr alone void CloseOpenFiles(); //write a file containing the current pid. The file can either be an absolute path to a file anywhere diff --git a/SpawnPrograms.c b/SpawnPrograms.c index 91c8762..d689d83 100644 --- a/SpawnPrograms.c +++ b/SpawnPrograms.c @@ -7,6 +7,7 @@ #include "String.h" #include "Errors.h" #include "FileSystem.h" +#include "Container.h" #include #include @@ -106,6 +107,7 @@ pid_t xfork(const char *Config) if (StrValid(Config)) { //if any of the process configs we asked for failed, then quit +fprintf(stderr, "XFPAC: %d\n", getpid()); if (ProcessApplyConfig(Config) & PROC_SETUP_FAIL) _exit(1); } } @@ -231,6 +233,7 @@ pid_t PipeSpawnFunction(int *infd, int *outfd, int *errfd, BASIC_FUNC Func, void //if Func is NULL we effectively do a fork, rather than calling a function we just //continue exectution from where we were + Flags=ProcessApplyConfig(Config); if (Func) { @@ -283,11 +286,14 @@ pid_t PseudoTTYSpawnFunction(int *ret_pty, BASIC_FUNC Func, void *Data, int Flag if (PseudoTTYGrab(&pty, &tty, Flags)) { + //ContainerApplyConfig(Config); pid=xforkio(tty, tty, tty); if (pid==0) { close(pty); + ConfigFlags=ProcessApplyConfig(Config); + setsid(); ProcessSetControlTTY(tty); @@ -295,7 +301,6 @@ pid_t PseudoTTYSpawnFunction(int *ret_pty, BASIC_FUNC Func, void *Data, int Flag //as it will be open on stdin/stdout close(tty); - ConfigFlags=ProcessApplyConfig(Config); //if Func is NULL we effectively do a fork, rather than calling a function we just //continue exectution from where we were @@ -411,6 +416,7 @@ int STREAMSpawnCommandAndPty(const char *Command, const char *Config, STREAM **C if (PseudoTTYGrab(&pty, &tty, TTYFLAG_PTY)) { + //ContainerApplyConfig(Config); //handle situation where Config might be null if (StrValid(Config)) Tempstr=CopyStr(Tempstr, Config); else Tempstr=CopyStr(Tempstr, "rw"); diff --git a/SpawnPrograms.h b/SpawnPrograms.h index 0b97781..7cc7a32 100644 --- a/SpawnPrograms.h +++ b/SpawnPrograms.h @@ -24,7 +24,7 @@ lockstdin= create a lockfile and hook it to stdin. This is used with progr trust 'trust' command. without this option certain characters are stripped from any commands that are run with the 'SpawnCommand' functions, notably ';' and '|'. You cannot therefore concatanate commands without using this - option. + option. pty instead of using pipes to talk to the child process use a pseudo tty device. This creates a link where text sent to the command undergoes input processing as though it was from a keyboard diff --git a/Ssh.c b/Ssh.c index aeccd57..5dc8368 100644 --- a/Ssh.c +++ b/Ssh.c @@ -3,13 +3,90 @@ #include "Pty.h" #include "SpawnPrograms.h" -STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pass, const char *Command, int Flags) + +#define SSH_CANON_PTY 1 //communicate to ssh program over a canonical pty (honor ctrl-d, ctrl-c etc) +#define SSH_NO_ESCAPE 2 //disable the 'escape character' +#define SSH_COMPRESS 4 //use -C to compress ssh traffic + + + +static int SSHParseFlags(const char *Token) +{ + const char *ptr; + int Flags=0; + + for (ptr=Token; *ptr != '\0'; ptr++) + { + switch (*ptr) + { + case 'a': + Flags |= STREAM_APPEND; + case 'r': + Flags |= SF_RDONLY; + case 'w': + Flags |= SF_WRONLY; + case 'l': + Flags |= SF_LIST; + } + } + + return(Flags); +} + + +static int SSHParseConfig(const char *Config, char **BindAddress, char **ConfigFile) +{ + char *Name=NULL, *Value=NULL; + const char *ptr; + int Flags=0; + + ptr=GetToken(Config, "\\S", &Value, 0); + + //if first item is a name=value, then there's no flags, so all of Config is name=value pairs + if (! strchr(Value, '=')) Flags=SSHParseFlags(Value); + else ptr=Config; + + + ptr=GetNameValuePair(ptr, "\\S", "=", &Name, &Value); + while (ptr) + { + //we could add options here like 'bind address' + //or specify encryption/key algorithms, but + //unfortunately that will mean rethinking SSHConnect + //as it has no way of passing such arguments at current + + + if ( (strcasecmp(Name, "bind")==0) && BindAddress) *BindAddress=CopyStr(*BindAddress, Value); + if ( (strcasecmp(Name, "config")==0) && ConfigFile) *ConfigFile=CopyStr(*ConfigFile, Value); + ptr=GetNameValuePair(ptr, "\\S", "=", &Name, &Value); + } + + Destroy(Name); + Destroy(Value); + + return(Flags); +} + + +STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pass, const char *Command, const char *Config) { ListNode *Dialog; - char *Tempstr=NULL, *KeyFile=NULL, *Token=NULL, *RemoteCmd=NULL, *TTYConfigs=NULL; + char *Tempstr=NULL, *KeyFile=NULL, *Token=NULL, *RemoteCmd=NULL, *TTYConfigs=NULL, *ConfigFile=NULL, *BindAddress=NULL; const char *ptr; STREAM *S; int IsTunnel=FALSE; + int Flags, SshFlags=0; + + Flags=SSHParseConfig(Config, &BindAddress, &ConfigFile); + + + //translate stream flags into specific SSH configurations + if (Flags & SF_COMPRESSED) SshFlags |= SSH_COMPRESS; + + if (Flags & SF_WRONLY) SshFlags |= SSH_CANON_PTY; + else if (Flags & STREAM_APPEND) SshFlags |= SSH_CANON_PTY | SSH_NO_ESCAPE; + else if (Flags & SF_LIST) SshFlags |= SSH_CANON_PTY | SSH_NO_ESCAPE; + //If we are using the .ssh/config connection-config system then there won't be a username, and 'Host' will @@ -29,8 +106,8 @@ STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pas Tempstr=MCatStr(Tempstr,"-i ",KeyFile," ",NULL); } - if (Flags & SSH_NO_ESCAPE) Tempstr=MCatStr(Tempstr, "-e none ", NULL); - if (Flags & SSH_COMPRESS) Tempstr=MCatStr(Tempstr, "-C ", NULL); + if (SshFlags & SSH_NO_ESCAPE) Tempstr=MCatStr(Tempstr, "-e none ", NULL); + if (SshFlags & SSH_COMPRESS) Tempstr=MCatStr(Tempstr, "-C ", NULL); ptr=GetToken(Command, "\\S", &Token, 0); while (ptr) @@ -53,6 +130,9 @@ STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pas ptr=GetToken(ptr, "\\S", &Token, 0); } + if (StrValid(BindAddress)) Tempstr=MCatStr(Tempstr, "-b \"", BindAddress, "\" ", NULL); + if (StrValid(ConfigFile)) Tempstr=MCatStr(Tempstr, "-E \"", ConfigFile, "\" ", NULL); + if (StrValid(RemoteCmd)) Tempstr=MCatStr(Tempstr, " \"", RemoteCmd, "\" ", NULL); @@ -67,7 +147,7 @@ STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pas //to tell it 'end of file'. We can't just close the connection, as we //may not have sent all the data. For this one situation we use canonical //pty settings, so we can use the 'cntrl-d' control character - if (Flags & SSH_CANON_PTY) TTYConfigs=CatStr(TTYConfigs, " canon"); + if (SshFlags & SSH_CANON_PTY) TTYConfigs=CatStr(TTYConfigs, " canon"); TTYConfigs=CatStr(TTYConfigs, " noshell setsid"); @@ -101,6 +181,8 @@ STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pas DestroyString(KeyFile); DestroyString(TTYConfigs); DestroyString(RemoteCmd); + DestroyString(ConfigFile); + DestroyString(BindAddress); DestroyString(Token); return(S); @@ -113,15 +195,18 @@ STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pas //get translated to: //'r' -> SF_RDONLY (read a file from remote server) //'w' -> SF_WRONLY (write a file to remote server) +//'a' -> SF_APPEND (append to a file to remote server) +//'l' list files on remote server //'x' or anything else runs a command on the remote server -STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, const char *iPath, int Flags) +STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, const char *iPath, const char *Config) { char *Tempstr=NULL, *Path=NULL; const char *ptr; - int SshFlags=0; + int Flags; STREAM *S; + Flags=SSHParseConfig(Config, NULL, NULL); if (iPath) { @@ -132,7 +217,8 @@ STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, } else ptr=""; - //if SF_RDONLY is set, then we treat this as a 'file get' + //if SF_RDONLY is set, then we treat this as a 'file get', + // we cannot do both read and write to a file over ssh if (Flags & SF_RDONLY) { Tempstr=QuoteCharsInStr(Tempstr, ptr, " ()"); @@ -142,18 +228,20 @@ STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, { Tempstr=QuoteCharsInStr(Tempstr, ptr, " ()"); Path=MCopyStr(Path, "cat - > ", Tempstr, NULL); - SshFlags |= SSH_CANON_PTY; } else if (Flags & STREAM_APPEND) { Tempstr=QuoteCharsInStr(Tempstr, ptr, " ()"); Path=MCopyStr(Path, "cat - >> ", Tempstr, NULL); - SshFlags |= SSH_CANON_PTY | SSH_NO_ESCAPE; + } + else if (Flags & SF_LIST) + { + Tempstr=QuoteCharsInStr(Tempstr, ptr, " ()"); + Path=MCopyStr(Path, "ls -d ", Tempstr, NULL); } else Path=CopyStr(Path, ptr); - if (Flags & SF_COMPRESSED) SshFlags |= SSH_COMPRESS; - S=SSHConnect(Host, Port, User, Pass, Path, SshFlags); + S=SSHConnect(Host, Port, User, Pass, Path, Config); Destroy(Tempstr); Destroy(Path); diff --git a/Ssh.h b/Ssh.h index 691cf55..710ecb4 100644 --- a/Ssh.h +++ b/Ssh.h @@ -11,6 +11,23 @@ Copyright (c) 2015 Colum Paget /* Connect to an ssh server and run a command. This requires the ssh command-line program to be available. +You will normally not use these functions, instead using something like 'STREAMOpen("ssh://myhost.com:2222", "r bind=192.168.6.1");' + +The 'config' argument to SSHConnect and SSHOpen is the same as other STREAMOpen style commands, consisting of a set of fopen-style 'open flags' +followed by name-value pairs for other settings + +Currently recognized 'open flags' are: +'r' -> SF_RDONLY (read a file from remote server) +'w' -> SF_WRONLY (write a file to remote server) +'w' -> SF_APPEND (append to a file to remote server) +'l' -> list files on remote server +'x' or anything else runs a command on the remote server + +Currently recognized name-value settings are: + +bind=
bind to a local address so that our connection seems to be coming from that address +config= use ssh config file + Note, such SSH connections CANNOT BE USED WITH CONNECTION CHAINS / PROXIES. They are always direct connections. if ssh's config system is being used to set up known connections (via ~/.ssh/config) then User, Pass and Port @@ -20,16 +37,12 @@ The returned stream can be used with the usual STREAM functions to read/write to ssh host. see Stream.h for available functions. */ -#define SSH_CANON_PTY 1 //communicate to ssh program over a canonical pty (honor ctrl-d, ctrl-c etc) -#define SSH_NO_ESCAPE 2 //disable the 'escape character' -#define SSH_COMPRESS 4 //use -C to compress ssh traffic - #ifdef __cplusplus extern "C" { #endif -STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pass, const char *Command, int Flags); -STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, const char *Path, int Flags); +STREAM *SSHConnect(const char *Host, int Port, const char *User, const char *Pass, const char *Command, const char *Config); +STREAM *SSHOpen(const char *Host, int Port, const char *User, const char *Pass, const char *Path, const char *Config); void SSHClose(STREAM *S); #ifdef __cplusplus diff --git a/Stream.c b/Stream.c index ba49e05..aaf42a8 100644 --- a/Stream.c +++ b/Stream.c @@ -1097,7 +1097,7 @@ STREAM *STREAMOpen(const char *URL, const char *Config) if ( (CompareStr(URL,"-")==0) || (strcasecmp(URL,"stdio:")==0) ) S=STREAMFromDualFD(dup(0), dup(1)); else if (strcasecmp(URL,"stdin:")==0) S=STREAMFromFD(dup(0)); else if (strcasecmp(URL,"stdout:")==0) S=STREAMFromFD(dup(1)); - else if (strcasecmp(Proto,"ssh")==0) S=SSHOpen(Host, Port, User, Pass, Path, STREAMParseConfig(Config)); + else if (strcasecmp(Proto,"ssh")==0) S=SSHOpen(Host, Port, User, Pass, Path, Config); else if (strcasecmp(Proto,"tty")==0) { S=STREAMFromFD(TTYConfigOpen(URL+4, Config)); @@ -2156,7 +2156,15 @@ char *STREAMReadDocument(char *RetStr, STREAM *S) if (result >= 0) bytes_read+=result; else break; } - StrTrunc(RetStr,bytes_read); + + //don't trust StrTrunc here, as we might have read + //binary data that can include '\0' characters + //StrTrunc(RetStr,bytes_read); + + //instead of StrTrunc do this. + RetStr[bytes_read]='\0'; + StrLenCacheAdd(RetStr, bytes_read); + //if result > 0 then we didn't break out on reading a 'STREAM_CLOSED' or other close condition, we broke out because we had hit the size limit if ((bytes_read==size) && (result > 0)) diff --git a/Stream.h b/Stream.h index aabc6a7..0c337ef 100644 --- a/Stream.h +++ b/Stream.h @@ -208,19 +208,20 @@ typedef enum {STREAM_TYPE_FILE, STREAM_TYPE_PIPE, STREAM_TYPE_TTY, STREAM_TYPE_U #define SF_RDLOCK 2048 //lock file on every read #define SF_FOLLOW 4096 //ONLY FOR FILES: follow symbolic links #define SF_TLS 4096 //ONLY FOR SOCKETS: use SSL/TLS -#define SF_SECURE 8192 //lock internal buffers into memory so they aren't written to swap or coredumps -#define SF_NONBLOCK 16384 //nonblocking open (you must use select to check that the file is ready to use) -#define SF_EXCL 32768 //ONLY FOR FILES: exclusive create with O_EXCL, file must not pre-exist -#define SF_TLS_AUTO 32768 //nothing to see here, move along -#define SF_ERROR 65536 //raise an error if open or connect fails -#define SF_EXEC_INHERIT 131072 //allow stream to be inherited across an exec (default is close-on-exec) -#define SF_BINARY 262144 //'binary mode' for websocket etc -#define SF_NOCACHE 524288 //don't cache file data in filesystem cache -#define SF_SORTED 1048576 //file is sorted, this is a hint to 'STREAMFind' -#define STREAM_IMMUTABLE 2097152 //file is immutable (if supported by fs) +#define SF_SECURE 8192 //lock internal buffers into memory so they aren't written to swap or coredumps +#define SF_NONBLOCK 16384 //nonblocking open (you must use select to check that the file is ready to use) +#define SF_EXCL 32768 //ONLY FOR FILES: exclusive create with O_EXCL, file must not pre-exist +#define SF_TLS_AUTO 32768 //nothing to see here, move along +#define SF_ERROR 65536 //raise an error if open or connect fails +#define SF_EXEC_INHERIT 131072 //allow stream to be inherited across an exec (default is close-on-exec) +#define SF_BINARY 262144 //'binary mode' for websocket etc +#define SF_NOCACHE 524288 //don't cache file data in filesystem cache +#define SF_LIST 524288 //only for SSH streams: list files +#define SF_SORTED 1048576 //file is sorted, this is a hint to 'STREAMFind' +#define STREAM_IMMUTABLE 2097152 //file is immutable (if supported by fs) #define STREAM_APPENDONLY 4194304 //file is append-only (if supported by fs) -#define SF_COMPRESSED 8388608 //enable compression, this requests compression -#define SF_TMPNAME 16777216 //file path is a template to create a temporary file name (must end in 'XXXXXX') +#define SF_COMPRESSED 8388608 //enable compression, this requests compression +#define SF_TMPNAME 16777216 //file path is a template to create a temporary file name (must end in 'XXXXXX') //Stream state values, set in S->State diff --git a/Terminal.c b/Terminal.c index 4a48314..0a74653 100644 --- a/Terminal.c +++ b/Terminal.c @@ -1005,13 +1005,14 @@ char *TerminalReadText(char *RetStr, int Flags, STREAM *S) switch (inchar) { + //seems like control-c sends this + case STREAM_NODATA: case ESCAPE: Destroy(RetStr); return(NULL); break; case STREAM_TIMEOUT: - case STREAM_NODATA: case '\n': case '\r': break; diff --git a/examples/Makefile b/examples/Makefile index f047264..26bf401 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -18,7 +18,9 @@ all: gcc -oMaps.exe Maps.c $(LIBS) gcc -oTTYTest.exe TTYTest.c $(LIBS) gcc -oErrors.exe Errors.c $(LIBS) + gcc -ossh-time.exe ssh-time.c $(LIBS) gcc -ossh-tunnel.exe ssh-tunnel.c $(LIBS) + gcc -ossh-listfiles.exe ssh-listfiles.c $(LIBS) gcc -oConfig.exe Config.c $(LIBS) gcc -oStrLen.exe StrLen.c $(LIBS) gcc -oSSLClient.exe SSLClient.c $(LIBS) @@ -30,3 +32,6 @@ all: gcc -ononewprivs.exe nonewprivs.c $(LIBS) gcc -ochroot.exe chroot.c $(LIBS) gcc -omovetest.exe movetest.c $(LIBS) + +clean: + rm -f *.exe diff --git a/examples/ssh-listfiles.c b/examples/ssh-listfiles.c new file mode 100644 index 0000000..9ae029c --- /dev/null +++ b/examples/ssh-listfiles.c @@ -0,0 +1,31 @@ +#include "../libUseful.h" + +//test running a command on an ssh server + +main(int argc, const char *argv[]) +{ + STREAM *S; + char *Tempstr=NULL; + + if (argv < 2) + { + printf("Insufficient arguments: %s \n", argv[0]); + exit(1); + } + + Tempstr=MCopyStr(Tempstr, "ssh:", argv[1], "/*", NULL); + S=STREAMOpen(Tempstr, "l"); + if (S) + { + Tempstr=STREAMReadLine(Tempstr, S); + while (Tempstr) + { + StripTrailingWhitespace(Tempstr); + printf("%s\n", Tempstr); + Tempstr=STREAMReadLine(Tempstr, S); + } + STREAMClose(S); + } + else printf("ERROR: failed to connect to %s\n", Tempstr); + +} diff --git a/examples/ssh-time.c b/examples/ssh-time.c index f2555b4..83e2057 100644 --- a/examples/ssh-time.c +++ b/examples/ssh-time.c @@ -13,7 +13,7 @@ main(int argc, const char *argv[]) exit(1); } - Tempstr=MCopyStr(Tempstr, "ssh:", argv[1], "/date '+%Y-%m-%d'", NULL); + Tempstr=MCopyStr(Tempstr, "ssh:", argv[1], "/date '+%Y-%m-%dT%H:%M:%S'", NULL); S=STREAMOpen(Tempstr, "x"); if (S) {