2.1.25 login based pages not working with uwsgi

Bug #1744739 reported by David Runge
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
GNU Mailman
Low
Mark Sapiro

Bug Description

Mailman 2.1.25's login based pages (private archives, admin page, etc.) don't work with uwsgi (uwsgi times out printing a "invalid CGI response !!!" error).
Downgrading to 2.1.24 fixes this issue (I can again log in and uwsgi doesn't reply with the "invalid CGI response !!!" message).

I'm on Arch Linux trying to use mailman 2.1.25 with uwsgi 2.0.15 through nginx 1.12.2.
I've also opened a downstream bug there [1].
For completeness I will attach the same log files as in the aforementioned bug report.

The uwsgi configuration in use for mailman is pretty straight forward:

``
[uwsgi]
procname-master = mailman
master = true
plugins = cgi
socket = /run/uwsgi/%n.sock
stats = /run/uwsgi/%n-stats.sock
processes = 1
threads = 2
cheaper-step = 1
idle = 120
die-on-idle = true
uid = http
gid = http
cgi = /=/usr/lib/mailman/cgi-bin
cgi-index = listinfo
``

Nginx fronts the application server and redirects to a unix socket, which in turn starts a systemd service. More info on the setup can be found on my website [2].

``
[Unit]
Description=uWSGI service unit
After=syslog.target

[Service]
ExecStart=/usr/bin/uwsgi --ini /etc/uwsgi/%I.ini
Type=notify
SuccessExitStatus=15 17 29 30
StandardError=syslog
NotifyAccess=all
KillSignal=SIGQUIT
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=full
ReadWriteDirectories=/etc/webapps
ProtectHome=yes

[Install]
WantedBy=multi-user.target
``

``
[Unit]
Description=Socket for uWSGI %I

[Socket]
ListenStream=/run/uwsgi/%I.sock

[Install]
WantedBy=sockets.target
``

[1] https://bugs.archlinux.org/task/56865
[2] https://sleepmap.de/2016/securely-serving-webapps-using-uwsgi/

Related branches

Revision history for this message
David Runge (dvzrv) wrote :
Revision history for this message
Mark Sapiro (msapiro) wrote :

I am not familiar with uwsgi, so I really don't understand what's going on, but what I can tell you is between Mailman 2.1.24 and Mailman 2.1 25 There were no changes in Mailman/SecurityManager.py or Mailman/Cgi/Auth.py which are the modules that have to do with logging in and passwords. The only change in Mailman/Cgi/private.py changed a getting the username and password from

        username = cgidata.getvalue('username', '')
    password = cgidata.getvalue('password', '')

to

        username = cgidata.getfirst('username', '')
    password = cgidata.getfirst('password', '')

There were similar changes amongst others in Mailman/Cgi/admin.py and Mailman/Cgi/admindb.py

In the above, cgidata is an instance of the Python standard library cgi.FieldStorage class. See https://docs.python.org/2/library/cgi.html#higher-level-interface - the difference is getvalue() can return a string or a list depending of whether the post data contains a single or multiple settings for the variable. getfirst always returns a single value, never a list.

However, this all occurs in handling the CGI input passed to the invoked process so it's hard to see how this would produce what you are seeing. As far as output from the CGI is concerned, there should be no change at all to that from 'private'. 'admin' and 'admindb' do have some changes in CSS to support enhanced accessibility for visually impaired users, but nothing else.

Revision history for this message
David Runge (dvzrv) wrote :

Hmm, I wonder where to start debugging.
Might need to find an easy way of reverting the above mentioned changes and try again...

Revision history for this message
Mark Sapiro (msapiro) wrote :

I have rethought this, and I think my focus on changes to the CGI modules was a red herring. I think it's much more likely that http://bazaar.launchpad.net/~mailman-coders/mailman/2.1/revision/1720 is responsible. I suspect your nginx/uwsgi setup depends on passing some environment variable through the CGI wrapper that we are now (as of 2.1.25) not passing.

Revision history for this message
David Runge (dvzrv) wrote :

That sounds plausible.

I can show you what nginx does in my case (the relevant part):

``
# Send all access to / to uwsgi
location / {
  gzip off;
  include uwsgi_params;
  uwsgi_modifier1 9;
  uwsgi_pass unix:/run/uwsgi/mailman.sock;
}
``

where uwsgi_params consists of the standard, shipped params:

``
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;

uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;

uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
``

To sum it up:

What's not available in the nginx params but in the allowed mailman params: HOST, HTTP_COOKIE, HTTP_FORWARDED_FOR, HTTP_HOST, HTTP_X_FORWARDED_FOR, LOGNAME, SCRIPT_NAME, USER

What's not available in the allowed mailman params, but in the nginx params: CONTENT_LENGTH, DOCUMENT_ROOT, SERVER_PROTOCOL, REQUEST_SCHEME, HTTPS, REMOTE_PORT

I guess one of the params from the second list is needed to keep nginx happy? Or is it the other way round and I now need to provide a param, that I haven't before?

Revision history for this message
Mark Sapiro (msapiro) wrote :

It shouldn't be that you have to provide a "new" param because all we're doing is passing the ones in the whitelist if they are provided. If a param is not provided it can't be passed, but 2.1.24 works without it so 2.1.25 should too.

As far as ones we don't pass that are provided, I'm guessing CONTENT_LENGTH might be the issue, but I'm having trouble understanding why.

I'm having difficulty because I'm missing a piece here. I.e. uwsgi presumably expects to invoke a wsgi application, but the normal flow in Mailman is the web server invokes a CGI wrapper (say private) which is one of the things compiled using the code in src/common.c and which lives in Mailman at $prefix/cgi-bin/private. That wrapper is SETGID to Mailman's group. It does some checks, trims the environment and invokes $prefix/scripts/driver which understands which CGI is requested and invokes (in this example) Mailman/Cgi/private.py.

None of this is a wsgi application.

So what is the flow in your case?

One basic question is how do you install Mailman. If you build it from source, you could just edit src/common.c to add CONTENT_LENGTH=, DOCUMENT_ROOT=, SERVER_PROTOCOL=, REQUEST_SCHEME=, HTTPS= and REMOTE_PORT= to the keepenvars list and see if that lets it work.

Also, I have a somewhat uneasy feeling about this because even though I said I thought this was the likely cause, if it is I would expect it to affect all the CGIs. Does listinfo work? If so, then this trimming of the environment may not be the issue after all.

If listinfo works, then you might try in your 2.1.24 installation, replacing the two occurrences of cgidata.getvalue with cgidata.getfirst in Mailman/Cgi/private.py and see if that breaks it.

Revision history for this message
David Runge (dvzrv) wrote :

I just patched src/common.c with the mentioned parameters in the Arch package (see attachment) and now 2.1.25 works!

Revision history for this message
Mark Sapiro (msapiro) wrote :

OK, thanks.

I'll add these in the next release.

Changed in mailman:
assignee: nobody → Mark Sapiro (msapiro)
importance: Undecided → Low
milestone: none → 2.1.26
status: New → In Progress
Mark Sapiro (msapiro)
Changed in mailman:
status: In Progress → Fix Committed
Mark Sapiro (msapiro)
Changed in mailman:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers