Merge branch 'master' into rorcz
[girocco.git] / src / peek_packet.c
blob60d0bd83d1f3334c838acfdf49c147ccdd00ef52
1 /*
3 peek_packet.c -- peek_packet utility to peek at incoming git-daemon request
4 Copyright (C) 2015 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 This utility is intended to be used by a script front end to git daemon
24 running in inetd mode. The first thing the script does is call this utility
25 which attempts to peek the first incoming Git packet off the connection
26 and then output contents after the initial 4-character hex length upto but
27 excluding the first \0 character.
29 At that point the script can validate the incoming request and if it chooses
30 to allow it then exec git daemon to process it. Since the packet was peeked
31 it's still there to be read by git daemon.
33 Note that there is a hard-coded timeout of 30 seconds and a hard-coded limit
34 of PATH_MAX for the length of the initial packet.
36 On failure a non-zero exit code is returned. On success a 0 exit code is
37 returned and peeked text is output to stdout.
39 The connection to be peeked must be fd 0.
41 This utility does not take any arguments and ignores any that are given.
43 The output of a successful peek should be one of these:
45 git-upload-pack /<...>
46 git-upload-archive /<...>
47 git-receive-pack /<...>
49 where "<...>" is replaced with the repository path so, for example, doing
50 "git ls-remote git://example.com/foo.git" would result in this output:
52 git-upload-pack /foo.git
54 Note that the first character could be a ~ instead of a /, but it's
55 probably best to reject those.
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 #include <limits.h>
63 #include <time.h>
64 #include <sys/socket.h>
66 /* Note that mod_reqtimeout has a default configuration of 20 seconds
67 * maximum to wait for the first byte of the initial request line and
68 * then no more than 40 seconds total, but after the first byte is
69 * received the rest must arrive at 500 bytes/sec or faster. That
70 * means 10000 bytes minimum in 40 seconds. We do not allow the
71 * initial Git packet to be longer than PATH_MAX (which is typically
72 * either 1024 or 4096). And since 20 + 1024/500 = 22.048 and
73 * 20 + 4096/500 = 28.192 using 30 seconds for a total timeout is
74 * quite reasonable in comparison to mod_reqtimeout's default conf.
77 #define TIMEOUT_SECS 30 /* no more than 30 seconds for initial packet */
79 #define POLL_QUANTUM 100000U /* how often to poll in microseconds */
81 static int xdig(char c)
83 if ('0' <= c && c <= '9')
84 return c - '0';
85 if ('a' <= c && c <= 'f')
86 return c - 'a' + 10;
87 if ('A' <= c && c <= 'F')
88 return c - 'A' + 10;
89 return -1;
92 static char buffer[PATH_MAX];
93 static time_t expiry;
95 /* Ideally we could just use MSG_PEEK + MSG_WAITALL, and that works nicely
96 * on BSD-type distros. Unfortunately very bad things happen on Linux with
97 * that combination -- a CPU core runs at 100% until all the data arrives.
98 * So instead we omit the MSG_WAITALL and poll every POLL_QUANTUM interval
99 * to see if we've satisfied the requested amount yet.
101 static int recv_peekall(int fd, void *buff, size_t len)
103 int ans;
104 while ((ans = recv(fd, buff, len, MSG_PEEK)) > 0 && (size_t)ans < len) {
105 if (time(NULL) > expiry)
106 exit(2);
107 usleep(POLL_QUANTUM);
109 return ans < 0 ? -1 : (int)len;
112 int main(int argc, char *argv[])
114 int len;
115 int xvals[4];
116 char hexlen[4];
117 size_t pktlen;
118 const char *nullptr;
120 (void)argc;
121 (void)argv;
123 expiry = time(NULL) + TIMEOUT_SECS;
125 len = recv_peekall(0, hexlen, 4);
126 if (len != 4)
127 return 1;
129 if ((xvals[0]=xdig(hexlen[0])) < 0 ||
130 (xvals[1]=xdig(hexlen[1])) < 0 ||
131 (xvals[2]=xdig(hexlen[2])) < 0 ||
132 (xvals[3]=xdig(hexlen[3])) < 0)
133 return 1;
134 pktlen = ((unsigned)xvals[0] << 12) |
135 ((unsigned)xvals[1] << 8) |
136 ((unsigned)xvals[2] << 4) |
137 (unsigned)xvals[3];
138 if (pktlen < 22 || pktlen > sizeof(buffer))
139 return 1;
141 len = recv_peekall(0, buffer, pktlen);
142 if (len != (int)pktlen)
143 return 1;
145 if (memcmp(buffer+4, "git-", 4)) /* sanity check */
146 return 1;
147 nullptr = (const char *)memchr(buffer+4, 0, pktlen-4);
148 if (!nullptr || nullptr < (buffer+21))
149 return 1;
150 puts(buffer + 4);
152 return 0;