-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Set a user agent for http/https tdnf requests #453
Conversation
@shivania2, you must sign our contributor license agreement before your changes are merged. Click here to sign the agreement. If you are a VMware employee, read this for further instruction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need a test.
client/config.c
Outdated
|
||
while (fgets(line, 2*UserAgentHeaderMaxLength, file)) { | ||
if (strstr(line, "ID=") != NULL) { | ||
sscanf(line, "ID=\"%[^\"]\"", pConf->pszOSName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested that this works with both unquoted and quoted settings?
client/prototypes.h
Outdated
uint32_t | ||
TDNFParseOSInfo( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this a static function in config.c
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use config parser for parsing /etc/os-release
?
Also, we have some info on OS with uname
APIs? can we use that here as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use config parser for parsing /etc/os-release?
No.
Also, we have some info on OS with uname APIs? can we use that here as well?
I don't think it's needed.
client/config.c
Outdated
strcat(pszUserAgentHeader, " "); | ||
} | ||
else | ||
strcat(pszUserAgentHeader, "UNKNOWN "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: use "unknown". No need to yell.
client/config.c
Outdated
@@ -91,6 +139,8 @@ TDNFReadConfig( | |||
} | |||
} | |||
|
|||
TDNFParseOSInfo(pConf); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validate return value here
client/config.c
Outdated
dwError = TDNFAllocateMemory(UserAgentHeaderMaxLength, sizeof(char), (void**)&pszUserAgentHeader); | ||
BAIL_ON_TDNF_ERROR(dwError); | ||
|
||
strcpy(pszUserAgentHeader, "User-Agent:tdnf/"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"User-Agent:tdnf/"
storing these keywords in macros would help
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use TDNFAllocateStringPrintf()
it will be a format string.
client/prototypes.h
Outdated
uint32_t | ||
TDNFParseOSInfo( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use config parser for parsing /etc/os-release
?
Also, we have some info on OS with uname
APIs? can we use that here as well?
client/config.c
Outdated
strcat(pszUserAgentHeader, "/"); | ||
|
||
if (pConf->pszOSVersion != NULL) | ||
strcat(pszUserAgentHeader, pConf->pszOSVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use snprintf
here.
client/config.c
Outdated
char *line = NULL; | ||
uint32_t dwError = 0; | ||
FILE *file = NULL; | ||
file = fopen("/etc/os-release", "r"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line before line 39 and store etc/os-release path in macro
client/config.c
Outdated
dwError = TDNFAllocateMemory(2*UserAgentHeaderMaxLength, sizeof(char), (void**)&line); | ||
BAIL_ON_TDNF_ERROR(dwError); | ||
|
||
dwError = TDNFAllocateMemory(UserAgentHeaderMaxLength, sizeof(char), (void**)&pConf->pszOSName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
50 bytes for name & version seems like an overkill and allocate memory based on line length inside loop.
Exploring the possible test case |
46bc356
to
1293042
Compare
client/config.c
Outdated
} | ||
|
||
while (fgets(temp, USERAGENTHEADERMAXLENGTH, file)) { | ||
if (strstr(temp, "ID=") != NULL && name_found == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use strncmp("ID=", temp, sizeof("ID=")-1);
no need of name_found and it will make this logic bit more robust
client/config.c
Outdated
BAIL_ON_TDNF_SYSTEM_ERROR(dwError); | ||
} | ||
|
||
while (fgets(temp, USERAGENTHEADERMAXLENGTH, file)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use getline
instead. Example here https://stackoverflow.com/questions/3501338/c-read-file-line-by-line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fgets()
is just fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fgets
works here but we assume that max line length is 50 bytes, with getline
we don't need not make any assumptions and fits perfectly for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
50 characters is too short, we can just create a 256 byte buffer on the stack, and no serious /etc/os-release
file will have lines any longer, and even if we can skip the rest. We can use getline()
, I just think it's overkill.
client/config.c
Outdated
static uint32_t TDNFParseOSInfo(PTDNF_CONF pConf) | ||
{ | ||
char temp[USERAGENTHEADERMAXLENGTH]; | ||
char name[10]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamically allocate memory for name and version and simply assign them to pConf->pszOSName and version.
Later it gets freed anyway.
1293042
to
8238803
Compare
client/config.c
Outdated
@@ -13,6 +13,9 @@ | |||
#include "../llconf/entry.h" | |||
#include "../llconf/ini.h" | |||
|
|||
#define USERAGENTHEADERMAXLENGTH 50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this better readable, use like USERAGENT_HEADER_MAXLENGTH
client/config.c
Outdated
BAIL_ON_TDNF_SYSTEM_ERROR(dwError); | ||
} | ||
|
||
dwError = TDNFAllocateMemory(10, sizeof(char), (void**)&name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't pre-allocate, parse the strings into the temp buffer first, then use TDNFAllocateString()
to allocate the exact amount you need.
client/config.c
Outdated
if (pConf->pszOSVersion == NULL) | ||
TDNFAllocateString("UNKNOWN", &pConf->pszOSVersion); | ||
|
||
dwError = TDNFAllocateStringPrintf(&pConf->pszUserAgentHeader, "User-Agent:tdnf/%s %s/%s", pszTdnfVersion, pConf->pszOSName, pConf->pszOSVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need a space after "User-Agent:". But, I think it doesn't need to be part of the string at all. See example at https://curl.se/libcurl/c/CURLOPT_USERAGENT.html
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Dark Secret Ninja/1.0");
Also make sure it's compliant with RFC 7231, see https://www.rfc-editor.org/rfc/rfc7231#page-46
Also, I don't see anywhere where you are setting the header for curl. At some point you will have to call curl_easy_setopt(curl, CURLOPT_USERAGENT, ...
8238803
to
12d3d44
Compare
client/config.c
Outdated
file = fopen(OS_CONF_FILE, "r"); | ||
|
||
if (!file) { | ||
dwError = errno; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
print a warning here and return 0 perhaps?
client/config.c
Outdated
@@ -26,6 +29,50 @@ TDNFConfGetRpmVerbosity( | |||
return nLogLevel; | |||
} | |||
|
|||
static uint32_t TDNFParseOSInfo(PTDNF_CONF pConf) | |||
{ | |||
char temp[USERAGENT_HEADER_MAX_LENGTH]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still say we should avoid static allocation here and use getline. It's upto you on what you want to use.
Also, be aware that fgets doesn't always null terminate the buffer. By chance if there is a line which is more than 50 bytes in os-release file, this might end up crashing tdnf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not what the man page says:
fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into
the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
Instead it seems to take care that it is aways terminated with 0.
client/config.c
Outdated
@@ -91,6 +139,11 @@ TDNFReadConfig( | |||
} | |||
} | |||
|
|||
dwError = TDNFParseOSInfo(pConf); | |||
if (dwError != ENOENT) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a question. If we the os-release file doesn't exist and it's considered not a fatal error, why should we consider other errors in TDNFParseOSInfo
as fatal? why not use some default values in case of failure and make the return type void?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think /etc/os-release
not present is an acceptable condition, but failing to parse it for any reason is not. It may be some deeper issue (a bug in the parser, file corrupted or whatever) , and we shouldn't proceed.
a9414c2
to
74df3aa
Compare
client/config.c
Outdated
TDNF_SAFE_FREE_MEMORY(name); | ||
TDNF_SAFE_FREE_MEMORY(version); | ||
goto cleanup; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete the empty line please
client/config.c
Outdated
if (strncmp("ID=", buf, sizeof("ID=")-1) == 0) | ||
{ | ||
sscanf(buf, "ID=%s\n", buf); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to take care of quotes, which may or may not be present. For example this is in Ubuntu:
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
...
This is in Photon:
# cat /etc/os-release
NAME="VMware Photon OS"
VERSION="5.0"
ID=photon
VERSION_ID=5.0
...
Note that Ubuntu has VERSION_ID
in quotes, but Photon does not. You need to check for quotes and remove them if there.
e547037
to
d6c7975
Compare
pytests/tests/test_useragent.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing a test! Are you going to finish this? I don't see where we are testing the header sent by tdnf (we are not calling it at all here).
7a52e76
to
ed0a329
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As stated per DM, let's drop the test change, it takes too much effort. I verified locally that the header is sent on requests. The change is good, I can merge this, without a test.
Thank You. I will remove the test case |
ed0a329
to
95c1054
Compare
tdnf does not set a user agent for http/https requests and the repository
pkgs.k8s.io
did block requests without user agents (because its the default policy in AWS WAF). This issue is fixed in kubernetes upstream repo. But to avoid this kind of issue in future, adding user agent for http/https tdnf requests