LCOV - code coverage report
Current view: top level - src - handy.c (source / functions) Hit Total Coverage
Test: burp-coverage-clean.info Lines: 38 240 15.8 %
Date: 2016-05-01 Functions: 6 24 25.0 %

          Line data    Source code
       1             : #include "burp.h"
       2             : #include "alloc.h"
       3             : #include "asfd.h"
       4             : #include "async.h"
       5             : #include "berrno.h"
       6             : #include "cmd.h"
       7             : #include "fsops.h"
       8             : #include "fzp.h"
       9             : #include "handy.h"
      10             : #include "hexmap.h"
      11             : #include "iobuf.h"
      12             : #include "log.h"
      13             : #include "msg.h"
      14             : #include "prepend.h"
      15             : #include "protocol1/handy.h"
      16             : #include "protocol2/blk.h"
      17             : 
      18             : #include <sys/types.h>
      19             : #include <sys/socket.h>
      20             : 
      21             : #ifdef HAVE_WIN32
      22             : #include <winsock2.h>
      23             : #include <ws2tcpip.h>
      24             : #endif
      25             : 
      26             : // return -1 for error, 0 for OK, 1 if the client wants to interrupt the
      27             : // transfer.
      28           6 : int do_quick_read(struct asfd *asfd, const char *datapth, struct cntr *cntr)
      29             : {
      30           6 :         int r=0;
      31             :         struct iobuf *rbuf;
      32           6 :         if(asfd->as->read_quick(asfd->as)) return -1;
      33           6 :         rbuf=asfd->rbuf;
      34             : 
      35           6 :         if(rbuf->buf)
      36             :         {
      37           0 :                 if(rbuf->cmd==CMD_MESSAGE
      38           0 :                   || rbuf->cmd==CMD_WARNING)
      39             :                 {
      40           0 :                         log_recvd(rbuf, cntr, 0);
      41             :                 }
      42           0 :                 else if(rbuf->cmd==CMD_INTERRUPT)
      43             :                 {
      44             :                         // Client wants to interrupt - double check that
      45             :                         // it is still talking about the file that we are
      46             :                         // sending.
      47           0 :                         if(datapth && !strcmp(rbuf->buf, datapth))
      48           0 :                                 r=1;
      49             :                 }
      50             :                 else
      51             :                 {
      52           0 :                         iobuf_log_unexpected(rbuf, __func__);
      53           0 :                         r=-1;
      54             :                 }
      55           0 :                 iobuf_free_content(rbuf);
      56             :         }
      57           6 :         return r;
      58             : }
      59             : 
      60           0 : int send_whole_file_gz(struct asfd *asfd,
      61             :         const char *fname, const char *datapth, int quick_read,
      62             :         uint64_t *bytes, struct cntr *cntr,
      63             :         int compression, struct fzp *fzp)
      64             : {
      65           0 :         int ret=0;
      66           0 :         int zret=0;
      67             : 
      68             :         unsigned have;
      69             :         z_stream strm;
      70           0 :         int flush=Z_NO_FLUSH;
      71             :         uint8_t in[ZCHUNK];
      72             :         uint8_t out[ZCHUNK];
      73             : 
      74             :         struct iobuf wbuf;
      75             : 
      76             : //logp("send_whole_file_gz: %s%s\n", fname, extrameta?" (meta)":"");
      77             : 
      78             :         /* allocate deflate state */
      79           0 :         strm.zalloc = Z_NULL;
      80           0 :         strm.zfree = Z_NULL;
      81           0 :         strm.opaque = Z_NULL;
      82           0 :         if((zret=deflateInit2(&strm, compression, Z_DEFLATED, (15+16),
      83             :                 8, Z_DEFAULT_STRATEGY))!=Z_OK)
      84             :                         return -1;
      85             : 
      86           0 :         do
      87             :         {
      88           0 :                 strm.avail_in=fzp_read(fzp, in, ZCHUNK);
      89           0 :                 if(!compression && !strm.avail_in) break;
      90             : 
      91           0 :                 *bytes+=strm.avail_in;
      92             : 
      93           0 :                 if(strm.avail_in) flush=Z_NO_FLUSH;
      94           0 :                 else flush=Z_FINISH;
      95             : 
      96           0 :                 strm.next_in=in;
      97             : 
      98             :                 // Run deflate() on input until output buffer not full, finish
      99             :                 // compression if all of source has been read in.
     100           0 :                 do
     101             :                 {
     102           0 :                         if(compression)
     103             :                         {
     104           0 :                                 strm.avail_out=ZCHUNK;
     105           0 :                                 strm.next_out=out;
     106           0 :                                 zret=deflate(&strm, flush);
     107           0 :                                 if(zret==Z_STREAM_ERROR)
     108             :                                 {
     109           0 :                                         logp("z_stream_error\n");
     110           0 :                                         ret=-1;
     111           0 :                                         break;
     112             :                                 }
     113           0 :                                 have=ZCHUNK-strm.avail_out;
     114             :                         }
     115             :                         else
     116             :                         {
     117           0 :                                 have=strm.avail_in;
     118           0 :                                 memcpy(out, in, have);
     119             :                         }
     120             : 
     121           0 :                         wbuf.cmd=CMD_APPEND;
     122           0 :                         wbuf.buf=(char *)out;
     123           0 :                         wbuf.len=have;
     124           0 :                         if(asfd->write(asfd, &wbuf))
     125             :                         {
     126             :                                 ret=-1;
     127             :                                 break;
     128             :                         }
     129           0 :                         if(quick_read && datapth)
     130             :                         {
     131             :                                 int qr;
     132           0 :                                 if((qr=do_quick_read(asfd, datapth, cntr))<0)
     133             :                                 {
     134             :                                         ret=-1;
     135             :                                         break;
     136             :                                 }
     137           0 :                                 if(qr) // Client wants to interrupt.
     138             :                                 {
     139             :                                         goto cleanup;
     140             :                                 }
     141             :                         }
     142           0 :                         if(!compression) break;
     143           0 :                 } while(!strm.avail_out);
     144             : 
     145           0 :                 if(ret) break;
     146             : 
     147           0 :                 if(!compression) continue;
     148             : 
     149           0 :                 if(strm.avail_in) /* all input will be used */
     150             :                 {
     151           0 :                         ret=-1;
     152           0 :                         logp("strm.avail_in=%d\n", strm.avail_in);
     153           0 :                         break;
     154             :                 }
     155             :         } while(flush!=Z_FINISH);
     156             : 
     157           0 :         if(!ret)
     158             :         {
     159           0 :                 if(compression && zret!=Z_STREAM_END)
     160             :                 {
     161           0 :                         logp("ret OK, but zstream not finished: %d\n", zret);
     162           0 :                         ret=-1;
     163             :                 }
     164             :         }
     165             : 
     166             : cleanup:
     167           0 :         deflateEnd(&strm);
     168             : 
     169           0 :         if(!ret)
     170             :         {
     171           0 :                 return write_endfile(asfd, *bytes, NULL);
     172             :         }
     173             : //logp("end of send\n");
     174             :         return ret;
     175             : }
     176             : 
     177           0 : int set_non_blocking(int fd)
     178             : {
     179             :         int flags;
     180           0 :         if((flags = fcntl(fd, F_GETFL, 0))<0) flags = 0;
     181           0 :         return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
     182             : }
     183             :      
     184           0 : int set_blocking(int fd)
     185             : {
     186             :         int flags;
     187           0 :         if((flags = fcntl(fd, F_GETFL, 0))<0) flags = 0;
     188           0 :         return fcntl(fd, F_SETFL, flags | ~O_NONBLOCK);
     189             : }
     190             : 
     191           5 : char *get_tmp_filename(const char *basis)
     192             : {
     193           5 :         return prepend(basis, ".tmp");
     194             : }
     195             : 
     196           0 : void add_fd_to_sets(int fd, fd_set *read_set, fd_set *write_set, fd_set *err_set, int *max_fd)
     197             : {
     198           0 :         if(read_set) FD_SET((unsigned int) fd, read_set);
     199           0 :         if(write_set) FD_SET((unsigned int) fd, write_set);
     200           0 :         if(err_set) FD_SET((unsigned int) fd, err_set);
     201             : 
     202           0 :         if(fd > *max_fd) *max_fd = fd;
     203           0 : }
     204             : 
     205             : #ifndef HAVE_WIN32
     206           0 : static int get_address_and_port(struct sockaddr_storage *addr,
     207             :         char *addrstr, size_t len, uint16_t *port)
     208             : {
     209           0 :         switch(addr->ss_family)
     210             :         {
     211             :                 case AF_INET:
     212             :                         struct sockaddr_in *s4;
     213           0 :                         s4=(struct sockaddr_in *)addr;
     214           0 :                         inet_ntop(AF_INET, &s4->sin_addr, addrstr, len);
     215           0 :                         *port=ntohs(s4->sin_port);
     216           0 :                         break;
     217             :                 case AF_INET6:
     218             :                         struct sockaddr_in6 *s6;
     219           0 :                         s6=(struct sockaddr_in6 *)addr;
     220           0 :                         inet_ntop(AF_INET6, &s6->sin6_addr, addrstr, len);
     221           0 :                         *port=ntohs(s6->sin6_port);
     222           0 :                         break;
     223             :                 default:
     224           0 :                         logp("unknown addr.ss_family: %d\n", addr->ss_family);
     225           0 :                         return -1;
     226             :         }
     227             :         return 0;
     228             : }
     229             : #endif
     230             : 
     231           0 : int log_peer_address(struct sockaddr_storage *addr)
     232             : {
     233             : #ifndef HAVE_WIN32
     234           0 :         uint16_t port=0;
     235           0 :         char addrstr[INET6_ADDRSTRLEN]="";
     236           0 :         if(get_address_and_port(addr, addrstr, INET6_ADDRSTRLEN, &port))
     237             :                 return -1;
     238           0 :         logp("Connect from peer: %s:%d\n", addrstr, port);
     239             : #endif
     240           0 :         return 0;
     241             : }
     242             : 
     243           0 : int set_peer_env_vars(struct sockaddr_storage *addr)
     244             : {
     245             : #ifndef HAVE_WIN32
     246           0 :         uint16_t port=0;
     247           0 :         char portstr[16]="";
     248           0 :         char addrstr[INET6_ADDRSTRLEN]="";
     249             : 
     250           0 :         if(get_address_and_port(addr, addrstr, INET6_ADDRSTRLEN, &port))
     251             :                 return -1;
     252             : 
     253           0 :         if(setenv("REMOTE_ADDR",  addrstr, 1))
     254             :         {
     255             :                 logp("setenv REMOTE_ADDR to %s failed: %s\n",
     256           0 :                                 addrstr, strerror(errno));
     257           0 :                 return -1;
     258             :         }
     259           0 :         snprintf(portstr, sizeof(portstr), "%d", port);
     260           0 :         if(setenv("REMOTE_PORT",  portstr, 1))
     261             :         {
     262           0 :                 logp("setenv REMOTE_PORT failed: %s\n", strerror(errno));
     263           0 :                 return -1;
     264             :         }
     265             : #endif
     266             :         return 0;
     267             : }
     268             : 
     269           0 : int set_keepalive(int fd, int value)
     270             : {
     271           0 :         int keepalive=value;
     272           0 :         if(setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
     273           0 :                 (char *)&keepalive, sizeof(keepalive)))
     274             :         {
     275             :                 logp("setsockopt keepalive=%d failed: %s\n",
     276           0 :                         value, strerror(errno));
     277           0 :                 return -1;
     278             :         }
     279             :         return 0;
     280             : }
     281             : 
     282           0 : int init_client_socket(const char *host, const char *port)
     283             : {
     284           0 :         int rfd=-1;
     285             :         int gai_ret;
     286             :         struct addrinfo hints;
     287             :         struct addrinfo *result;
     288             :         struct addrinfo *rp;
     289             : 
     290             :         memset(&hints, 0, sizeof(struct addrinfo));
     291             :         hints.ai_family = AF_UNSPEC;
     292           0 :         hints.ai_socktype = SOCK_STREAM;
     293             :         hints.ai_flags = 0;
     294             :         hints.ai_protocol = 0;
     295             : 
     296           0 :         if((gai_ret=getaddrinfo(host, port, &hints, &result)))
     297             :         {
     298           0 :                 logp("getaddrinfo: %s\n", gai_strerror(gai_ret));
     299           0 :                 return -1;
     300             :         }
     301             : 
     302           0 :         for(rp=result; rp; rp=rp->ai_next)
     303             :         {
     304           0 :                 rfd=socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
     305           0 :                 if(rfd<0) continue;
     306           0 :                 set_keepalive(rfd, 1);
     307           0 :                 if(connect(rfd, rp->ai_addr, rp->ai_addrlen) != -1) break;
     308           0 :                 close_fd(&rfd);
     309             :         }
     310           0 :         freeaddrinfo(result);
     311           0 :         if(!rp)
     312             :         {
     313             :                 /* host==NULL and AI_PASSIVE not set -> loopback */
     314             :                 logp("could not connect to %s:%s\n",
     315           0 :                         host?host:"loopback", port);
     316           0 :                 close_fd(&rfd);
     317           0 :                 return -1;
     318             :         }
     319           0 :         reuseaddr(rfd);
     320             : 
     321             : #ifdef HAVE_WIN32
     322             :         setmode(rfd, O_BINARY);
     323             : #endif
     324           0 :         return rfd;
     325             : }
     326             : 
     327           0 : void reuseaddr(int fd)
     328             : {
     329           0 :         int optval=1;
     330             : #ifdef HAVE_OLD_SOCKOPT
     331             : #define sockopt_val_t char *
     332             : #else
     333             : #define sockopt_val_t void *
     334             : #endif
     335           0 :         if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
     336           0 :                 (sockopt_val_t)&optval, sizeof(optval))<0)
     337             :                         logp("Error: setsockopt SO_REUSEADDR: %s",
     338           0 :                                 strerror(errno));
     339           0 : }
     340             : 
     341           0 : void setup_signal(int sig, void handler(int sig))
     342             : {
     343             :         struct sigaction sa;
     344             :         memset(&sa, 0, sizeof(sa));
     345           0 :         sa.sa_handler=handler;
     346           0 :         sigaction(sig, &sa, NULL);
     347           0 : }
     348             : 
     349             : /* Function based on src/lib/priv.c from bacula. */
     350           0 : int chuser_and_or_chgrp(const char *user, const char *group)
     351             : {
     352             : #if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
     353             :         struct passwd *passw = NULL;
     354             :         struct group *grp = NULL;
     355             :         gid_t gid;
     356             :         uid_t uid;
     357             :         char *username=NULL;
     358             : 
     359             :         if(!user && !group) return 0;
     360             : 
     361             :         if(user)
     362             :         {
     363             :                 if(!(passw=getpwnam(user)))
     364             :                 {
     365             :                         logp("could not find user '%s': %s\n",
     366             :                                 user, strerror(errno));
     367             :                         return -1;
     368             :                 }
     369             :         }
     370             :         else
     371             :         {
     372             :                 if(!(passw=getpwuid(getuid())))
     373             :                 {
     374             :                         logp("could not find password entry: %s\n",
     375             :                                 strerror(errno));
     376             :                         return -1;
     377             :                 }
     378             :                 user=passw->pw_name;
     379             :         }
     380             :         // Any OS uname pointer may get overwritten, so save name, uid, and gid
     381             :         if(!(username=strdup_w(user, __func__)))
     382             :                 return -1;
     383             :         uid=passw->pw_uid;
     384             :         gid=passw->pw_gid;
     385             :         if(group)
     386             :         {
     387             :                 if(!(grp=getgrnam(group)))
     388             :                 {
     389             :                         logp("could not find group '%s': %s\n", group,
     390             :                                 strerror(errno));
     391             :                         free_w(&username);
     392             :                         return -1;
     393             :                 }
     394             :                 gid=grp->gr_gid;
     395             :         }
     396             :         if(gid!=getgid() // do not do it if we already have the same gid.
     397             :           && initgroups(username, gid))
     398             :         {
     399             :                 if(grp)
     400             :                         logp("could not initgroups for group '%s', user '%s': %s\n", group, user, strerror(errno));
     401             :                 else
     402             :                         logp("could not initgroups for user '%s': %s\n", user, strerror(errno));
     403             :                 free_w(&username);
     404             :                 return -1;
     405             :         }
     406             :         free_w(&username);
     407             :         if(grp)
     408             :         {
     409             :                 if(gid!=getgid() // do not do it if we already have the same gid
     410             :                  && setgid(gid))
     411             :                 {
     412             :                         logp("could not set group '%s': %s\n", group,
     413             :                                 strerror(errno));
     414             :                         return -1;
     415             :                 }
     416             :         }
     417             :         if(uid!=getuid() // do not do it if we already have the same uid
     418             :           && setuid(uid))
     419             :         {
     420             :                 logp("could not set specified user '%s': %s\n", username,
     421             :                         strerror(errno));
     422             :                 return -1;
     423             :         }
     424             : #endif
     425           0 :         return 0;
     426             : }
     427             : 
     428         115 : const char *getdatestr(const time_t t)
     429             : {
     430             :         static char buf[32]="";
     431         115 :         const struct tm *ctm=NULL;
     432             : 
     433         230 :         if(!t
     434         115 :           || !(ctm=gmtime(&t)))
     435             :                 return "never";
     436             : 
     437         115 :         strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ctm);
     438         115 :         return buf;
     439             : }
     440             : 
     441           0 : const char *time_taken(time_t d)
     442             : {
     443             :         static char str[32]="";
     444           0 :         int seconds=0;
     445           0 :         int minutes=0;
     446           0 :         int hours=0;
     447           0 :         int days=0;
     448           0 :         char ss[4]="";
     449           0 :         char ms[4]="";
     450           0 :         char hs[4]="";
     451           0 :         char ds[4]="";
     452           0 :         seconds=d % 60;
     453           0 :         minutes=(d/60) % 60;
     454           0 :         hours=(d/60/60) % 24;
     455           0 :         days=(d/60/60/24);
     456           0 :         if(days)
     457             :         {
     458             :                 snprintf(ds, sizeof(ds), "%02d:", days);
     459             :                 snprintf(hs, sizeof(hs), "%02d:", hours);
     460             :         }
     461           0 :         else if(hours)
     462             :         {
     463             :                 snprintf(hs, sizeof(hs), "%02d:", hours);
     464             :         }
     465             :         snprintf(ms, sizeof(ms), "%02d:", minutes);
     466             :         snprintf(ss, sizeof(ss), "%02d", seconds);
     467             :         snprintf(str, sizeof(str), "%s%s%s%s", ds, hs, ms, ss);
     468           0 :         return str;
     469             : }
     470             : 
     471             : // Not in dpth.c so that Windows client can see it.
     472          10 : int dpth_protocol1_is_compressed(int compressed, const char *datapath)
     473             : {
     474          10 :         const char *dp=NULL;
     475             : 
     476          10 :         if(compressed>0) return compressed;
     477          10 :         if(compressed==0) return 0;
     478             : 
     479             :         /* Legacy - if the compressed value is -1 - that is, it is not set in
     480             :            the manifest, deduce the value from the datapath. */
     481           6 :         if((dp=strrchr(datapath, '.')) && !strcmp(dp, ".gz")) return 1;
     482           6 :         return 0;
     483             : }
     484             : 
     485           2 : long version_to_long(const char *version)
     486             : {
     487           2 :         long ret=0;
     488           2 :         char *copy=NULL;
     489           2 :         char *tok1=NULL;
     490           2 :         char *tok2=NULL;
     491           2 :         char *tok3=NULL;
     492           2 :         if(!version || !*version) return 0;
     493           2 :         if(!(copy=strdup_w(version, __func__)))
     494             :                 return -1;
     495           2 :         if(!(tok1=strtok(copy, "."))
     496           2 :           || !(tok2=strtok(NULL, "."))
     497           4 :           || !(tok3=strtok(NULL, ".")))
     498             :         {
     499           0 :                 free_w(&copy);
     500           0 :                 return -1;
     501             :         }
     502           2 :         ret+=atol(tok3);
     503           2 :         ret+=atol(tok2)*100;
     504           2 :         ret+=atol(tok1)*100*100;
     505           2 :         free_w(&copy);
     506           2 :         return ret;
     507             : }
     508             : 
     509             : /* These receive_a_file() and send_file() functions are for use by extra_comms
     510             :    and the CA stuff, rather than backups/restores. */
     511           0 : int receive_a_file(struct asfd *asfd, const char *path, struct cntr *cntr)
     512             : {
     513           0 :         int ret=-1;
     514           0 :         BFILE *bfd=NULL;
     515           0 :         uint64_t rcvdbytes=0;
     516           0 :         uint64_t sentbytes=0;
     517             : 
     518           0 :         if(!(bfd=bfile_alloc())) goto end;
     519           0 :         bfile_init(bfd, 0, cntr);
     520             : #ifdef HAVE_WIN32
     521             :         bfd->set_win32_api(bfd, 0);
     522             : #endif
     523           0 :         if(bfd->open(bfd, asfd, path,
     524             :                 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
     525           0 :                 S_IRUSR | S_IWUSR))
     526             :         {
     527             :                 berrno be;
     528           0 :                 berrno_init(&be);
     529             :                 logp("Could not open for writing %s: %s\n",
     530           0 :                         path, berrno_bstrerror(&be, errno));
     531             :                 goto end;
     532             :         }
     533             : 
     534           0 :         ret=transfer_gzfile_in(asfd, path, bfd, &rcvdbytes, &sentbytes, cntr);
     535           0 :         if(bfd->close(bfd, asfd))
     536             :         {
     537           0 :                 logp("error closing %s in %s\n", path, __func__);
     538           0 :                 goto end;
     539             :         }
     540           0 :         logp("Received: %s\n", path);
     541           0 :         ret=0;
     542             : end:
     543           0 :         bfd->close(bfd, asfd);
     544           0 :         bfile_free(&bfd);
     545           0 :         return ret;
     546             : }
     547             : 
     548             : /* Windows will use this function, when sending a certificate signing request.
     549             :    It is not using the Windows API stuff because it needs to arrive on the
     550             :    server side without any junk in it. */
     551           0 : int send_a_file(struct asfd *asfd, const char *path, struct cntr *cntr)
     552             : {
     553           0 :         int ret=0;
     554           0 :         struct fzp *fzp=NULL;
     555           0 :         uint64_t bytes=0;
     556           0 :         if(!(fzp=fzp_open(path, "rb"))
     557           0 :           || send_whole_file_gz(asfd, path, "datapth", 0, &bytes,
     558           0 :                 cntr, 9 /*compression*/, fzp))
     559             :         {
     560             :                 ret=-1;
     561             :                 goto end;
     562             :         }
     563           0 :         logp("Sent %s\n", path);
     564             : end:
     565           0 :         fzp_close(&fzp);
     566           0 :         return ret;
     567             : }
     568             : 
     569          22 : int strncmp_w(const char *s1, const char *s2)
     570             : {
     571          22 :         return strncmp(s1, s2, strlen(s2));
     572             : }
     573             : 
     574             : // Strip any trailing slashes (unless it is '/').
     575           0 : void strip_trailing_slashes(char **str)
     576             : {
     577             :         size_t l;
     578             :         // FIX THIS: pretty crappy.
     579             :         while(1)
     580             :         {
     581           0 :                 if(!str || !*str
     582           0 :                   || !strcmp(*str, "/")
     583           0 :                   || !(l=strlen(*str))
     584           0 :                   || (*str)[l-1]!='/')
     585           0 :                         return;
     586           0 :                 (*str)[l-1]='\0';
     587           0 :         }
     588             : }
     589             : 
     590           0 : int breakpoint(int breakpoint, const char *func)
     591             : {
     592           0 :         logp("Breakpoint %d hit in %s\n", breakpoint, func);
     593           0 :         return -1;
     594             : }
     595             : 
     596             : /* Windows users have a nasty habit of putting in backslashes. Convert them. */
     597             : #ifdef HAVE_WIN32
     598             : void convert_backslashes(char **path)
     599             : {
     600             :         char *p=NULL;
     601             :         for(p=*path; *p; p++) if(*p=='\\') *p='/';
     602             : }
     603             : #endif
     604             : 
     605           0 : char *encode_time(time_t utime, char *buf)
     606             : {
     607             :         const struct tm *tm;
     608           0 :         int n=0;
     609           0 :         time_t time=utime;
     610             : 
     611             : #ifdef HAVE_WIN32
     612             :         /* Avoid a seg fault in Microsoft's CRT localtime_r(),
     613             :          *  which incorrectly references a NULL returned from gmtime() if
     614             :          *  time is negative before or after the timezone adjustment. */
     615             :         struct tm *gtm;
     616             : 
     617             :         if(!(gtm=gmtime(&time))) return buf;
     618             : 
     619             :         if(gtm->tm_year==1970 && gtm->tm_mon==1 && gtm->tm_mday<3) return buf;
     620             : #endif
     621             : 
     622           0 :         if((tm=localtime(&time)))
     623             :                 n=sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
     624             :                         tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
     625           0 :                         tm->tm_hour, tm->tm_min, tm->tm_sec);
     626           0 :         return buf+n;
     627             : }

Generated by: LCOV version 1.10