tuyau

Client/server for transfering files (like cp)
git clone https://noulin.net/git/tuyau.git
Log | Files | Refs | README

sclient.c (15265B)


      1 #! /usr/bin/env sheepy
      2 /* or direct path to sheepy: #! /usr/local/bin/sheepy */
      3 /* Libsheepy documentation: https://spartatek.se/libsheepy/ */
      4 
      5 /*
      6 usage
      7 - create server config
      8 - start server
      9 - create client config
     10 - run client
     11 
     12 Upload files:
     13 ./sclient.c files serverName
     14 ./sclient.c files serverName:path/
     15 ./sclient.c file serverName:path/newfilename
     16 
     17 Downdload files:
     18 ./sclient.c serverName:path/files
     19   >> downloads to current directory
     20 ./sclient.c serverName:path/files path/
     21 ./sclient.c serverName:path/file path/newfilename
     22 
     23 When the client sends files to the server, client decides when
     24 to close the connection and the client closes the connection
     25 When the client receives files from the server, the server decides when
     26 to close the connection and the client closes the connection
     27 
     28 */
     29 
     30 #include <netdb.h>
     31 #include <netinet/in.h>
     32 #include <string.h>
     33 #include <stdlib.h>
     34 #include <unistd.h>
     35 
     36 // open
     37 #include <sys/types.h>
     38 #include <sys/stat.h>
     39 #include <fcntl.h>
     40 
     41 // basename
     42 #include <libgen.h>
     43 
     44 #include <openssl/err.h>
     45 //#include <openssl/pem.h>
     46 #include <openssl/ssl.h>
     47 //#include <openssl/x509v3.h>
     48 
     49 #include "netFrame.h"
     50 #include "libsheepyObject.h"
     51 #include "shpPackages/short/short.h"
     52 #include "makeHeader.h"
     53 
     54 #define CONFIG "~/.tuyau/config.yml"
     55 
     56 /* enable/disable logging */
     57 /* #undef pLog */
     58 /* #define pLog(...) */
     59 
     60 bool saveReceivedData(void *receiveB, size_t sz, void *context);
     61 
     62 int main(int ARGC, char** ARGV) {
     63 
     64   initLibsheepy(ARGV[0]);
     65   setLogMode(LOG_FUNC);
     66 
     67   // Steps
     68   //
     69   // load configuration
     70   // check if there is a server name in argv 1
     71   //   search for server name in last argument
     72   // if receive and 2 args, arg 2 must be a valid path in client
     73   // open connection to server
     74   // receive files in receivePath
     75   // send files
     76 
     77   // load configuration
     78   cleanCharP(config) = expandHome(CONFIG);
     79   cleanAllocateSmallJson(cfg);
     80   readFileG(cfg, config);
     81 
     82   if (ARGC < 2) {
     83     logE("Missing arguments");
     84     ret 1;
     85   }
     86 
     87   // check if there is a server name in argv 1
     88   // if yes, the user wants to receive data from the server
     89   //    destPath is the path in the server to download from
     90   //    set server name
     91   // if no, the user wants to upload to the server
     92 
     93   // send or receive data to/from server
     94   bool isReceive = no;
     95   const char *serverName = null;
     96   char *destPath         = null;
     97   char *receivePath      = null;
     98   iter(cfg, D) {
     99     cast(smallDictt*,d,D);
    100     //lv(d);
    101     if (startsWithG(ARGV[1], iK(cfg)) and ARGV[1][lenG(iK(cfg))] == ':' and ARGV[1][lenG(iK(cfg))+1] != 0) {
    102       serverName = iK(cfg);
    103       destPath   = ARGV[1] + lenG(iK(cfg)) + 1;
    104       isReceive  = yes;
    105       break;
    106     }
    107   }
    108 
    109   if (not isReceive) {
    110     // search for server name in last argument
    111     if (ARGC == 2) {
    112       logE("Missing arguments");
    113       ret 1;
    114     }
    115     logD("path in client");
    116     // check last arg
    117     bool foundDest = no;
    118     iter(cfg, D) {
    119       cast(smallDictt*,d,D);
    120       //lv(d);
    121       if (startsWithG(ARGV[ARGC-1], iK(cfg))) {
    122         if (ARGV[ARGC-1][lenG(iK(cfg))] == 0) {
    123           logD("no dest path");
    124           serverName = iK(cfg);
    125           foundDest  = yes;
    126           break;
    127         }
    128         elif (ARGV[ARGC-1][lenG(iK(cfg))] == ':' and ARGV[ARGC-1][lenG(iK(cfg))+1] != 0) {
    129           logD("found destination");
    130           serverName = iK(cfg);
    131           destPath   = ARGV[ARGC-1] + lenG(iK(cfg)) + 1;
    132           foundDest  = yes;
    133           break;
    134         }
    135       }
    136     }
    137   } // not isReceive
    138   elif (ARGC > 2) {
    139     // receive
    140     // arg 2 must be a valid path in client
    141     receivePath           = ARGV[2];
    142     cleanCharP(localPath) = normalizePathG(ARGV[2]);
    143     if (not isEmptyG(localPath)/*ignore "." -> ""*/) {
    144       logD("check if receivePath is dir or dir with filename");
    145       if (not isPath(localPath)) {
    146         if (endsWithG(receivePath,'/')) {
    147           logE("Directory doesn't exist %s", localPath);
    148           ret 1;
    149         }
    150         cleanCharP(dir) = shDirname(localPath);
    151         if (not isPath(dir)) {
    152           logE("Directory doesn't exist %s", dir);
    153           ret 1;
    154         }
    155       }
    156     }
    157   }
    158 
    159   if (not serverName) {
    160     logE("server name not found");
    161     ret 1;
    162   }
    163 
    164   cleanFinishSmallDictP(svrInfo) = getG(cfg, rtSmallDictt, serverName);
    165 
    166   lv(svrInfo);
    167 
    168   // open connection to server
    169   int sock;
    170   struct sockaddr_in server;
    171   struct hostent *hp;
    172 
    173 
    174   cleanAllocateNetFrame(netframe);
    175 
    176 
    177   sock = socket(AF_INET, SOCK_STREAM, 0);
    178   if (sock < 0){
    179     perror("Failed to create socket");
    180     ret 1;
    181   }
    182 
    183   server.sin_family = AF_INET;
    184 
    185   hp = gethostbyname($(svrInfo, "hostname"));
    186   if (hp==0) {
    187     perror("gethostbyname failed");
    188     close(sock);
    189     XFailure;
    190   }
    191 
    192   memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
    193   server.sin_port = htons(u$(svrInfo, "port"));
    194 
    195   if (connect(sock,(struct sockaddr *) &server, sizeof(server))){
    196     perror("connect failed");
    197     close(sock);
    198     XFailure;
    199   }
    200 
    201   SSL_CTX *ssl_ctx;
    202   SSL *ssl;
    203   ssl_ctx = SSL_CTX_new(TLS_method());
    204   BIO *sbio = BIO_new(BIO_f_ssl());
    205   ssl = SSL_new(ssl_ctx);
    206   // attach the socket descriptor
    207   SSL_set_fd(ssl, sock);
    208 
    209   // perform the connection
    210   if (SSL_connect(ssl) == -1) {
    211     logE("Couldn't establish the TLS connection");
    212     ret 1;
    213   }
    214 
    215   // buffer for network packets
    216   u8 buf[1024*1024]   = init0Var;
    217 
    218   if (isReceive) {
    219     // receive files in receivePath
    220 
    221     u32 bufi      = 0;
    222     makeHeader(buf, &bufi, 2 /* command */, svrInfo, null /*sendFile*/, destPath, null /*filesize*/);
    223 
    224     o(netframe,send , ssl, buf, bufi);
    225 
    226     o(netframe, setCallback, saveReceivedData, receivePath /*context*/);
    227 
    228     var r = o(netframe,receive, ssl);
    229     logVarG(r);
    230 
    231   }
    232   else {
    233     // send files
    234 
    235     cleanAllocateSmallArray(pathToSend);
    236 
    237     // verify that paths in client are valid
    238     rangeFrom(i, 1, ARGC-1) {
    239       if (not isPath(ARGV[i])) {
    240         logE("Incorrect path: "BLD YLW"%s"RST,ARGV[i]);
    241         continue;
    242       }
    243       pushG(pathToSend, ARGV[i]);
    244     }
    245 
    246     cleanAllocateSmallDict(recusive);
    247 
    248     // send files in command line arguments
    249 
    250     iter(pathToSend, P) {
    251       castS(p, P);
    252       char *sendFile = ssGet(P);
    253 
    254       // create local directory in server
    255       // add files from local directory to recusive dict, the last element is the local path
    256       if (isDirG(p)) {
    257         logD("mkdir in server");
    258 
    259         char *dirname = basename(ssGet(p));
    260         cleanCharP(dest) = null;
    261         if (destPath)
    262           dest = catS(destPath, "/", dirname);
    263         else
    264           dest = strdup(dirname);
    265 
    266         u32 bufi      = 0;
    267         u64 *filesize = null;
    268         makeHeader(buf, &bufi, 1 /* command */, svrInfo, ssGet(p), dest, &filesize);
    269 
    270         o(netframe,send , ssl, buf, bufi);
    271 
    272         var dir = readDirAllG(rtSmallArrayt, p);
    273         //lv(dir);
    274         if (not isEmptyG(dir)) {
    275           // save local path in last element
    276           pushG(dir, ssGet(p));
    277           setNFreeG(recusive, dirname, dir);
    278         }
    279         lv(recusive);
    280         continue;
    281       }
    282 
    283       // send
    284       u32 bufi      = 0;
    285       u64 *filesize = null;
    286       makeHeader(buf, &bufi, 0 /* command */, svrInfo, sendFile, destPath, &filesize);
    287 
    288       if (*filesize < (sizeof(buf) - bufi)) {
    289         // the file is smaller than the network buffer, send all in one go
    290         pError0(bReadFile(sendFile, buf + bufi));
    291         o(netframe,send , ssl, buf, bufi + *filesize);
    292       }
    293       else {
    294         logD("big file");
    295         // loop to send big file in chunks
    296         u64 szToSend = *filesize;
    297         u8 *b        = buf + bufi;
    298         u64 bSz      = sizeof(buf) - bufi;
    299         int fd       = open(sendFile, O_RDONLY);
    300         do {
    301           if (szToSend <= bSz) {
    302             read(fd, b, szToSend);
    303             o(netframe,send , ssl, b, szToSend);
    304             szToSend = 0;
    305           }
    306           else {
    307             read(fd, b, bSz);
    308             if (szToSend == *filesize) {
    309               o(netframe,send , ssl, buf, bufi + bSz);
    310             }
    311             else {
    312               o(netframe,send , ssl, b, bSz);
    313             }
    314             szToSend -= bSz;
    315           }
    316         } while(szToSend);
    317         close(fd);
    318       }
    319     } // loop on arguments
    320 
    321 
    322     // send files recursively
    323     // each key is a path in server corresponding to a local directory
    324     // the element is the list of files in the directory
    325     // it is similar to the iter(pathToSend, P) loop
    326 
    327     iter(recusive, A) {
    328       cast(smallArrayt*,a,A);
    329       cleanCharP(localPath) = cropElemG(a, rtChar, -1);
    330       lv(localPath);
    331       iter(a, P) {
    332         castS(p, P);
    333         char *sendFile = ssGet(P);
    334 
    335         // create local directory in server
    336         // add files from local directory to recusive dict, the last element is the local path
    337         cleanCharP(localp)  = catS(localPath, "/", ssGet(p));
    338         if (isDirG(localp)) {
    339           logD("mkdir in server");
    340           cleanCharP(dirname) = catS(iK(recusive), "/", ssGet(p));
    341           cleanCharP(dest) = null;
    342           if (destPath)
    343             dest = catS(destPath, "/", dirname);
    344           else
    345             dest = strdup(dirname);
    346 
    347           u32 bufi      = 0;
    348           u64 *filesize = null;
    349           makeHeader(buf, &bufi, 1 /* command */, svrInfo, localp, dest, &filesize);
    350 
    351           o(netframe,send , ssl, buf, bufi);
    352 
    353           var dir = readDirAllG(rtSmallArrayt, localp);
    354           lv(dir);
    355           if (not isEmptyG(dir)) {
    356             // save local path in last element
    357             pushG(dir, localp);
    358             setNFreeG(recusive, dirname, dir);
    359           }
    360           //lv(recusive);
    361           continue;
    362         }
    363 
    364         logD("Send files from localPath/p to destPath/iK(recusive)/");
    365         // send
    366         cleanCharP(dest) = null;
    367         if (destPath)
    368           dest = catS(destPath, "/", iK(recusive));
    369         else
    370           dest = strdup(iK(recusive));
    371         u32 bufi         = 0;
    372         u64 *filesize    = null;
    373         makeHeader(buf, &bufi, 0 /* command */, svrInfo, localp, dest, &filesize);
    374 
    375         if (*filesize < (sizeof(buf) - bufi)) {
    376           // the file is smaller than the network buffer, send all in one go
    377           pError0(bReadFile(localp, buf + bufi));
    378           o(netframe,send , ssl, buf, bufi + *filesize);
    379         }
    380         else {
    381           logD("big file");
    382           // loop to send big file in chunks
    383           u64 szToSend = *filesize;
    384           u8 *b        = buf + bufi;
    385           u64 bSz      = sizeof(buf) - bufi;
    386           int fd       = open(localp, O_RDONLY);
    387           do {
    388             if (szToSend <= bSz) {
    389               read(fd, b, szToSend);
    390               o(netframe,send , ssl, b, szToSend);
    391               szToSend = 0;
    392             }
    393             else {
    394               read(fd, b, bSz);
    395               if (szToSend == *filesize) {
    396                 o(netframe,send , ssl, buf, bufi + bSz);
    397               }
    398               else {
    399                 o(netframe,send , ssl, b, bSz);
    400               }
    401               szToSend -= bSz;
    402             }
    403           } while(szToSend);
    404           close(fd);
    405         }
    406       }
    407     }
    408 
    409     lv(recusive);
    410 
    411     // last frame size 0
    412     o(netframe,end, ssl);
    413 
    414   } // isReceive
    415 
    416   // release SSL state
    417   SSL_free(ssl);
    418   SSL_CTX_free(ssl_ctx);
    419   if (!isReceive) {
    420     // some data needs to be received in order to send all data to the server, SSL_write says all the data is sent but it is not true, why?
    421     // 478 bytes are received. It seems to be encrypted.
    422     // SSL_shutdown() and shutdown() don't help
    423     int r = recv(sock, buf, sizeof(buf), 0);
    424     if (r == -1) {
    425       logE("recv before closing the socket");
    426     }
    427   }
    428   close(sock);
    429 }
    430 
    431 
    432 int packetCount = 0;
    433 int fd          = -1;
    434 u64 transferSz  = 0;
    435 
    436 bool saveReceivedData(void *receiveB, size_t sz, void *context) {
    437   logVarG(sz);
    438   if (packetCount == 0) {
    439     // receive a new command
    440     // header
    441     u8 *command               = (u8*) receiveB;
    442 
    443     char *root                = context;
    444 
    445     if (*command == 0) {
    446       // receive file
    447 
    448       u16 *filenameLength       = (u16*)(receiveB + 9);
    449       // receiveB + 11 = filename
    450       cleanCharP(filename)      = malloc(*filenameLength + 1);
    451       filename[*filenameLength] = 0;
    452       memcpy(filename, receiveB + 11, *filenameLength);
    453       u32 bufi                  = 11 + *filenameLength;
    454       u16 *filemode             = (u16*)(receiveB + bufi);
    455       bufi += 2;
    456       u16 *pathLength           = (u16*)(receiveB + bufi);
    457       bufi += 2;
    458       cleanCharP(destPath)      = null;
    459       if (*pathLength) {
    460         destPath              = malloc(*pathLength + 1);
    461         memcpy(destPath, receiveB + bufi, *pathLength);
    462         destPath[*pathLength] = 0;
    463         bufi                 += *pathLength;
    464       }
    465       // file size
    466       u64 *filesize             = (u64*)(receiveB + bufi);
    467       bufi += 8;
    468 
    469       logD("bufi %d fsz %d  + %d, filename %s, root %s, destPath %s",bufi, *filesize, bufi + *filesize, filename, root,destPath);
    470 
    471       // local filename
    472       cleanCharP(path) = null;
    473 
    474       if (not root) {
    475         // save file in current directory
    476         if (not destPath) {
    477           path = strdup(filename);
    478         }
    479         else {
    480           path = catS(destPath, "/", filename);
    481         }
    482       }
    483       elif (isDir(root)) {
    484         path = catS(root, "/", nS(destPath), "/", filename);
    485       }
    486       else {
    487         // root has a filename
    488         path = strdup(root);
    489       }
    490 
    491       pErrorNULL(normalizePathG(&path));
    492       lv(path);
    493 
    494       if (*filesize <= sz - bufi) {
    495         // the file is smaller than the network buffer, the complete file is already here
    496         pError0(writeFile(path, receiveB + bufi, *filesize));
    497         pError0(fileChmod(path, *filemode));
    498       }
    499       else {
    500         logD("open fd");
    501         // increase packetCount to receive big file in chunks
    502         inc packetCount;
    503         fd = open(path, O_WRONLY | O_CREAT, *filemode);
    504         if (fd == -1) {
    505           logE("cant open %s", path);
    506           ret yes;
    507         }
    508         int r = write(fd, receiveB + bufi, sz - bufi);
    509         if (r == -1) {
    510           logE("cant write %s", path);
    511           ret yes;
    512         }
    513         transferSz = *filesize - (sz - bufi);
    514       }
    515     }
    516     elif (*command == 1) {
    517       // mkdir
    518       if (root and not isDir(root)) {
    519         logE("Cant create directory if root is not a directory %s", root);
    520         ret yes;
    521       }
    522       logD("mkdir");
    523       u16 *filemode         = (u16*)(receiveB + 9);
    524       u16 *pathLength       = (u16*)(receiveB + 11);
    525       cleanCharP(destPath)  = malloc(*pathLength + 1);
    526       memcpy(destPath, receiveB + 13, *pathLength);
    527       destPath[*pathLength] = 0;
    528       cleanCharP(path)      = null;
    529       if (not root) {
    530         path = strdup(destPath);
    531       }
    532       else {
    533         path = catS(root, "/", destPath);
    534       }
    535       pErrorNULL(normalizePathG(&path));
    536       pErrorNULL(trimG(&path));
    537       logD(BLD GRN"mkdir" RST);
    538       lv(path);
    539       pError0(mkdirParents(path));
    540       pError0(fileChmod(path, *filemode));
    541     }
    542   }
    543   else {
    544     // receive large files packetCount > 0
    545     if (fd == -1) ret yes;
    546     // big file
    547     int r = write(fd, receiveB, sz);
    548     if (r == -1) {
    549       logE("cant write");
    550       ret yes;
    551     }
    552     if (transferSz <= sz) {
    553       transferSz  = 0;
    554       close(fd);
    555       fd          = -1;
    556       packetCount = 0;
    557     }
    558     else transferSz -= sz;
    559   }
    560   ret yes;
    561 }
    562 // vim: set expandtab ts=2 sw=2: