E certo... proviamo a capire cosa stiamo combinando.

Inizialmente tu dichiari un dato di tipo struct list che è appunto primo, assegni NULL al suo campo next (tieni a mente questo passaggio!) e al valore puntato da tmp (per il quale hai allocato l'opportuno spazio) assegni proprio primo.

Dopo leggi n da tastiera e per n-1 volte esegui quel ciclo for che in pratica ti costruisce la lista, allocando spazio per i suoi vari nodi.

Uscito dal ciclo, assegni NULL al campo next di tmp (giustamente, perché ora questo punta alla coda della lista, cioè all'ultimo nodo) ma dopo che fai? Riaggiorni il valore puntato da tmp a primo... ma primo aveva NULL come valore del campo next, quindi è ovvio che il flusso di controllo non entra nei cicli successivi (i predicati di entrambi, infatti, sono subito falsi).

Il punto è che quando vai a costruire la lista, al primo passo tu non aggiorni il campo next di primo perché il valore puntato da tmp è solo una copia di quest'ultimo... è un po' come quando passi un argomento per valore ad una funzione: questa lo può anche modificare, ma nella funzione chiamante questa modifica non sarà visibile.

Questo ti dovrebbe lasciar intuire che forse la soluzione consiste nel dichiarare primo come puntatore e cambiare un po' le cose di conseguenza (non tanto, anzi).

Tra l'altro ti faccio notare che l'ultimo ciclo while non andrebbe bene comunque perché non aggiorni il campo next di tmp (come invece fai correttamente in quello precedente).

Ah un'ultima cosa: quando programmi in C evita di ricorrere alle dichiarazioni sparse delle variabili nel mezzo del codice: è una caratteristica introdotta col C++.