Alpaca can do that.
The idea is to run alpaca as a local proxy. Alpaca will fetch the pac file from the network (or use a specified one), as well as your NT credentials.
Other programs will use the local proxy and be transparently forwarded to the upstream proxy or a direct connection, as instructed by alpaca.
Running the Alpaca proxy
You will want alpaca to run in the background. Either start it manually, or create a service file to do so. The alpaca-proxy AUR package contains a systemd user unit file, for instance.
Local pac file, no auth
alpaca -C "file:///home/username/proxy.pac.js"
An example pac file is provided at the bottom of this answer.
Configuration in environment, NTLM proxy authentication
$ export LISTEN_ADDRESS=localhost
$ export LISTEN_PORT=3128
$ export NTLM_CREDENTIALS="myusername@MYDOMAIN:00000000000000000000000000000000"
$ export PAC_URL="http://some.url/to/some-file.pac"
$ alpaca
Found credentials for MYDOMAIN\me in environment
pacfetcher.go:100: Attempting to download PAC from http://some.url/to/some-file.pac
main.go:115: Listening on tcp4 localhost:3128
main.go:115: Listening on tcp6 localhost:3128
proxyfinder.go:135: [1] GET http://google.com/ via "PROXY 11.12.13.14:80"
NTLM credentials can be obtained from alpaca:
$ alpaca -d MYDOMAIN -u myusername -H
Using the proxy
Once the proxy is running, instruct programs to use the proxy on the local host.
Explicit proxy configuration
You can do a quick test by instructing a given program to use the proxy. As supported by curl:
curl --proxy "http://127.0.0.1:3128" https://ipinfo.io/ip
Environment variables
Most programs support the lowercase http(s)_proxy environment variable (some programs require them to be upercase, so we use both).
$ export http_proxy='http://localhost:3128'
$ export https_proxy="$http_proxy" HTTPS_PROXY="$http_proxy" HTTP_PROXY="$http_proxy"
$ curl google.com
curl -v google.com
* Uses proxy env variable http_proxy == 'http://localhost:3128'
* Host localhost:3128 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3128...
* Established connection to localhost (::1 port 3128) from ::1 port 58512
* using HTTP/1.x
> GET http://google.com/ HTTP/1.1
[...]
Example pac file:
// ES5 syntax only https://github.com/robertkrimen/otto#caveat-emptor
var blocked_nhosts_list = [
// example.com will only match example.com and www.example.com
'play.google.com',
];
var blocked_sl_tl_hosts_list = [
// example.com will match example.com and all its subdomains
'ipinfo.io',
];
var blocked_nhosts = {};
blocked_nhosts_list.forEach(function (v) { blocked_nhosts[v] = true });
var blocked_sl_tl_hosts = {};
blocked_sl_tl_hosts_list.forEach(function (v) { blocked_sl_tl_hosts[v] = true });
var PROXY = 'PROXY 127.0.0.1:1081';
var DIRECT = 'DIRECT';
// extract SLD.TLD from host: sub.example.com -> example.com
var second_level_host_regex = /[^.]+\.[^.]+$/;
// extract THLD.SLD.TLD from host: more.sub.example.com -> sub.example.com
var third_level_host_regex = /[^.]+\.[^.]+\.[^.]+$/;
function FindProxyForURL(url, host) {
// normalized host
var nhost = host;
if (nhost.substring(0, 4) === 'www.') {
nhost = nhost.substring(4);
}
if (nhost.substring(nhost.length - 1) === '.') {
nhost = nhost.substring(0, nhost.length - 1);
}
if (isPlainHostName(nhost)) return DIRECT;
// second level host
var slhost = nhost.match(second_level_host_regex)[0];
// third level host
var match = nhost.match(third_level_host_regex);
var tlhost = match ? match[0] : null;
if (blocked_nhosts[nhost]) return PROXY;
if (blocked_sl_tl_hosts[slhost]) return PROXY;
if (tlhost !== null && blocked_sl_tl_hosts[tlhost]) return PROXY;
if (dnsDomainIs(nhost, '.dev')) return PROXY;
return DIRECT;
}